package auth import ( "context" "fmt" 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" ) // Discovery provides interface to mange Kind and GVR mapping type Discovery interface { GetGVRFromGVK(schema.GroupVersionKind) (schema.GroupVersionResource, error) } // CanIOptions provides utility to check if user has authorization for the given operation type CanIOptions interface { // RunAccessCheck checks if the caller can perform the operation // - operation is a combination of namespace, kind, verb // - can only evaluate a single verb // - group version resource is determined from the kind using the discovery client REST mapper // - If disallowed, the reason and evaluationError is available in the logs // - each can generates a SelfSubjectAccessReview resource and response is evaluated for permissions RunAccessCheck(context.Context) (bool, error) } type canIOptions struct { namespace string verb string kind string subresource string discovery Discovery ssarClient authorizationv1client.SelfSubjectAccessReviewInterface } // NewCanI returns a new instance of operation access controller evaluator func NewCanI(discovery Discovery, ssarClient authorizationv1client.SelfSubjectAccessReviewInterface, kind, namespace, verb, subresource string) CanIOptions { return &canIOptions{ namespace: namespace, verb: verb, kind: kind, subresource: subresource, discovery: discovery, ssarClient: ssarClient, } } // RunAccessCheck checks if the caller can perform the operation // - operation is a combination of namespace, kind, verb // - can only evaluate a single verb // - group version resource is determined from the kind using the discovery client REST mapper // - If disallowed, the reason and evaluationError is available in the logs // - each can generates a SelfSubjectAccessReview resource and response is evaluated for permissions func (o *canIOptions) RunAccessCheck(ctx context.Context) (bool, error) { // get GroupVersionResource from RESTMapper // get GVR from kind apiVersion, kind := kubeutils.GetKindFromGVK(o.kind) gv, err := schema.ParseGroupVersion(apiVersion) if err != nil { return false, fmt.Errorf("failed to parse group/version %s", apiVersion) } gvr, err := o.discovery.GetGVRFromGVK(gv.WithKind(kind)) 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.SelfSubjectAccessReview{ Spec: authorizationv1.SelfSubjectAccessReviewSpec{ ResourceAttributes: &authorizationv1.ResourceAttributes{ Namespace: o.namespace, Verb: o.verb, Group: gvr.Group, Resource: gvr.Resource, Subresource: o.subresource, }, }, } // Set self subject access review // - namespace // - verb // - resource // - subresource logger := logger.WithValues("kind", sar.Kind, "namespace", sar.Namespace, "name", sar.Name) // Create the Resource resp, err := o.ssarClient.Create(ctx, sar, metav1.CreateOptions{}) if err != nil { logger.Error(err, "failed to create resource") return false, err } if !resp.Status.Allowed { reason := resp.Status.Reason evaluationError := resp.Status.EvaluationError // Reporting ? (just logs) logger.Info("disallowed operation", "reason", reason, "evaluationError", evaluationError) } return resp.Status.Allowed, nil }