mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-29 02:45:06 +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:
parent
5d4e250dfe
commit
7a838de4f1
5 changed files with 139 additions and 29 deletions
pkg/auth
|
@ -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
34
pkg/auth/checker/auth.go
Normal 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,
|
||||
}
|
||||
}
|
18
pkg/auth/checker/helpers.go
Normal file
18
pkg/auth/checker/helpers.go
Normal 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
37
pkg/auth/checker/self.go
Normal 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
|
||||
}
|
41
pkg/auth/checker/subject.go
Normal file
41
pkg/auth/checker/subject.go
Normal 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
|
||||
}
|
Loading…
Add table
Reference in a new issue