1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

feat: add auth checker interface (#7323)

* feat: add auth checker interface

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

* tests

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é 2023-05-30 12:01:44 +02:00 committed by GitHub
parent 5d4e250dfe
commit 7a838de4f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 139 additions and 29 deletions

View file

@ -4,9 +4,8 @@ import (
"context"
"fmt"
"github.com/kyverno/kyverno/pkg/auth/checker"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
authorizationv1 "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"
)
@ -34,7 +33,7 @@ type canIOptions struct {
subresource string
user string
discovery Discovery
sarClient authorizationv1client.SubjectAccessReviewInterface
checker checker.AuthChecker
}
// NewCanI returns a new instance of operation access controller evaluator
@ -46,7 +45,7 @@ func NewCanI(discovery Discovery, sarClient authorizationv1client.SubjectAccessR
subresource: subresource,
user: user,
discovery: discovery,
sarClient: sarClient,
checker: checker.NewSubjectChecker(sarClient, user, nil),
}
}
@ -68,37 +67,18 @@ func (o *canIOptions) RunAccessCheck(ctx context.Context) (bool, error) {
if err != nil {
return false, fmt.Errorf("failed to get GVR for kind %s", o.kind)
}
if gvr.Empty() {
// cannot find GVR
return false, fmt.Errorf("failed to get the Group Version Resource for kind %s", o.kind)
}
sar := &authorizationv1.SubjectAccessReview{
Spec: authorizationv1.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Namespace: o.namespace,
Verb: o.verb,
Group: gvr.Group,
Resource: gvr.Resource,
Subresource: o.subresource,
},
User: o.user,
},
}
logger := logger.WithValues("kind", sar.Kind, "namespace", sar.Namespace, "name", sar.Name, "gvr", gvr.String())
resp, err := o.sarClient.Create(ctx, sar, metav1.CreateOptions{})
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)
if err != nil {
logger.Error(err, "failed to create resource")
logger.Error(err, "failed to check permissions")
return false, err
}
if !resp.Status.Allowed {
reason := resp.Status.Reason
evaluationError := resp.Status.EvaluationError
logger.Info("disallowed operation", "reason", reason, "evaluationError", evaluationError)
if !result.Allowed {
logger.Info("disallowed operation", "reason", result.Reason, "evaluationError", result.EvaluationError)
}
return resp.Status.Allowed, nil
return result.Allowed, nil
}

34
pkg/auth/checker/auth.go Normal file
View file

@ -0,0 +1,34 @@
package checker
import (
"context"
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
)
// AuthResult contains authorization check result
type AuthResult struct {
Allowed bool
Reason string
EvaluationError string
}
// AuthChecker provides utility to check authorization
type AuthChecker interface {
// Check checks if the caller can perform an operation
Check(ctx context.Context, group, version, resource, subresource, namespace, verb string) (*AuthResult, error)
}
func NewSelfChecker(client authorizationv1client.SelfSubjectAccessReviewInterface) AuthChecker {
return self{
client: client,
}
}
func NewSubjectChecker(client authorizationv1client.SubjectAccessReviewInterface, user string, groups []string) AuthChecker {
return subject{
client: client,
user: user,
groups: groups,
}
}

View file

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

37
pkg/auth/checker/self.go Normal file
View file

@ -0,0 +1,37 @@
package checker
import (
"context"
authorizationv1 "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
)
type self struct {
client authorizationv1client.SelfSubjectAccessReviewInterface
}
func (c self) Check(ctx context.Context, group, version, resource, subresource, namespace, verb string) (*AuthResult, error) {
review := &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Group: group,
Version: version,
Resource: resource,
Subresource: subresource,
Namespace: namespace,
Verb: verb,
},
},
}
resp, err := c.client.Create(ctx, review, metav1.CreateOptions{})
if err != nil {
return nil, err
}
return &AuthResult{
Allowed: resp.Status.Allowed,
Reason: resp.Status.Reason,
EvaluationError: resp.Status.EvaluationError,
}, nil
}

View file

@ -0,0 +1,41 @@
package checker
import (
"context"
authorizationv1 "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
)
type subject struct {
client authorizationv1client.SubjectAccessReviewInterface
user string
groups []string
}
func (c subject) Check(ctx context.Context, group, version, resource, subresource, namespace, verb string) (*AuthResult, error) {
review := &authorizationv1.SubjectAccessReview{
Spec: authorizationv1.SubjectAccessReviewSpec{
ResourceAttributes: &authorizationv1.ResourceAttributes{
Group: group,
Version: version,
Resource: resource,
Subresource: subresource,
Namespace: namespace,
Verb: verb,
},
User: c.user,
Groups: c.groups,
},
}
resp, err := c.client.Create(ctx, review, metav1.CreateOptions{})
if err != nil {
return nil, err
}
return &AuthResult{
Allowed: resp.Status.Allowed,
Reason: resp.Status.Reason,
EvaluationError: resp.Status.EvaluationError,
}, nil
}