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:
parent
5d4e250dfe
commit
7a838de4f1
5 changed files with 139 additions and 29 deletions
|
@ -4,9 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kyverno/kyverno/pkg/auth/checker"
|
||||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
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"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
|
authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
|
||||||
)
|
)
|
||||||
|
@ -34,7 +33,7 @@ type canIOptions struct {
|
||||||
subresource string
|
subresource string
|
||||||
user string
|
user string
|
||||||
discovery Discovery
|
discovery Discovery
|
||||||
sarClient authorizationv1client.SubjectAccessReviewInterface
|
checker checker.AuthChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCanI returns a new instance of operation access controller evaluator
|
// NewCanI returns a new instance of operation access controller evaluator
|
||||||
|
@ -46,7 +45,7 @@ func NewCanI(discovery Discovery, sarClient authorizationv1client.SubjectAccessR
|
||||||
subresource: subresource,
|
subresource: subresource,
|
||||||
user: user,
|
user: user,
|
||||||
discovery: discovery,
|
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 {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to get GVR for kind %s", o.kind)
|
return false, fmt.Errorf("failed to get GVR for kind %s", o.kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
if gvr.Empty() {
|
if gvr.Empty() {
|
||||||
// cannot find GVR
|
// cannot find GVR
|
||||||
return false, fmt.Errorf("failed to get the Group Version Resource for kind %s", o.kind)
|
return false, fmt.Errorf("failed to get the Group Version Resource for kind %s", o.kind)
|
||||||
}
|
}
|
||||||
|
logger := logger.WithValues("kind", kind, "namespace", o.namespace, "gvr", gvr.String(), "verb", o.verb)
|
||||||
sar := &authorizationv1.SubjectAccessReview{
|
result, err := o.checker.Check(ctx, gvr.Group, gvr.Version, gvr.Resource, o.subresource, o.namespace, o.verb)
|
||||||
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{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "failed to create resource")
|
logger.Error(err, "failed to check permissions")
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
if !result.Allowed {
|
||||||
if !resp.Status.Allowed {
|
logger.Info("disallowed operation", "reason", result.Reason, "evaluationError", result.EvaluationError)
|
||||||
reason := resp.Status.Reason
|
|
||||||
evaluationError := resp.Status.EvaluationError
|
|
||||||
logger.Info("disallowed operation", "reason", reason, "evaluationError", evaluationError)
|
|
||||||
}
|
}
|
||||||
|
return result.Allowed, nil
|
||||||
return resp.Status.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