2020-03-11 18:14:23 -07:00
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
2022-11-29 14:59:40 +01:00
|
|
|
"context"
|
2020-03-11 18:14:23 -07:00
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
|
2022-08-31 14:03:47 +08:00
|
|
|
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
2020-03-11 18:14:23 -07:00
|
|
|
authorizationv1 "k8s.io/api/authorization/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
)
|
|
|
|
|
2022-05-17 08:19:03 +02:00
|
|
|
// CanIOptions provides utility to check if user has authorization for the given operation
|
2022-09-07 08:54:34 +02:00
|
|
|
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
|
2022-11-29 14:59:40 +01:00
|
|
|
RunAccessCheck(context.Context) (bool, error)
|
2022-09-07 08:54:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type canIOptions struct {
|
2020-03-11 18:14:23 -07:00
|
|
|
namespace string
|
|
|
|
verb string
|
|
|
|
kind string
|
2022-05-17 16:40:51 +02:00
|
|
|
client dclient.Interface
|
2020-03-11 18:14:23 -07:00
|
|
|
}
|
|
|
|
|
2022-05-17 08:19:03 +02:00
|
|
|
// NewCanI returns a new instance of operation access controller evaluator
|
2022-09-07 08:54:34 +02:00
|
|
|
func NewCanI(client dclient.Interface, kind, namespace, verb string) CanIOptions {
|
|
|
|
return &canIOptions{
|
2022-04-27 15:34:08 +02:00
|
|
|
namespace: namespace,
|
|
|
|
kind: kind,
|
|
|
|
verb: verb,
|
|
|
|
client: client,
|
2020-03-11 18:14:23 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-17 08:19:03 +02:00
|
|
|
// RunAccessCheck checks if the caller can perform the operation
|
2020-03-11 18:14:23 -07:00
|
|
|
// - 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
|
2020-08-30 14:13:20 +05:30
|
|
|
// - If disallowed, the reason and evaluationError is available in the logs
|
2020-03-11 18:14:23 -07:00
|
|
|
// - each can generates a SelfSubjectAccessReview resource and response is evaluated for permissions
|
2022-11-29 14:59:40 +01:00
|
|
|
func (o *canIOptions) RunAccessCheck(ctx context.Context) (bool, error) {
|
2020-03-11 18:14:23 -07:00
|
|
|
// get GroupVersionResource from RESTMapper
|
|
|
|
// get GVR from kind
|
2022-05-03 07:30:04 +02:00
|
|
|
gvr, err := o.client.Discovery().GetGVRFromKind(o.kind)
|
2020-12-03 19:19:36 -08:00
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("failed to get GVR for kind %s", o.kind)
|
|
|
|
}
|
|
|
|
|
2020-03-11 18:14:23 -07:00
|
|
|
if reflect.DeepEqual(gvr, schema.GroupVersionResource{}) {
|
|
|
|
// cannot find GVR
|
|
|
|
return false, fmt.Errorf("failed to get the Group Version Resource for kind %s", o.kind)
|
|
|
|
}
|
|
|
|
|
2020-03-17 18:34:44 -07:00
|
|
|
sar := &authorizationv1.SelfSubjectAccessReview{
|
2020-03-11 18:14:23 -07:00
|
|
|
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
|
|
|
|
ResourceAttributes: &authorizationv1.ResourceAttributes{
|
|
|
|
Namespace: o.namespace,
|
|
|
|
Verb: o.verb,
|
|
|
|
Group: gvr.Group,
|
|
|
|
Resource: gvr.Resource,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
// Set self subject access review
|
|
|
|
// - namespace
|
|
|
|
// - verb
|
|
|
|
// - resource
|
|
|
|
// - subresource
|
2022-04-27 15:34:08 +02:00
|
|
|
logger := logger.WithValues("kind", sar.Kind, "namespace", sar.Namespace, "name", sar.Name)
|
2020-03-11 18:14:23 -07:00
|
|
|
|
|
|
|
// Create the Resource
|
2022-11-29 14:59:40 +01:00
|
|
|
resp, err := o.client.CreateResource(ctx, "", "SelfSubjectAccessReview", "", sar, false)
|
2020-03-11 18:14:23 -07:00
|
|
|
if err != nil {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Error(err, "failed to create resource")
|
2020-03-11 18:14:23 -07:00
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// status.allowed
|
|
|
|
allowed, ok, err := unstructured.NestedBool(resp.Object, "status", "allowed")
|
|
|
|
if !ok {
|
|
|
|
if err != nil {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Error(err, "failed to get the field", "field", "status.allowed")
|
2020-03-11 18:14:23 -07:00
|
|
|
}
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Info("field not found", "field", "status.allowed")
|
2020-03-11 18:14:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if !allowed {
|
|
|
|
// status.reason
|
|
|
|
reason, ok, err := unstructured.NestedString(resp.Object, "status", "reason")
|
|
|
|
if !ok {
|
|
|
|
if err != nil {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Error(err, "failed to get the field", "field", "status.reason")
|
2020-03-11 18:14:23 -07:00
|
|
|
}
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Info("field not found", "field", "status.reason")
|
2020-03-11 18:14:23 -07:00
|
|
|
}
|
|
|
|
// status.evaluationError
|
2020-12-03 19:19:36 -08:00
|
|
|
evaluationError, ok, err := unstructured.NestedString(resp.Object, "status", "evaluationError")
|
2020-03-11 18:14:23 -07:00
|
|
|
if !ok {
|
|
|
|
if err != nil {
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Error(err, "failed to get the field", "field", "status.evaluationError")
|
2020-03-11 18:14:23 -07:00
|
|
|
}
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Info("field not found", "field", "status.evaluationError")
|
2020-03-11 18:14:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reporting ? (just logs)
|
2020-03-17 11:05:20 -07:00
|
|
|
logger.Info("disallowed operation", "reason", reason, "evaluationError", evaluationError)
|
2020-03-11 18:14:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return allowed, nil
|
|
|
|
}
|