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

refactor: auth package and add full unit test coverage (#5749)

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2022-12-22 06:24:37 +01:00 committed by GitHub
parent 3975323362
commit 3cce75ae0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 16 deletions

View file

@ -5,13 +5,17 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/kyverno/kyverno/pkg/clients/dclient"
authorizationv1 "k8s.io/api/authorization/v1" authorizationv1 "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes" authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
) )
// Discovery provides interface to mange Kind and GVR mapping
type Discovery interface {
GetGVRFromKind(kind string) (schema.GroupVersionResource, error)
}
// CanIOptions provides utility to check if user has authorization for the given operation // CanIOptions provides utility to check if user has authorization for the given operation
type CanIOptions interface { type CanIOptions interface {
// RunAccessCheck checks if the caller can perform the operation // RunAccessCheck checks if the caller can perform the operation
@ -28,19 +32,19 @@ type canIOptions struct {
verb string verb string
kind string kind string
subresource string subresource string
discovery dclient.IDiscovery discovery Discovery
client kubernetes.Interface ssarClient authorizationv1client.SelfSubjectAccessReviewInterface
} }
// NewCanI returns a new instance of operation access controller evaluator // NewCanI returns a new instance of operation access controller evaluator
func NewCanI(client dclient.Interface, kind, namespace, verb, subresource string) CanIOptions { func NewCanI(discovery Discovery, ssarClient authorizationv1client.SelfSubjectAccessReviewInterface, kind, namespace, verb, subresource string) CanIOptions {
return &canIOptions{ return &canIOptions{
namespace: namespace, namespace: namespace,
verb: verb, verb: verb,
kind: kind, kind: kind,
subresource: subresource, subresource: subresource,
discovery: client.Discovery(), discovery: discovery,
client: client.GetKubeClient(), ssarClient: ssarClient,
} }
} }
@ -82,7 +86,7 @@ func (o *canIOptions) RunAccessCheck(ctx context.Context) (bool, error) {
logger := logger.WithValues("kind", sar.Kind, "namespace", sar.Namespace, "name", sar.Name) logger := logger.WithValues("kind", sar.Kind, "namespace", sar.Namespace, "name", sar.Name)
// Create the Resource // Create the Resource
resp, err := o.client.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, sar, metav1.CreateOptions{}) resp, err := o.ssarClient.Create(ctx, sar, metav1.CreateOptions{})
if err != nil { if err != nil {
logger.Error(err, "failed to create resource") logger.Error(err, "failed to create resource")
return false, err return false, err

View file

@ -2,10 +2,15 @@ package auth
import ( import (
"context" "context"
"errors"
"testing" "testing"
"github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
v1 "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
) )
func TestNewCanI(t *testing.T) { func TestNewCanI(t *testing.T) {
@ -29,12 +34,100 @@ func TestNewCanI(t *testing.T) {
}} }}
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, tt.args.kind, tt.args.namespace, tt.args.verb, "") got := NewCanI(tt.args.client.Discovery(), tt.args.client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews(), tt.args.kind, tt.args.namespace, tt.args.verb, "")
assert.NotNil(t, got) assert.NotNil(t, got)
}) })
} }
} }
type discovery struct{}
func (d *discovery) GetGVRFromKind(kind string) (schema.GroupVersionResource, error) {
return schema.GroupVersionResource{}, errors.New("dummy")
}
func TestCanIOptions_DiscoveryError(t *testing.T) {
type fields struct {
namespace string
verb string
kind string
discovery Discovery
}
tests := []struct {
name string
fields fields
want bool
wantErr bool
}{{
name: "deployments",
fields: fields{
discovery: &discovery{},
kind: "Deployment",
namespace: "default",
verb: "test",
},
want: false,
wantErr: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := NewCanI(tt.fields.discovery, nil, tt.fields.kind, tt.fields.namespace, tt.fields.verb, "")
got, err := o.RunAccessCheck(context.TODO())
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
}
})
}
}
type ssar struct{}
func (d *ssar) Create(_ context.Context, _ *v1.SelfSubjectAccessReview, _ metav1.CreateOptions) (*v1.SelfSubjectAccessReview, error) {
return nil, errors.New("dummy")
}
func TestCanIOptions_SsarError(t *testing.T) {
type fields struct {
namespace string
verb string
kind string
discovery Discovery
ssarClient authorizationv1client.SelfSubjectAccessReviewInterface
}
tests := []struct {
name string
fields fields
want bool
wantErr bool
}{{
name: "deployments",
fields: fields{
discovery: dclient.NewEmptyFakeClient().Discovery(),
ssarClient: &ssar{},
kind: "Deployment",
namespace: "default",
verb: "test",
},
want: false,
wantErr: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := NewCanI(tt.fields.discovery, tt.fields.ssarClient, tt.fields.kind, tt.fields.namespace, tt.fields.verb, "")
got, err := o.RunAccessCheck(context.TODO())
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
}
})
}
}
func TestCanIOptions_RunAccessCheck(t *testing.T) { func TestCanIOptions_RunAccessCheck(t *testing.T) {
type fields struct { type fields struct {
namespace string namespace string
@ -80,7 +173,7 @@ func TestCanIOptions_RunAccessCheck(t *testing.T) {
}} }}
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, tt.fields.kind, tt.fields.namespace, tt.fields.verb, "") o := NewCanI(tt.fields.client.Discovery(), tt.fields.client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews(), tt.fields.kind, tt.fields.namespace, tt.fields.verb, "")
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

@ -399,7 +399,7 @@ func checkManifestAnnotations(mnfstAnnotations map[string]string, annotations ma
} }
func checkDryRunPermission(dclient dclient.Interface, kind, namespace string) (bool, error) { func checkDryRunPermission(dclient dclient.Interface, kind, namespace string) (bool, error) {
canI := auth.NewCanI(dclient, kind, namespace, "create", "") canI := auth.NewCanI(dclient.Discovery(), dclient.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews(), kind, namespace, "create", "")
ok, err := canI.RunAccessCheck(context.TODO()) ok, err := canI.RunAccessCheck(context.TODO())
if err != nil { if err != nil {
return false, err return false, err

View file

@ -37,7 +37,7 @@ func NewAuth(client dclient.Interface, 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, kind, namespace string) (bool, error) { func (a *Auth) CanICreate(ctx context.Context, kind, namespace string) (bool, error) {
canI := auth.NewCanI(a.client, kind, namespace, "create", "") canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews(), kind, namespace, "create", "")
ok, err := canI.RunAccessCheck(ctx) ok, err := canI.RunAccessCheck(ctx)
if err != nil { if err != nil {
return false, err return false, err
@ -47,7 +47,7 @@ func (a *Auth) CanICreate(ctx context.Context, kind, namespace string) (bool, er
// CanIUpdate returns 'true' if self can 'update' resource // CanIUpdate returns 'true' if self can 'update' resource
func (a *Auth) CanIUpdate(ctx context.Context, kind, namespace string) (bool, error) { func (a *Auth) CanIUpdate(ctx context.Context, kind, namespace string) (bool, error) {
canI := auth.NewCanI(a.client, kind, namespace, "update", "") canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews(), kind, namespace, "update", "")
ok, err := canI.RunAccessCheck(ctx) ok, err := canI.RunAccessCheck(ctx)
if err != nil { if err != nil {
return false, err return false, err
@ -57,7 +57,7 @@ func (a *Auth) CanIUpdate(ctx context.Context, kind, namespace string) (bool, er
// CanIDelete returns 'true' if self can 'delete' resource // CanIDelete returns 'true' if self can 'delete' resource
func (a *Auth) CanIDelete(ctx context.Context, kind, namespace string) (bool, error) { func (a *Auth) CanIDelete(ctx context.Context, kind, namespace string) (bool, error) {
canI := auth.NewCanI(a.client, kind, namespace, "delete", "") canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews(), kind, namespace, "delete", "")
ok, err := canI.RunAccessCheck(ctx) ok, err := canI.RunAccessCheck(ctx)
if err != nil { if err != nil {
return false, err return false, err
@ -67,7 +67,7 @@ func (a *Auth) CanIDelete(ctx context.Context, kind, namespace string) (bool, er
// CanIGet returns 'true' if self can 'get' resource // CanIGet returns 'true' if self can 'get' resource
func (a *Auth) CanIGet(ctx context.Context, kind, namespace string) (bool, error) { func (a *Auth) CanIGet(ctx context.Context, kind, namespace string) (bool, error) {
canI := auth.NewCanI(a.client, kind, namespace, "get", "") canI := auth.NewCanI(a.client.Discovery(), a.client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews(), kind, namespace, "get", "")
ok, err := canI.RunAccessCheck(ctx) ok, err := canI.RunAccessCheck(ctx)
if err != nil { if err != nil {
return false, err return false, err

View file

@ -63,7 +63,7 @@ func validateAuth(ctx context.Context, client dclient.Interface, policy kyvernov
spec := policy.GetSpec() spec := policy.GetSpec()
kinds := sets.New(spec.MatchResources.GetKinds()...) kinds := sets.New(spec.MatchResources.GetKinds()...)
for kind := range kinds { for kind := range kinds {
checker := auth.NewCanI(client, kind, namespace, "delete", "") checker := auth.NewCanI(client.Discovery(), client.GetKubeClient().AuthorizationV1().SelfSubjectAccessReviews(), kind, namespace, "delete", "")
allowed, err := checker.RunAccessCheck(ctx) allowed, err := checker.RunAccessCheck(ctx)
if err != nil { if err != nil {
return err return err