...

Source file src/sigs.k8s.io/controller-runtime/pkg/metrics/filters/filters.go

Documentation: sigs.k8s.io/controller-runtime/pkg/metrics/filters

     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  }
   122  

View as plain text