func TestParseImageAndDockerReference(t *testing.T) { const ( ok1 = "busybox" ok2 = fullRHELRef bad1 = "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES" bad2 = "" ) // Success ref, err := reference.ParseNamed(ok1) require.NoError(t, err) r1, r2, err := parseImageAndDockerReference(refImageMock{ref}, ok2) require.NoError(t, err) assert.Equal(t, ok1, r1.String()) assert.Equal(t, ok2, r2.String()) // Unidentified images are rejected. _, _, err = parseImageAndDockerReference(refImageMock{nil}, ok2) require.Error(t, err) assert.IsType(t, PolicyRequirementError(""), err) // Failures for _, refs := range [][]string{ {bad1, ok2}, {ok1, bad2}, {bad1, bad2}, } { ref, err := reference.ParseNamed(refs[0]) if err == nil { _, _, err := parseImageAndDockerReference(refImageMock{ref}, refs[1]) assert.Error(t, err) } } }
// ParseStoreReference takes a name or an ID, tries to figure out which it is // relative to the given store, and returns it in a reference object. func (s storageTransport) ParseStoreReference(store storage.Store, ref string) (*storageReference, error) { var name reference.Named var sum digest.Digest var err error if ref == "" { return nil, ErrInvalidReference } if ref[0] == '[' { // Ignore the store specifier. closeIndex := strings.IndexRune(ref, ']') if closeIndex < 1 { return nil, ErrInvalidReference } ref = ref[closeIndex+1:] } refInfo := strings.SplitN(ref, "@", 2) if len(refInfo) == 1 { // A name. name, err = reference.ParseNamed(refInfo[0]) if err != nil { return nil, err } } else if len(refInfo) == 2 { // An ID, possibly preceded by a name. if refInfo[0] != "" { name, err = reference.ParseNamed(refInfo[0]) if err != nil { return nil, err } } sum, err = digest.Parse("sha256:" + refInfo[1]) if err != nil { return nil, err } } else { // Coverage: len(refInfo) is always 1 or 2 // Anything else: store specified in a form we don't // recognize. return nil, ErrInvalidReference } storeSpec := "[" + store.GetGraphDriverName() + "@" + store.GetGraphRoot() + "]" id := "" if sum.Validate() == nil { id = sum.Hex() } refname := "" if name != nil { name = reference.WithDefaultTag(name) refname = verboseName(name) } if refname == "" { logrus.Debugf("parsed reference into %q", storeSpec+"@"+id) } else if id == "" { logrus.Debugf("parsed reference into %q", storeSpec+refname) } else { logrus.Debugf("parsed reference into %q", storeSpec+refname+"@"+id) } return newReference(storageTransport{store: store}, refname, id, name), nil }
func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { // Check that there's a store location prefix. Values we're passed are // expected to come from PolicyConfigurationIdentity or // PolicyConfigurationNamespaces, so if there's no store location, // something's wrong. if scope[0] != '[' { return ErrInvalidReference } // Parse the store location prefix. closeIndex := strings.IndexRune(scope, ']') if closeIndex < 1 { return ErrInvalidReference } storeSpec := scope[1:closeIndex] scope = scope[closeIndex+1:] storeInfo := strings.SplitN(storeSpec, "@", 2) if len(storeInfo) == 1 && storeInfo[0] != "" { // One component: the graph root. if !filepath.IsAbs(storeInfo[0]) { return ErrPathNotAbsolute } } else if len(storeInfo) == 2 && storeInfo[0] != "" && storeInfo[1] != "" { // Two components: the driver type and the graph root. if !filepath.IsAbs(storeInfo[1]) { return ErrPathNotAbsolute } } else { // Anything else: store specified in a form we don't // recognize. return ErrInvalidReference } // That might be all of it, and that's okay. if scope == "" { return nil } // But if there is anything left, it has to be a name, with or without // a tag, with or without an ID, since we don't return namespace values // that are just bare IDs. scopeInfo := strings.SplitN(scope, "@", 2) if len(scopeInfo) == 1 && scopeInfo[0] != "" { _, err := reference.ParseNamed(scopeInfo[0]) if err != nil { return err } } else if len(scopeInfo) == 2 && scopeInfo[0] != "" && scopeInfo[1] != "" { _, err := reference.ParseNamed(scopeInfo[0]) if err != nil { return err } _, err = ddigest.Parse("sha256:" + scopeInfo[1]) if err != nil { return err } } else { return ErrInvalidReference } return nil }
func TestNewReference(t *testing.T) { // An ID reference. id, err := digest.Parse(sha256digest) require.NoError(t, err) ref, err := NewReference(id, nil) require.NoError(t, err) daemonRef, ok := ref.(daemonReference) require.True(t, ok) assert.Equal(t, id, daemonRef.id) assert.Nil(t, daemonRef.ref) // Named references for _, c := range validNamedReferenceTestCases { parsed, err := reference.ParseNamed(c.input) require.NoError(t, err) ref, err := NewReference("", parsed) require.NoError(t, err, c.input) daemonRef, ok := ref.(daemonReference) require.True(t, ok, c.input) assert.Equal(t, "", daemonRef.id.String()) require.NotNil(t, daemonRef.ref) assert.Equal(t, c.dockerRef, daemonRef.ref.String(), c.input) } // Both an ID and a named reference provided parsed, err := reference.ParseNamed("busybox:latest") require.NoError(t, err) _, err = NewReference(id, parsed) assert.Error(t, err) // A reference with neither a tag nor digest parsed, err = reference.ParseNamed("busybox") require.NoError(t, err) _, err = NewReference("", parsed) assert.Error(t, err) // A github.com/distribution/reference value can have a tag and a digest at the same time! parsed, err = reference.ParseNamed("busybox@" + sha256digest) require.NoError(t, err) refDigested, ok := parsed.(reference.Canonical) require.True(t, ok) tagDigestRef := refWithTagAndDigest{refDigested} _, err = NewReference("", tagDigestRef) assert.Error(t, err) }
// newPRMExactRepository is NewPRMExactRepository, except it resturns the private type. func newPRMExactRepository(dockerRepository string) (*prmExactRepository, error) { if _, err := reference.ParseNamed(dockerRepository); err != nil { return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerRepository %s: %s", dockerRepository, err.Error())) } return &prmExactRepository{ prmCommon: prmCommon{Type: prmTypeExactRepository}, DockerRepository: dockerRepository, }, nil }
func testImageAndSig(t *testing.T, prm PolicyReferenceMatch, imageRef, sigRef string, result bool) { // This assumes that all ways to obtain a reference.Named perform equivalent validation, // and therefore values refused by reference.ParseNamed can not happen in practice. parsedImageRef, err := reference.ParseNamed(imageRef) if err != nil { return } res := prm.matchesDockerReference(refImageMock{parsedImageRef}, sigRef) assert.Equal(t, result, res, fmt.Sprintf("%s vs. %s", imageRef, sigRef)) }
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference. func ParseReference(refString string) (types.ImageReference, error) { if !strings.HasPrefix(refString, "//") { return nil, errors.Errorf("docker: image reference %s does not start with //", refString) } ref, err := reference.ParseNamed(strings.TrimPrefix(refString, "//")) if err != nil { return nil, err } ref = reference.WithDefaultTag(ref) return NewReference(ref) }
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OpenShift ImageReference. func ParseReference(ref string) (types.ImageReference, error) { r, err := reference.ParseNamed(ref) if err != nil { return nil, errors.Wrapf(err, "failed to parse image reference %q", ref) } tagged, ok := r.(reference.NamedTagged) if !ok { return nil, errors.Errorf("invalid image reference %s, %#v", ref, r) } return NewReference(tagged) }
// newPRMExactReference is NewPRMExactReference, except it resturns the private type. func newPRMExactReference(dockerReference string) (*prmExactReference, error) { ref, err := reference.ParseNamed(dockerReference) if err != nil { return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerReference %s: %s", dockerReference, err.Error())) } if reference.IsNameOnly(ref) { return nil, InvalidPolicyFormatError(fmt.Sprintf("dockerReference %s contains neither a tag nor digest", dockerReference)) } return &prmExactReference{ prmCommon: prmCommon{Type: prmTypeExactReference}, DockerReference: dockerReference, }, nil }
func newOCI1ImageSource(t *testing.T, dockerRef string) *oci1ImageSource { realConfigJSON, err := ioutil.ReadFile("fixtures/oci1-config.json") require.NoError(t, err) ref, err := reference.ParseNamed(dockerRef) require.NoError(t, err) return &oci1ImageSource{ configBlobImageSource: configBlobImageSource{ f: func(digest digest.Digest) (io.ReadCloser, int64, error) { return ioutil.NopCloser(bytes.NewReader(realConfigJSON)), int64(len(realConfigJSON)), nil }, }, ref: ref, } }
func TestTransportParseStoreReference(t *testing.T) { for _, c := range []struct{ input, expectedRef, expectedID string }{ {"", "", ""}, // Empty input // Handling of the store prefix // FIXME? Should we be silently discarding input like this? {"[unterminated", "", ""}, // Unterminated store specifier {"[garbage]busybox", "docker.io/library/busybox:latest", ""}, // Store specifier is overridden by the store we pass to ParseStoreReference {"UPPERCASEISINVALID", "", ""}, // Invalid single-component name {"sha256:" + sha256digestHex, "docker.io/library/sha256:" + sha256digestHex, ""}, // Valid single-component name; the hex part is not an ID unless it has a "@" prefix {sha256digestHex, "", ""}, // Invalid single-component ID; not an ID without a "@" prefix, so it's parsed as a name, but names aren't allowed to look like IDs {"@" + sha256digestHex, "", sha256digestHex}, // Valid single-component ID {"sha256:ab", "docker.io/library/sha256:ab", ""}, // Valid single-component name, explicit tag {"busybox", "docker.io/library/busybox:latest", ""}, // Valid single-component name, implicit tag {"busybox:notlatest", "docker.io/library/busybox:notlatest", ""}, // Valid single-component name, explicit tag {"docker.io/library/busybox:notlatest", "docker.io/library/busybox:notlatest", ""}, // Valid single-component name, everything explicit {"UPPERCASEISINVALID@" + sha256digestHex, "", ""}, // Invalid name in name@ID {"busybox@ab", "", ""}, // Invalid ID in name@ID {"busybox@", "", ""}, // Empty ID in name@ID {"busybox@sha256:" + sha256digestHex, "", ""}, // This (a digested docker/docker reference format) is also invalid, since it's an invalid ID in name@ID {"@" + sha256digestHex, "", sha256digestHex}, // Valid two-component name, with ID only {"busybox@" + sha256digestHex, "docker.io/library/busybox:latest", sha256digestHex}, // Valid two-component name, implicit tag {"busybox:notlatest@" + sha256digestHex, "docker.io/library/busybox:notlatest", sha256digestHex}, // Valid two-component name, explicit tag {"docker.io/library/busybox:notlatest@" + sha256digestHex, "docker.io/library/busybox:notlatest", sha256digestHex}, // Valid two-component name, everything explicit } { storageRef, err := Transport.ParseStoreReference(Transport.(*storageTransport).store, c.input) if c.expectedRef == "" && c.expectedID == "" { assert.Error(t, err, c.input) } else { require.NoError(t, err, c.input) assert.Equal(t, *(Transport.(*storageTransport)), storageRef.transport, c.input) assert.Equal(t, c.expectedRef, storageRef.reference, c.input) assert.Equal(t, c.expectedID, storageRef.id, c.input) if c.expectedRef == "" { assert.Nil(t, storageRef.name, c.input) } else { dockerRef, err := reference.ParseNamed(c.expectedRef) require.NoError(t, err) require.NotNil(t, storageRef.name, c.input) assert.Equal(t, dockerRef.String(), storageRef.name.String()) } } } }
// dirImageMock returns a types.UnparsedImage for a directory, claiming a specified dockerReference. // The caller must call .Close() on the returned UnparsedImage. func dirImageMock(t *testing.T, dir, dockerReference string) types.UnparsedImage { ref, err := reference.ParseNamed(dockerReference) require.NoError(t, err) return dirImageMockWithRef(t, dir, refImageReferenceMock{ref}) }
func TestPolicyContextRequirementsForImageRef(t *testing.T) { ktGPG := SBKeyTypeGPGKeys prm := NewPRMMatchRepoDigestOrExact() policy := &Policy{ Default: PolicyRequirements{NewPRReject()}, Transports: map[string]PolicyTransportScopes{}, } // Just put _something_ into the PolicyTransportScopes map for the keys we care about, and make it pairwise // distinct so that we can compare the values and show them when debugging the tests. for _, t := range []struct{ transport, scope string }{ {"docker", ""}, {"docker", "unmatched"}, {"docker", "deep.com"}, {"docker", "deep.com/n1"}, {"docker", "deep.com/n1/n2"}, {"docker", "deep.com/n1/n2/n3"}, {"docker", "deep.com/n1/n2/n3/repo"}, {"docker", "deep.com/n1/n2/n3/repo:tag2"}, {"atomic", "unmatched"}, } { if _, ok := policy.Transports[t.transport]; !ok { policy.Transports[t.transport] = PolicyTransportScopes{} } policy.Transports[t.transport][t.scope] = PolicyRequirements{xNewPRSignedByKeyData(ktGPG, []byte(t.transport+t.scope), prm)} } pc, err := NewPolicyContext(policy) require.NoError(t, err) for _, c := range []struct{ inputTransport, input, matchedTransport, matched string }{ // Full match {"docker", "deep.com/n1/n2/n3/repo:tag2", "docker", "deep.com/n1/n2/n3/repo:tag2"}, // Namespace matches {"docker", "deep.com/n1/n2/n3/repo:nottag2", "docker", "deep.com/n1/n2/n3/repo"}, {"docker", "deep.com/n1/n2/n3/notrepo:tag2", "docker", "deep.com/n1/n2/n3"}, {"docker", "deep.com/n1/n2/notn3/repo:tag2", "docker", "deep.com/n1/n2"}, {"docker", "deep.com/n1/notn2/n3/repo:tag2", "docker", "deep.com/n1"}, // Host name match {"docker", "deep.com/notn1/n2/n3/repo:tag2", "docker", "deep.com"}, // Default {"docker", "this.doesnt/match:anything", "docker", ""}, // No match within a matched transport which doesn't have a "" scope {"atomic", "this.doesnt/match:anything", "", ""}, // No configuration available for this transport at all {"dir", "what/ever", "", ""}, // "what/ever" is not a valid scope for the real "dir" transport, but we only need it to be a valid reference.Named. } { var expected PolicyRequirements if c.matchedTransport != "" { e, ok := policy.Transports[c.matchedTransport][c.matched] require.True(t, ok, fmt.Sprintf("case %s:%s: expected reqs not found", c.inputTransport, c.input)) expected = e } else { expected = policy.Default } ref, err := reference.ParseNamed(c.input) require.NoError(t, err) reqs := pc.requirementsForImageRef(pcImageReferenceMock{c.inputTransport, ref}) comment := fmt.Sprintf("case %s:%s: %#v", c.inputTransport, c.input, reqs[0]) // Do not use assert.Equal, which would do a deep contents comparison; we want to compare // the pointers. Also, == does not work on slices; so test that the slices start at the // same element and have the same length. assert.True(t, &(reqs[0]) == &(expected[0]), comment) assert.True(t, len(reqs) == len(expected), comment) } }