sigs.k8s.io/controller-runtime@v0.18.2/pkg/metrics/filters/filters.go (about) 1 package filters 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 "time" 8 9 "github.com/go-logr/logr" 10 "k8s.io/apimachinery/pkg/util/wait" 11 "k8s.io/apiserver/pkg/authentication/authenticatorfactory" 12 "k8s.io/apiserver/pkg/authorization/authorizer" 13 "k8s.io/apiserver/pkg/authorization/authorizerfactory" 14 authenticationv1 "k8s.io/client-go/kubernetes/typed/authentication/v1" 15 authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" 16 "k8s.io/client-go/rest" 17 18 metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 19 ) 20 21 // WithAuthenticationAndAuthorization provides a metrics.Filter for authentication and authorization. 22 // Metrics will be authenticated (via TokenReviews) and authorized (via SubjectAccessReviews) with the 23 // kube-apiserver. 24 // For the authentication and authorization the controller needs a ClusterRole 25 // with the following rules: 26 // * apiGroups: authentication.k8s.io, resources: tokenreviews, verbs: create 27 // * apiGroups: authorization.k8s.io, resources: subjectaccessreviews, verbs: create 28 // 29 // To scrape metrics e.g. via Prometheus the client needs a ClusterRole 30 // with the following rule: 31 // * nonResourceURLs: "/metrics", verbs: get 32 // 33 // Note: Please note that configuring this metrics provider will introduce a dependency to "k8s.io/apiserver" 34 // to your go module. 35 func WithAuthenticationAndAuthorization(config *rest.Config, httpClient *http.Client) (metricsserver.Filter, error) { 36 authenticationV1Client, err := authenticationv1.NewForConfigAndClient(config, httpClient) 37 if err != nil { 38 return nil, err 39 } 40 authorizationV1Client, err := authorizationv1.NewForConfigAndClient(config, httpClient) 41 if err != nil { 42 return nil, err 43 } 44 45 authenticatorConfig := authenticatorfactory.DelegatingAuthenticatorConfig{ 46 Anonymous: false, // Require authentication. 47 CacheTTL: 1 * time.Minute, 48 TokenAccessReviewClient: authenticationV1Client, 49 TokenAccessReviewTimeout: 10 * time.Second, 50 // wait.Backoff is copied from: https://github.com/kubernetes/apiserver/blob/v0.29.0/pkg/server/options/authentication.go#L43-L50 51 // options.DefaultAuthWebhookRetryBackoff is not used to avoid a dependency on "k8s.io/apiserver/pkg/server/options". 52 WebhookRetryBackoff: &wait.Backoff{ 53 Duration: 500 * time.Millisecond, 54 Factor: 1.5, 55 Jitter: 0.2, 56 Steps: 5, 57 }, 58 } 59 delegatingAuthenticator, _, err := authenticatorConfig.New() 60 if err != nil { 61 return nil, fmt.Errorf("failed to create authenticator: %w", err) 62 } 63 64 authorizerConfig := authorizerfactory.DelegatingAuthorizerConfig{ 65 SubjectAccessReviewClient: authorizationV1Client, 66 AllowCacheTTL: 5 * time.Minute, 67 DenyCacheTTL: 30 * time.Second, 68 // wait.Backoff is copied from: https://github.com/kubernetes/apiserver/blob/v0.29.0/pkg/server/options/authentication.go#L43-L50 69 // options.DefaultAuthWebhookRetryBackoff is not used to avoid a dependency on "k8s.io/apiserver/pkg/server/options". 70 WebhookRetryBackoff: &wait.Backoff{ 71 Duration: 500 * time.Millisecond, 72 Factor: 1.5, 73 Jitter: 0.2, 74 Steps: 5, 75 }, 76 } 77 delegatingAuthorizer, err := authorizerConfig.New() 78 if err != nil { 79 return nil, fmt.Errorf("failed to create authorizer: %w", err) 80 } 81 82 return func(log logr.Logger, handler http.Handler) (http.Handler, error) { 83 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 84 ctx := req.Context() 85 86 res, ok, err := delegatingAuthenticator.AuthenticateRequest(req) 87 if err != nil { 88 log.Error(err, "Authentication failed") 89 http.Error(w, "Authentication failed", http.StatusInternalServerError) 90 return 91 } 92 if !ok { 93 log.V(4).Info("Authentication failed") 94 http.Error(w, "Unauthorized", http.StatusUnauthorized) 95 return 96 } 97 98 attributes := authorizer.AttributesRecord{ 99 User: res.User, 100 Verb: strings.ToLower(req.Method), 101 Path: req.URL.Path, 102 } 103 104 authorized, reason, err := delegatingAuthorizer.Authorize(ctx, attributes) 105 if err != nil { 106 msg := fmt.Sprintf("Authorization for user %s failed", attributes.User.GetName()) 107 log.Error(err, msg) 108 http.Error(w, msg, http.StatusInternalServerError) 109 return 110 } 111 if authorized != authorizer.DecisionAllow { 112 msg := fmt.Sprintf("Authorization denied for user %s", attributes.User.GetName()) 113 log.V(4).Info(fmt.Sprintf("%s: %s", msg, reason)) 114 http.Error(w, msg, http.StatusForbidden) 115 return 116 } 117 118 handler.ServeHTTP(w, req) 119 }), nil 120 }, nil 121 }