1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

fix: add the resource name to the SubjectAccessReview (#10221)

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
Mariam Fahmy 2024-08-07 15:46:44 +03:00 committed by GitHub
parent 4342c36c09
commit 4d1f040e49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 116 additions and 32 deletions

View file

@ -18,6 +18,14 @@ type MatchResources struct {
All kyvernov1.ResourceFilters `json:"all,omitempty" yaml:"all,omitempty"` All kyvernov1.ResourceFilters `json:"all,omitempty" yaml:"all,omitempty"`
} }
// GetResourceFilters returns all resource filters
func (m *MatchResources) GetResourceFilters() kyvernov1.ResourceFilters {
var filters kyvernov1.ResourceFilters
filters = append(filters, m.All...)
filters = append(filters, m.Any...)
return filters
}
// GetKinds returns all kinds // GetKinds returns all kinds
func (m *MatchResources) GetKinds() []string { func (m *MatchResources) GetKinds() []string {
var kinds []string var kinds []string

View file

@ -32,13 +32,15 @@ type canIOptions struct {
gvk string gvk string
subresource string subresource string
user string user string
name string
discovery Discovery discovery Discovery
checker checker.AuthChecker checker checker.AuthChecker
} }
// NewCanI returns a new instance of operation access controller evaluator // NewCanI returns a new instance of operation access controller evaluator
func NewCanI(discovery Discovery, sarClient authorizationv1client.SubjectAccessReviewInterface, gvk, namespace, verb, subresource string, user string) CanIOptions { func NewCanI(discovery Discovery, sarClient authorizationv1client.SubjectAccessReviewInterface, gvk, namespace, name, verb, subresource string, user string) CanIOptions {
return &canIOptions{ return &canIOptions{
name: name,
namespace: namespace, namespace: namespace,
verb: verb, verb: verb,
gvk: gvk, gvk: gvk,
@ -72,7 +74,7 @@ func (o *canIOptions) RunAccessCheck(ctx context.Context) (bool, string, error)
return false, "", fmt.Errorf("failed to get the Group Version Resource for kind %s", o.gvk) return false, "", fmt.Errorf("failed to get the Group Version Resource for kind %s", o.gvk)
} }
logger := logger.WithValues("kind", kind, "namespace", o.namespace, "gvr", gvr.String(), "verb", o.verb) logger := logger.WithValues("kind", kind, "namespace", o.namespace, "gvr", gvr.String(), "verb", o.verb)
result, err := o.checker.Check(ctx, gvr.Group, gvr.Version, gvr.Resource, o.subresource, o.namespace, o.verb) result, err := o.checker.Check(ctx, gvr.Group, gvr.Version, gvr.Resource, o.subresource, o.namespace, o.name, o.verb)
if err != nil { if err != nil {
logger.Error(err, "failed to check permissions") logger.Error(err, "failed to check permissions")
return false, "", err return false, "", err

View file

@ -17,6 +17,7 @@ func TestNewCanI(t *testing.T) {
type args struct { type args struct {
client dclient.Interface client dclient.Interface
kind string kind string
name string
namespace string namespace string
verb string verb string
} }
@ -27,14 +28,24 @@ func TestNewCanI(t *testing.T) {
name: "deployments", name: "deployments",
args: args{ args: args{
client: dclient.NewEmptyFakeClient(), client: dclient.NewEmptyFakeClient(),
name: "",
kind: "Deployment", kind: "Deployment",
namespace: "default", namespace: "default",
verb: "test", verb: "test",
}, },
}, {
name: "secrets",
args: args{
client: dclient.NewEmptyFakeClient(),
name: "test-secret",
kind: "Secret",
namespace: "default",
verb: "test",
},
}} }}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got := NewCanI(tt.args.client.Discovery(), tt.args.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), tt.args.kind, tt.args.namespace, tt.args.verb, "", "admin") got := NewCanI(tt.args.client.Discovery(), tt.args.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), tt.args.kind, tt.args.namespace, tt.args.verb, tt.args.name, "", "admin")
assert.NotNil(t, got) assert.NotNil(t, got)
}) })
} }
@ -48,6 +59,7 @@ func (d *discovery) GetGVRFromGVK(schema.GroupVersionKind) (schema.GroupVersionR
func TestCanIOptions_DiscoveryError(t *testing.T) { func TestCanIOptions_DiscoveryError(t *testing.T) {
type fields struct { type fields struct {
name string
namespace string namespace string
verb string verb string
kind string kind string
@ -62,16 +74,28 @@ func TestCanIOptions_DiscoveryError(t *testing.T) {
name: "deployments", name: "deployments",
fields: fields{ fields: fields{
discovery: &discovery{}, discovery: &discovery{},
name: "",
kind: "Deployment", kind: "Deployment",
namespace: "default", namespace: "default",
verb: "test", verb: "test",
}, },
want: false, want: false,
wantErr: true, wantErr: true,
}, {
name: "secrets",
fields: fields{
discovery: &discovery{},
name: "test-secret",
kind: "Secret",
namespace: "default",
verb: "test",
},
want: false,
wantErr: true,
}} }}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := NewCanI(tt.fields.discovery, nil, tt.fields.kind, tt.fields.namespace, tt.fields.verb, "", "admin") o := NewCanI(tt.fields.discovery, nil, tt.fields.kind, tt.fields.namespace, tt.fields.name, tt.fields.verb, "", "admin")
got, _, err := o.RunAccessCheck(context.TODO()) got, _, err := o.RunAccessCheck(context.TODO())
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
@ -91,6 +115,7 @@ func (d *sar) Create(_ context.Context, _ *v1.SubjectAccessReview, _ metav1.Crea
func TestCanIOptions_SsarError(t *testing.T) { func TestCanIOptions_SsarError(t *testing.T) {
type fields struct { type fields struct {
name string
namespace string namespace string
verb string verb string
kind string kind string
@ -107,16 +132,29 @@ func TestCanIOptions_SsarError(t *testing.T) {
fields: fields{ fields: fields{
discovery: dclient.NewEmptyFakeClient().Discovery(), discovery: dclient.NewEmptyFakeClient().Discovery(),
sarClient: &sar{}, sarClient: &sar{},
name: "",
kind: "Deployment", kind: "Deployment",
namespace: "default", namespace: "default",
verb: "test", verb: "test",
}, },
want: false, want: false,
wantErr: true, wantErr: true,
}, {
name: "secrets",
fields: fields{
discovery: dclient.NewEmptyFakeClient().Discovery(),
sarClient: &sar{},
name: "test-secret",
kind: "Secret",
namespace: "default",
verb: "test",
},
want: false,
wantErr: true,
}} }}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := NewCanI(tt.fields.discovery, tt.fields.sarClient, tt.fields.kind, tt.fields.namespace, tt.fields.verb, "", "admin") o := NewCanI(tt.fields.discovery, tt.fields.sarClient, tt.fields.kind, tt.fields.namespace, tt.fields.name, tt.fields.verb, "", "admin")
got, _, err := o.RunAccessCheck(context.TODO()) got, _, err := o.RunAccessCheck(context.TODO())
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
@ -130,6 +168,7 @@ func TestCanIOptions_SsarError(t *testing.T) {
func TestCanIOptions_RunAccessCheck(t *testing.T) { func TestCanIOptions_RunAccessCheck(t *testing.T) {
type fields struct { type fields struct {
name string
namespace string namespace string
verb string verb string
kind string kind string
@ -144,6 +183,7 @@ func TestCanIOptions_RunAccessCheck(t *testing.T) {
name: "deployments", name: "deployments",
fields: fields{ fields: fields{
client: dclient.NewEmptyFakeClient(), client: dclient.NewEmptyFakeClient(),
name: "",
kind: "Deployment", kind: "Deployment",
namespace: "default", namespace: "default",
verb: "test", verb: "test",
@ -154,6 +194,7 @@ func TestCanIOptions_RunAccessCheck(t *testing.T) {
name: "unknown", name: "unknown",
fields: fields{ fields: fields{
client: dclient.NewEmptyFakeClient(), client: dclient.NewEmptyFakeClient(),
name: "",
kind: "Unknown", kind: "Unknown",
namespace: "default", namespace: "default",
verb: "test", verb: "test",
@ -164,16 +205,28 @@ func TestCanIOptions_RunAccessCheck(t *testing.T) {
name: "v2 pods", name: "v2 pods",
fields: fields{ fields: fields{
client: dclient.NewEmptyFakeClient(), client: dclient.NewEmptyFakeClient(),
name: "",
kind: "v2/Pod", kind: "v2/Pod",
namespace: "default", namespace: "default",
verb: "test", verb: "test",
}, },
want: false, want: false,
wantErr: true, wantErr: true,
}, {
name: "secrets",
fields: fields{
client: dclient.NewEmptyFakeClient(),
name: "test-secret",
kind: "Secret",
namespace: "default",
verb: "test",
},
want: false,
wantErr: false,
}} }}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := NewCanI(tt.fields.client.Discovery(), tt.fields.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), tt.fields.kind, tt.fields.namespace, tt.fields.verb, "", "admin") o := NewCanI(tt.fields.client.Discovery(), tt.fields.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), tt.fields.kind, tt.fields.namespace, tt.fields.name, tt.fields.verb, "", "admin")
got, _, err := o.RunAccessCheck(context.TODO()) got, _, err := o.RunAccessCheck(context.TODO())
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)

View file

@ -16,7 +16,7 @@ type AuthResult struct {
// AuthChecker provides utility to check authorization // AuthChecker provides utility to check authorization
type AuthChecker interface { type AuthChecker interface {
// Check checks if the caller can perform an operation // Check checks if the caller can perform an operation
Check(ctx context.Context, group, version, resource, subresource, namespace, verb string) (*AuthResult, error) Check(ctx context.Context, group, version, resource, subresource, namespace, name, verb string) (*AuthResult, error)
} }
func NewSelfChecker(client authorizationv1client.SelfSubjectAccessReviewInterface) AuthChecker { func NewSelfChecker(client authorizationv1client.SelfSubjectAccessReviewInterface) AuthChecker {

View file

@ -6,7 +6,7 @@ import (
func Check(ctx context.Context, checker AuthChecker, group, version, resource, subresource, namespace string, verbs ...string) (bool, error) { func Check(ctx context.Context, checker AuthChecker, group, version, resource, subresource, namespace string, verbs ...string) (bool, error) {
for _, verb := range verbs { for _, verb := range verbs {
result, err := checker.Check(ctx, group, version, resource, subresource, namespace, verb) result, err := checker.Check(ctx, group, version, resource, subresource, namespace, "", verb)
if err != nil { if err != nil {
return false, err return false, err
} }

View file

@ -12,7 +12,7 @@ type self struct {
client authorizationv1client.SelfSubjectAccessReviewInterface client authorizationv1client.SelfSubjectAccessReviewInterface
} }
func (c self) Check(ctx context.Context, group, version, resource, subresource, namespace, verb string) (*AuthResult, error) { func (c self) Check(ctx context.Context, group, version, resource, subresource, namespace, name, verb string) (*AuthResult, error) {
review := &authorizationv1.SelfSubjectAccessReview{ review := &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{ Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{ ResourceAttributes: &authorizationv1.ResourceAttributes{
@ -22,6 +22,7 @@ func (c self) Check(ctx context.Context, group, version, resource, subresource,
Subresource: subresource, Subresource: subresource,
Namespace: namespace, Namespace: namespace,
Verb: verb, Verb: verb,
Name: name,
}, },
}, },
} }

View file

@ -14,7 +14,7 @@ type subject struct {
groups []string groups []string
} }
func (c subject) Check(ctx context.Context, group, version, resource, subresource, namespace, verb string) (*AuthResult, error) { func (c subject) Check(ctx context.Context, group, version, resource, subresource, namespace, name, verb string) (*AuthResult, error) {
review := &authorizationv1.SubjectAccessReview{ review := &authorizationv1.SubjectAccessReview{
Spec: authorizationv1.SubjectAccessReviewSpec{ Spec: authorizationv1.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{ ResourceAttributes: &authorizationv1.ResourceAttributes{
@ -24,6 +24,7 @@ func (c subject) Check(ctx context.Context, group, version, resource, subresourc
Subresource: subresource, Subresource: subresource,
Namespace: namespace, Namespace: namespace,
Verb: verb, Verb: verb,
Name: name,
}, },
User: c.user, User: c.user,
Groups: c.groups, Groups: c.groups,

View file

@ -70,7 +70,7 @@ func (a *dclientAdapter) IsNamespaced(group, version, kind string) (bool, error)
} }
func (a *dclientAdapter) CanI(ctx context.Context, kind, namespace, verb, subresource, user string) (bool, string, error) { func (a *dclientAdapter) CanI(ctx context.Context, kind, namespace, verb, subresource, user string) (bool, string, error) {
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, verb, subresource, user) canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, "", verb, subresource, user)
ok, reason, err := canI.RunAccessCheck(ctx) ok, reason, err := canI.RunAccessCheck(ctx)
if err != nil { if err != nil {
return false, reason, err return false, reason, err

View file

@ -39,7 +39,7 @@ func NewAuth(client dclient.Interface, user string, log logr.Logger) *Auth {
// CanICreate returns 'true' if self can 'create' resource // CanICreate returns 'true' if self can 'create' resource
func (a *Auth) CanICreate(ctx context.Context, gvk, namespace, subresource string) (bool, error) { func (a *Auth) CanICreate(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "create", "", a.user) canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "", "create", "", a.user)
ok, _, err := canI.RunAccessCheck(ctx) ok, _, err := canI.RunAccessCheck(ctx)
if err != nil { if err != nil {
return false, err return false, err
@ -49,7 +49,7 @@ func (a *Auth) CanICreate(ctx context.Context, gvk, namespace, subresource strin
// CanIUpdate returns 'true' if self can 'update' resource // CanIUpdate returns 'true' if self can 'update' resource
func (a *Auth) CanIUpdate(ctx context.Context, gvk, namespace, subresource string) (bool, error) { func (a *Auth) CanIUpdate(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "update", "", a.user) canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "", "update", "", a.user)
ok, _, err := canI.RunAccessCheck(ctx) ok, _, err := canI.RunAccessCheck(ctx)
if err != nil { if err != nil {
return false, err return false, err
@ -59,7 +59,7 @@ func (a *Auth) CanIUpdate(ctx context.Context, gvk, namespace, subresource strin
// CanIDelete returns 'true' if self can 'delete' resource // CanIDelete returns 'true' if self can 'delete' resource
func (a *Auth) CanIDelete(ctx context.Context, gvk, namespace, subresource string) (bool, error) { func (a *Auth) CanIDelete(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "delete", "", a.user) canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "", "delete", "", a.user)
ok, _, err := canI.RunAccessCheck(ctx) ok, _, err := canI.RunAccessCheck(ctx)
if err != nil { if err != nil {
return false, err return false, err
@ -69,7 +69,7 @@ func (a *Auth) CanIDelete(ctx context.Context, gvk, namespace, subresource strin
// CanIGet returns 'true' if self can 'get' resource // CanIGet returns 'true' if self can 'get' resource
func (a *Auth) CanIGet(ctx context.Context, gvk, namespace, subresource string) (bool, error) { func (a *Auth) CanIGet(ctx context.Context, gvk, namespace, subresource string) (bool, error) {
canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "get", "", a.user) canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), gvk, namespace, "", "get", "", a.user)
ok, _, err := canI.RunAccessCheck(ctx) ok, _, err := canI.RunAccessCheck(ctx)
if err != nil { if err != nil {
return false, err return false, err

View file

@ -70,9 +70,29 @@ func validatePolicy(clusterResources sets.Set[string], policy kyvernov2.CleanupP
func validateAuth(ctx context.Context, client dclient.Interface, policy kyvernov2.CleanupPolicyInterface) error { func validateAuth(ctx context.Context, client dclient.Interface, policy kyvernov2.CleanupPolicyInterface) error {
namespace := policy.GetNamespace() namespace := policy.GetNamespace()
spec := policy.GetSpec() spec := policy.GetSpec()
kinds := sets.New(spec.MatchResources.GetKinds()...) resourceFilters := spec.MatchResources.GetResourceFilters()
for kind := range kinds { for _, res := range resourceFilters {
checker := auth.NewCanI(client.Discovery(), client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, "delete", "", config.KyvernoUserName(config.KyvernoServiceAccountName())) for _, kind := range res.Kinds {
if len(res.Names) == 0 {
err := canI(ctx, client, kind, namespace, "", "")
if err != nil {
return err
}
} else {
for _, name := range res.Names {
err := canI(ctx, client, kind, namespace, name, "")
if err != nil {
return err
}
}
}
}
}
return nil
}
func canI(ctx context.Context, client dclient.Interface, kind, namespace, name, subresource string) error {
checker := auth.NewCanI(client.Discovery(), client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, name, "delete", subresource, config.KyvernoUserName(config.KyvernoServiceAccountName()))
allowedDeletion, _, err := checker.RunAccessCheck(ctx) allowedDeletion, _, err := checker.RunAccessCheck(ctx)
if err != nil { if err != nil {
return err return err
@ -81,7 +101,7 @@ func validateAuth(ctx context.Context, client dclient.Interface, policy kyvernov
return fmt.Errorf("cleanup controller has no permission to delete kind %s", kind) return fmt.Errorf("cleanup controller has no permission to delete kind %s", kind)
} }
checker = auth.NewCanI(client.Discovery(), client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, "list", "", config.KyvernoUserName(config.KyvernoServiceAccountName())) checker = auth.NewCanI(client.Discovery(), client.GetKubeClient().AuthorizationV1().SubjectAccessReviews(), kind, namespace, name, "list", subresource, config.KyvernoUserName(config.KyvernoServiceAccountName()))
allowedList, _, err := checker.RunAccessCheck(ctx) allowedList, _, err := checker.RunAccessCheck(ctx)
if err != nil { if err != nil {
return err return err
@ -89,7 +109,6 @@ func validateAuth(ctx context.Context, client dclient.Interface, policy kyvernov
if !allowedList { if !allowedList {
return fmt.Errorf("cleanup controller has no permission to list kind %s", kind) return fmt.Errorf("cleanup controller has no permission to list kind %s", kind)
} }
}
return nil return nil
} }