From 2fb63b99f7e32e311bf9d2e18a72d946f516dba1 Mon Sep 17 00:00:00 2001 From: SpreetailHurley <48611385+SpreetailHurley@users.noreply.github.com> Date: Thu, 2 May 2019 12:17:00 -0500 Subject: [PATCH 1/6] Add files via upload --- digest.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ tag.go | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 digest.go create mode 100644 tag.go diff --git a/digest.go b/digest.go new file mode 100644 index 000000000..7efc115e9 --- /dev/null +++ b/digest.go @@ -0,0 +1,97 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package name defines structured types for representing image references. +package name + +import ( + "strings" +) + +const ( + // These have the form: sha256: + // TODO(dekkagaijin): replace with opencontainers/go-digest or docker/distribution's validation. + digestChars = "sh:0123456789abcdef" + digestDelim = "@" +) + +// Digest stores a digest name in a structured form. +type Digest struct { + Repository + digest string +} + +// Ensure Digest implements Reference +var _ Reference = (*Digest)(nil) + +// Context implements Reference. +func (d Digest) Context() Repository { + return d.Repository +} + +// Identifier implements Reference. +func (d Digest) Identifier() string { + return d.DigestStr() +} + +// DigestStr returns the digest component of the Digest. +func (d Digest) DigestStr() string { + return d.digest +} + +// Name returns the name from which the Digest was derived. +func (d Digest) Name() string { + return d.Repository.Name() + digestDelim + d.DigestStr() +} + +func (d Digest) String() string { + return d.Name() +} + +func checkDigest(name string) error { + return checkElement("digest", name, digestChars, 7+64, 7+64) +} + +// NewDigest returns a new Digest representing the given name, according to the given strictness. +func NewDigest(name string, strict Strictness) (Digest, error) { + // Split on "@" + parts := strings.Split(name, digestDelim) + if len(parts) != 2 { + return Digest{}, NewErrBadName("a digest must contain exactly one '@' separator (e.g. registry/repository@digest) saw: %s", name) + } + base := parts[0] + digest := parts[1] + + // A digest seems to be the most-specific metadata so it is preferred over a tag match + // To avoid conflicts in handling of both tags and digests, when an image is found to have both then prefer the digest + if strings.Contains( base,":" ) { + base = strings.Split( base,":" )[0] + } + + // We don't require a digest, but if we get one check it's valid, + // even when not being strict. + // If we are being strict, we want to validate the digest regardless in case + // it's empty. + if digest != "" || strict == StrictValidation { + if err := checkDigest(digest); err != nil { + return Digest{}, err + } + } + + repo, err := NewRepository(base, strict) + if err != nil { + return Digest{}, err + } + return Digest{repo, digest}, nil +} diff --git a/tag.go b/tag.go new file mode 100644 index 000000000..c6a4edcb8 --- /dev/null +++ b/tag.go @@ -0,0 +1,109 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package name + +import ( + "fmt" + "strings" +) + +const ( + defaultTag = "latest" + // TODO(dekkagaijin): use the docker/distribution regexes for validation. + tagChars = "abcdefghijklmnopqrstuvwxyz0123456789_-.ABCDEFGHIJKLMNOPQRSTUVWXYZ" + tagDelim = ":" +) + +// Tag stores a docker tag name in a structured form. +type Tag struct { + Repository + tag string +} + +// Ensure Tag implements Reference +var _ Reference = (*Tag)(nil) + +// Context implements Reference. +func (t Tag) Context() Repository { + return t.Repository +} + +// Identifier implements Reference. +func (t Tag) Identifier() string { + return t.TagStr() +} + +// TagStr returns the tag component of the Tag. +func (t Tag) TagStr() string { + if t.tag != "" { + return t.tag + } + return defaultTag +} + +// Name returns the name from which the Tag was derived. +func (t Tag) Name() string { + return t.Repository.Name() + tagDelim + t.TagStr() +} + +func (t Tag) String() string { + return t.Name() +} + +// Scope returns the scope required to perform the given action on the tag. +func (t Tag) Scope(action string) string { + return t.Repository.Scope(action) +} + +func checkTag(name string) error { + return checkElement("tag", name, tagChars, 1, 127) +} + +// NewTag returns a new Tag representing the given name, according to the given strictness. +func NewTag(name string, strict Strictness) (Tag, error) { + base := name + tag := "" + + // Split on ":" + parts := strings.Split(name, tagDelim) + // Verify that we aren't confusing a tag for a hostname w/ port for the purposes of weak validation. + if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { + base = strings.Join(parts[:len(parts)-1], tagDelim) + tag = parts[len(parts)-1] + } + + // GCP Cloud Build returns :@sha: + // The above 'split' will strip the digest and attempt to return :@sha as the base + // If this is detected, consider it an error condition for NewTag; handling is performed in NewDigest + if strings.Contains( base,"@" ) { + return Tag{}, fmt.Errorf("Found digest; bailing out") + } + + // We don't require a tag, but if we get one check it's valid, + // even when not being strict. + // If we are being strict, we want to validate the tag regardless in case + // it's empty. + if tag != "" || strict == StrictValidation { + if err := checkTag(tag); err != nil { + return Tag{}, err + } + } + + repo, err := NewRepository(base, strict) + if err != nil { + return Tag{}, err + } + return Tag{repo, tag}, nil +} From a08ee73932bceeb8739a47801efcfac966ddcc82 Mon Sep 17 00:00:00 2001 From: Jonathan Hurley Date: Thu, 2 May 2019 12:21:47 -0500 Subject: [PATCH 2/6] [issue-345] cleanup bad upload --- digest.go | 97 ---------------- tag.go | 109 ------------------ .../go-containerregistry/pkg/name/digest.go | 6 + .../go-containerregistry/pkg/name/tag.go | 8 ++ 4 files changed, 14 insertions(+), 206 deletions(-) delete mode 100644 digest.go delete mode 100644 tag.go diff --git a/digest.go b/digest.go deleted file mode 100644 index 7efc115e9..000000000 --- a/digest.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2018 Google LLC All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package name defines structured types for representing image references. -package name - -import ( - "strings" -) - -const ( - // These have the form: sha256: - // TODO(dekkagaijin): replace with opencontainers/go-digest or docker/distribution's validation. - digestChars = "sh:0123456789abcdef" - digestDelim = "@" -) - -// Digest stores a digest name in a structured form. -type Digest struct { - Repository - digest string -} - -// Ensure Digest implements Reference -var _ Reference = (*Digest)(nil) - -// Context implements Reference. -func (d Digest) Context() Repository { - return d.Repository -} - -// Identifier implements Reference. -func (d Digest) Identifier() string { - return d.DigestStr() -} - -// DigestStr returns the digest component of the Digest. -func (d Digest) DigestStr() string { - return d.digest -} - -// Name returns the name from which the Digest was derived. -func (d Digest) Name() string { - return d.Repository.Name() + digestDelim + d.DigestStr() -} - -func (d Digest) String() string { - return d.Name() -} - -func checkDigest(name string) error { - return checkElement("digest", name, digestChars, 7+64, 7+64) -} - -// NewDigest returns a new Digest representing the given name, according to the given strictness. -func NewDigest(name string, strict Strictness) (Digest, error) { - // Split on "@" - parts := strings.Split(name, digestDelim) - if len(parts) != 2 { - return Digest{}, NewErrBadName("a digest must contain exactly one '@' separator (e.g. registry/repository@digest) saw: %s", name) - } - base := parts[0] - digest := parts[1] - - // A digest seems to be the most-specific metadata so it is preferred over a tag match - // To avoid conflicts in handling of both tags and digests, when an image is found to have both then prefer the digest - if strings.Contains( base,":" ) { - base = strings.Split( base,":" )[0] - } - - // We don't require a digest, but if we get one check it's valid, - // even when not being strict. - // If we are being strict, we want to validate the digest regardless in case - // it's empty. - if digest != "" || strict == StrictValidation { - if err := checkDigest(digest); err != nil { - return Digest{}, err - } - } - - repo, err := NewRepository(base, strict) - if err != nil { - return Digest{}, err - } - return Digest{repo, digest}, nil -} diff --git a/tag.go b/tag.go deleted file mode 100644 index c6a4edcb8..000000000 --- a/tag.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018 Google LLC All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package name - -import ( - "fmt" - "strings" -) - -const ( - defaultTag = "latest" - // TODO(dekkagaijin): use the docker/distribution regexes for validation. - tagChars = "abcdefghijklmnopqrstuvwxyz0123456789_-.ABCDEFGHIJKLMNOPQRSTUVWXYZ" - tagDelim = ":" -) - -// Tag stores a docker tag name in a structured form. -type Tag struct { - Repository - tag string -} - -// Ensure Tag implements Reference -var _ Reference = (*Tag)(nil) - -// Context implements Reference. -func (t Tag) Context() Repository { - return t.Repository -} - -// Identifier implements Reference. -func (t Tag) Identifier() string { - return t.TagStr() -} - -// TagStr returns the tag component of the Tag. -func (t Tag) TagStr() string { - if t.tag != "" { - return t.tag - } - return defaultTag -} - -// Name returns the name from which the Tag was derived. -func (t Tag) Name() string { - return t.Repository.Name() + tagDelim + t.TagStr() -} - -func (t Tag) String() string { - return t.Name() -} - -// Scope returns the scope required to perform the given action on the tag. -func (t Tag) Scope(action string) string { - return t.Repository.Scope(action) -} - -func checkTag(name string) error { - return checkElement("tag", name, tagChars, 1, 127) -} - -// NewTag returns a new Tag representing the given name, according to the given strictness. -func NewTag(name string, strict Strictness) (Tag, error) { - base := name - tag := "" - - // Split on ":" - parts := strings.Split(name, tagDelim) - // Verify that we aren't confusing a tag for a hostname w/ port for the purposes of weak validation. - if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { - base = strings.Join(parts[:len(parts)-1], tagDelim) - tag = parts[len(parts)-1] - } - - // GCP Cloud Build returns :@sha: - // The above 'split' will strip the digest and attempt to return :@sha as the base - // If this is detected, consider it an error condition for NewTag; handling is performed in NewDigest - if strings.Contains( base,"@" ) { - return Tag{}, fmt.Errorf("Found digest; bailing out") - } - - // We don't require a tag, but if we get one check it's valid, - // even when not being strict. - // If we are being strict, we want to validate the tag regardless in case - // it's empty. - if tag != "" || strict == StrictValidation { - if err := checkTag(tag); err != nil { - return Tag{}, err - } - } - - repo, err := NewRepository(base, strict) - if err != nil { - return Tag{}, err - } - return Tag{repo, tag}, nil -} diff --git a/vendor/github.com/google/go-containerregistry/pkg/name/digest.go b/vendor/github.com/google/go-containerregistry/pkg/name/digest.go index ea6287a84..7efc115e9 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/name/digest.go +++ b/vendor/github.com/google/go-containerregistry/pkg/name/digest.go @@ -73,6 +73,12 @@ func NewDigest(name string, strict Strictness) (Digest, error) { base := parts[0] digest := parts[1] + // A digest seems to be the most-specific metadata so it is preferred over a tag match + // To avoid conflicts in handling of both tags and digests, when an image is found to have both then prefer the digest + if strings.Contains( base,":" ) { + base = strings.Split( base,":" )[0] + } + // We don't require a digest, but if we get one check it's valid, // even when not being strict. // If we are being strict, we want to validate the digest regardless in case diff --git a/vendor/github.com/google/go-containerregistry/pkg/name/tag.go b/vendor/github.com/google/go-containerregistry/pkg/name/tag.go index b8375e1f9..c6a4edcb8 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/name/tag.go +++ b/vendor/github.com/google/go-containerregistry/pkg/name/tag.go @@ -15,6 +15,7 @@ package name import ( + "fmt" "strings" ) @@ -83,6 +84,13 @@ func NewTag(name string, strict Strictness) (Tag, error) { tag = parts[len(parts)-1] } + // GCP Cloud Build returns :@sha: + // The above 'split' will strip the digest and attempt to return :@sha as the base + // If this is detected, consider it an error condition for NewTag; handling is performed in NewDigest + if strings.Contains( base,"@" ) { + return Tag{}, fmt.Errorf("Found digest; bailing out") + } + // We don't require a tag, but if we get one check it's valid, // even when not being strict. // If we are being strict, we want to validate the tag regardless in case From 4a3811ee2b9d74c899bf9438861d463f06efc92f Mon Sep 17 00:00:00 2001 From: Jonathan Hurley Date: Fri, 3 May 2019 11:34:28 -0500 Subject: [PATCH 3/6] [issue-345] correcting for signer.go --- pkg/kritis/gcbsigner/signer.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/pkg/kritis/gcbsigner/signer.go b/pkg/kritis/gcbsigner/signer.go index 5f9ace890..93746916e 100644 --- a/pkg/kritis/gcbsigner/signer.go +++ b/pkg/kritis/gcbsigner/signer.go @@ -17,6 +17,9 @@ limitations under the License. package gcbsigner import ( + "fmt" + "strings" + "github.com/golang/glog" "github.com/grafeas/kritis/pkg/kritis/apis/kritis/v1beta1" "github.com/grafeas/kritis/pkg/kritis/crd/authority" @@ -59,13 +62,27 @@ var ( // Returns an error if creating an attestation for any authority fails. func (s Signer) ValidateAndSign(prov BuildProvenance, bps []v1beta1.BuildPolicy) error { for _, bp := range bps { - glog.Infof("Validating %q against BuildPolicy %q", prov.ImageRef, bp.Name) + + // if prov.ImageRef is formatted by GCP Cloud Build then it will take the form /:@sha256: ; this breaks in s.config.Validate because of how the string is split + // instead, if prov.ImageRef contains more than 1 ':', split it further and then reassemble the string to pass to Validate + // this assumes that the digest is the most specific identifier for an image and, as a result, the tag is discarded if both are found + ImageRef := "" + parts := strings.Split( prov.ImageRef,":" ) + if len( parts ) > 2 { + digest := parts[ len(parts)-1 ] + repo := strings.Split( parts[ 0 ],"@" )[0] + ImageRef = ( fmt.Sprintf( "%s@sha256:%s", repo, digest ) ) + } else { + ImageRef = prov.ImageRef + } + + glog.Infof("Validating %q against BuildPolicy %q", ImageRef, bp.Name) if result := s.config.Validate(bp, prov.BuiltFrom); result != nil { - glog.Errorf("Image %q does not match BuildPolicy %q: %s", prov.ImageRef, bp.ObjectMeta.Name, result) + glog.Errorf("Image %q does not match BuildPolicy %q: %s", ImageRef, bp.ObjectMeta.Name, result) continue } - glog.Infof("Image %q matches BuildPolicy %s, creating attestations", prov.ImageRef, bp.Name) - if err := s.addAttestation(prov.ImageRef, bp.Namespace, bp.Spec.AttestationAuthorityName); err != nil { + glog.Infof("Image %q matches BuildPolicy %s, creating attestations", ImageRef, bp.Name) + if err := s.addAttestation(ImageRef, bp.Namespace, bp.Spec.AttestationAuthorityName); err != nil { return err } } From 111b9182294591cb33949ebd2239fed85590f861 Mon Sep 17 00:00:00 2001 From: Jonathan Hurley Date: Fri, 3 May 2019 11:41:30 -0500 Subject: [PATCH 4/6] [issue-345] correcting for containeranalysis.go --- .../metadata/containeranalysis/containeranalysis.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/kritis/metadata/containeranalysis/containeranalysis.go b/pkg/kritis/metadata/containeranalysis/containeranalysis.go index f06298a6b..f9bb856df 100644 --- a/pkg/kritis/metadata/containeranalysis/containeranalysis.go +++ b/pkg/kritis/metadata/containeranalysis/containeranalysis.go @@ -112,6 +112,17 @@ func (c Client) fetchOccurrence(containerImage string, kind string) ([]*grafeas. } func isValidImageOnGCR(containerImage string) bool { + + // if containerImage is formatted by GCP Cloud Build then it will take the form /:@sha256: ; this breaks in name.ParseReference because of how the string is split + // instead, if containerImage contains more than 1 ':', split it further and then reassemble the string to pass to ParseReference + // this assumes that the digest is the most specific identifier for an image and, as a result, the tag is discarded if both are found + parts := strings.Split( containerImage,":" ) + if len( parts ) > 2 { + digest := parts[ len(parts)-1 ] + repo := strings.Split( parts[ 0 ],"@" )[0] + return isValidImageOnGCR( fmt.Sprintf( "%s@sha256:%s", repo, digest ) ) + } + ref, err := name.ParseReference(containerImage, name.WeakValidation) if err != nil { glog.Warning(err) @@ -190,6 +201,7 @@ func (c Client) CreateAttestationOccurence(note *grafeas.Note, // Create Attestation Signature sig, err := util.CreateAttestationSignature(containerImage, pgpSigningKey) + if err != nil { return nil, err } From 6d1d5a7bdfc96d52af265b83dd71d7e9db0dbc53 Mon Sep 17 00:00:00 2001 From: Jonathan Hurley Date: Fri, 3 May 2019 11:49:17 -0500 Subject: [PATCH 5/6] [issue-345] resolving paren formatting --- pkg/kritis/gcbsigner/signer.go | 10 +++++----- .../metadata/containeranalysis/containeranalysis.go | 11 +++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pkg/kritis/gcbsigner/signer.go b/pkg/kritis/gcbsigner/signer.go index 93746916e..a41284a04 100644 --- a/pkg/kritis/gcbsigner/signer.go +++ b/pkg/kritis/gcbsigner/signer.go @@ -67,11 +67,11 @@ func (s Signer) ValidateAndSign(prov BuildProvenance, bps []v1beta1.BuildPolicy) // instead, if prov.ImageRef contains more than 1 ':', split it further and then reassemble the string to pass to Validate // this assumes that the digest is the most specific identifier for an image and, as a result, the tag is discarded if both are found ImageRef := "" - parts := strings.Split( prov.ImageRef,":" ) - if len( parts ) > 2 { - digest := parts[ len(parts)-1 ] - repo := strings.Split( parts[ 0 ],"@" )[0] - ImageRef = ( fmt.Sprintf( "%s@sha256:%s", repo, digest ) ) + parts := strings.Split(prov.ImageRef, ":") + if len(parts) > 2 { + digest := parts[len(parts)-1] + repo := strings.Split(parts[0], "@")[0] + ImageRef = fmt.Sprintf("%s@sha256:%s", repo, digest) } else { ImageRef = prov.ImageRef } diff --git a/pkg/kritis/metadata/containeranalysis/containeranalysis.go b/pkg/kritis/metadata/containeranalysis/containeranalysis.go index f9bb856df..752e695ef 100644 --- a/pkg/kritis/metadata/containeranalysis/containeranalysis.go +++ b/pkg/kritis/metadata/containeranalysis/containeranalysis.go @@ -112,15 +112,14 @@ func (c Client) fetchOccurrence(containerImage string, kind string) ([]*grafeas. } func isValidImageOnGCR(containerImage string) bool { - // if containerImage is formatted by GCP Cloud Build then it will take the form /:@sha256: ; this breaks in name.ParseReference because of how the string is split // instead, if containerImage contains more than 1 ':', split it further and then reassemble the string to pass to ParseReference // this assumes that the digest is the most specific identifier for an image and, as a result, the tag is discarded if both are found - parts := strings.Split( containerImage,":" ) - if len( parts ) > 2 { - digest := parts[ len(parts)-1 ] - repo := strings.Split( parts[ 0 ],"@" )[0] - return isValidImageOnGCR( fmt.Sprintf( "%s@sha256:%s", repo, digest ) ) + parts := strings.Split(containerImage, ":") + if len(parts) > 2 { + digest := parts[len(parts)-1] + repo := strings.Split(parts[0], "@")[0] + return isValidImageOnGCR(fmt.Sprintf("%s@sha256:%s", repo, digest)) } ref, err := name.ParseReference(containerImage, name.WeakValidation) From 97cc3dad99ab4bfa556b8ad11686f3a0a65bdd5d Mon Sep 17 00:00:00 2001 From: Jonathan Hurley Date: Fri, 3 May 2019 11:54:48 -0500 Subject: [PATCH 6/6] reverting to upstream --- .../google/go-containerregistry/pkg/name/digest.go | 6 ------ .../google/go-containerregistry/pkg/name/tag.go | 8 -------- 2 files changed, 14 deletions(-) diff --git a/vendor/github.com/google/go-containerregistry/pkg/name/digest.go b/vendor/github.com/google/go-containerregistry/pkg/name/digest.go index 7efc115e9..ea6287a84 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/name/digest.go +++ b/vendor/github.com/google/go-containerregistry/pkg/name/digest.go @@ -73,12 +73,6 @@ func NewDigest(name string, strict Strictness) (Digest, error) { base := parts[0] digest := parts[1] - // A digest seems to be the most-specific metadata so it is preferred over a tag match - // To avoid conflicts in handling of both tags and digests, when an image is found to have both then prefer the digest - if strings.Contains( base,":" ) { - base = strings.Split( base,":" )[0] - } - // We don't require a digest, but if we get one check it's valid, // even when not being strict. // If we are being strict, we want to validate the digest regardless in case diff --git a/vendor/github.com/google/go-containerregistry/pkg/name/tag.go b/vendor/github.com/google/go-containerregistry/pkg/name/tag.go index c6a4edcb8..b8375e1f9 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/name/tag.go +++ b/vendor/github.com/google/go-containerregistry/pkg/name/tag.go @@ -15,7 +15,6 @@ package name import ( - "fmt" "strings" ) @@ -84,13 +83,6 @@ func NewTag(name string, strict Strictness) (Tag, error) { tag = parts[len(parts)-1] } - // GCP Cloud Build returns :@sha: - // The above 'split' will strip the digest and attempt to return :@sha as the base - // If this is detected, consider it an error condition for NewTag; handling is performed in NewDigest - if strings.Contains( base,"@" ) { - return Tag{}, fmt.Errorf("Found digest; bailing out") - } - // We don't require a tag, but if we get one check it's valid, // even when not being strict. // If we are being strict, we want to validate the tag regardless in case