func (v *WrappingValidator) ValidateUpdate(obj, old runtime.Object) fielderrors.ValidationErrorList { if v.validateUpdate == nil { // if there is no update validation, fail. return fielderrors.ValidationErrorList{fielderrors.NewFieldForbidden("obj", obj)} } return callValidateUpdate(reflect.ValueOf(obj), reflect.ValueOf(old), *v.validateUpdate) }
func (v *TagVerifier) Verify(old, stream *api.ImageStream, user user.Info) fielderrors.ValidationErrorList { var errors fielderrors.ValidationErrorList oldTags := map[string]api.TagReference{} if old != nil && old.Spec.Tags != nil { oldTags = old.Spec.Tags } for tag, tagRef := range stream.Spec.Tags { if tagRef.From == nil { continue } if len(tagRef.From.Namespace) == 0 { continue } if stream.Namespace == tagRef.From.Namespace { continue } if oldRef, ok := oldTags[tag]; ok && !tagRefChanged(oldRef, tagRef, stream.Namespace) { continue } streamName, _, err := parseFromReference(stream, tagRef.From) if err != nil { errors = append(errors, fielderrors.NewFieldInvalid(fmt.Sprintf("spec.tags[%s].from.name", tag), tagRef.From.Name, "must be of the form <tag>, <repo>:<tag>, <id>, or <repo>@<id>")) continue } subjectAccessReview := authorizationapi.SubjectAccessReview{ Verb: "get", Resource: "imagestreams", User: user.GetName(), Groups: util.NewStringSet(user.GetGroups()...), ResourceName: streamName, } ctx := kapi.WithNamespace(kapi.NewContext(), tagRef.From.Namespace) glog.V(1).Infof("Performing SubjectAccessReview for user=%s, groups=%v to %s/%s", user.GetName(), user.GetGroups(), tagRef.From.Namespace, streamName) resp, err := v.subjectAccessReviewClient.CreateSubjectAccessReview(ctx, &subjectAccessReview) if err != nil || resp == nil || (resp != nil && !resp.Allowed) { errors = append(errors, fielderrors.NewFieldForbidden(fmt.Sprintf("spec.tags[%s].from", tag), fmt.Sprintf("%s/%s", tagRef.From.Namespace, streamName))) continue } } return errors }
func TestTagVerifier(t *testing.T) { tests := map[string]struct { oldTags map[string]api.TagReference newTags map[string]api.TagReference sarError error sarAllowed bool expectSar bool expected fielderrors.ValidationErrorList }{ "old nil, no tags": {}, "old nil, all tags are new": { newTags: map[string]api.TagReference{ api.DefaultImageTag: { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "otherns", Name: "otherstream:latest", }, }, }, expectSar: true, sarAllowed: true, }, "nil from": { newTags: map[string]api.TagReference{ api.DefaultImageTag: { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: "registry/old/stream:latest", }, }, }, expectSar: false, }, "same namespace": { newTags: map[string]api.TagReference{ "other": { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "namespace", Name: "otherstream:latest", }, }, }, }, "ref unchanged": { oldTags: map[string]api.TagReference{ api.DefaultImageTag: { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "otherns", Name: "otherstream:latest", }, }, }, newTags: map[string]api.TagReference{ api.DefaultImageTag: { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "otherns", Name: "otherstream:latest", }, }, }, expectSar: false, }, "invalid from name": { newTags: map[string]api.TagReference{ api.DefaultImageTag: { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "otherns", Name: "a:b:c", }, }, }, expected: fielderrors.ValidationErrorList{ fielderrors.NewFieldInvalid("spec.tags[latest].from.name", "a:b:c", "must be of the form <tag>, <repo>:<tag>, <id>, or <repo>@<id>"), }, }, "sar error": { newTags: map[string]api.TagReference{ api.DefaultImageTag: { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "otherns", Name: "otherstream:latest", }, }, }, expectSar: true, sarError: errors.New("foo"), expected: fielderrors.ValidationErrorList{ fielderrors.NewFieldForbidden("spec.tags[latest].from", "otherns/otherstream"), }, }, "sar denied": { newTags: map[string]api.TagReference{ api.DefaultImageTag: { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "otherns", Name: "otherstream:latest", }, }, }, expectSar: true, sarAllowed: false, expected: fielderrors.ValidationErrorList{ fielderrors.NewFieldForbidden("spec.tags[latest].from", "otherns/otherstream"), }, }, "ref changed": { oldTags: map[string]api.TagReference{ api.DefaultImageTag: { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "otherns", Name: "otherstream:foo", }, }, }, newTags: map[string]api.TagReference{ api.DefaultImageTag: { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "otherns", Name: "otherstream:latest", }, }, }, expectSar: true, sarAllowed: true, }, } for name, test := range tests { sar := &fakeSubjectAccessReviewRegistry{ err: test.sarError, allow: test.sarAllowed, } old := &api.ImageStream{ Spec: api.ImageStreamSpec{ Tags: test.oldTags, }, } stream := &api.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "stream", }, Spec: api.ImageStreamSpec{ Tags: test.newTags, }, } tagVerifier := &TagVerifier{sar} errs := tagVerifier.Verify(old, stream, &fakeUser{}) sarCalled := sar.request != nil if e, a := test.expectSar, sarCalled; e != a { t.Errorf("%s: expected SAR request=%t, got %t", name, e, a) } if test.expectSar { if e, a := "otherns", sar.requestNamespace; e != a { t.Errorf("%s: sar namespace: expected %v, got %v", name, e, a) } expectedSar := &authorizationapi.SubjectAccessReview{ Verb: "get", Resource: "imagestreams", User: "******", Groups: util.NewStringSet("group1"), ResourceName: "otherstream", } if e, a := expectedSar, sar.request; !reflect.DeepEqual(e, a) { t.Errorf("%s: unexpected SAR request: %s", name, util.ObjectDiff(e, a)) } } if e, a := test.expected, errs; !reflect.DeepEqual(e, a) { t.Errorf("%s: unexpected validation errors: %s", name, util.ObjectDiff(e, a)) } } }