Skip to content

Commit

Permalink
authorizer/webhook: make webhook authorizer cluster aware
Browse files Browse the repository at this point in the history
  • Loading branch information
s-urbaniak committed Feb 17, 2022
1 parent 50b2aff commit 64d2e2d
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 22 deletions.
1 change: 1 addition & 0 deletions pkg/kubeapiserver/authorizer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, erro
}
webhookAuthorizer, err := webhook.New(config.WebhookConfigFile,
config.WebhookVersion,
"",
config.WebhookCacheAuthorizedTTL,
config.WebhookCacheUnauthorizedTTL,
*config.WebhookRetryBackoff,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ type DelegatingAuthorizerConfig struct {
// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
WebhookRetryBackoff *wait.Backoff

// The cluster to execute authorization decisions against (optional).
Cluster string
}

func (c DelegatingAuthorizerConfig) New() (authorizer.Authorizer, error) {
Expand All @@ -58,5 +61,6 @@ func (c DelegatingAuthorizerConfig) New() (authorizer.Authorizer, error) {
RecordRequestTotal: RecordRequestTotal,
RecordRequestLatency: RecordRequestLatency,
},
c.Cluster,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ type WebhookAuthorizer struct {
retryBackoff wait.Backoff
decisionOnError authorizer.Decision
metrics AuthorizerMetrics
cluster string
}

// NewFromInterface creates a WebhookAuthorizer using the given subjectAccessReview client
func NewFromInterface(subjectAccessReview authorizationv1client.AuthorizationV1Interface, authorizedTTL, unauthorizedTTL time.Duration, retryBackoff wait.Backoff, metrics AuthorizerMetrics) (*WebhookAuthorizer, error) {
return newWithBackoff(&subjectAccessReviewV1Client{subjectAccessReview.RESTClient()}, authorizedTTL, unauthorizedTTL, retryBackoff, metrics)
func NewFromInterface(subjectAccessReview authorizationv1client.AuthorizationV1Interface, authorizedTTL, unauthorizedTTL time.Duration, retryBackoff wait.Backoff, metrics AuthorizerMetrics, cluster string) (*WebhookAuthorizer, error) {
return newWithBackoff(&subjectAccessReviewV1Client{subjectAccessReview.RESTClient(), cluster}, authorizedTTL, unauthorizedTTL, retryBackoff, metrics, cluster)
}

// New creates a new WebhookAuthorizer from the provided kubeconfig file.
Expand All @@ -93,19 +94,19 @@ func NewFromInterface(subjectAccessReview authorizationv1client.AuthorizationV1I
//
// For additional HTTP configuration, refer to the kubeconfig documentation
// https://kubernetes.io/docs/user-guide/kubeconfig-file/.
func New(kubeConfigFile string, version string, authorizedTTL, unauthorizedTTL time.Duration, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (*WebhookAuthorizer, error) {
subjectAccessReview, err := subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile, version, retryBackoff, customDial)
func New(kubeConfigFile, version, cluster string, authorizedTTL, unauthorizedTTL time.Duration, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (*WebhookAuthorizer, error) {
subjectAccessReview, err := subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile, version, cluster, retryBackoff, customDial)
if err != nil {
return nil, err
}
return newWithBackoff(subjectAccessReview, authorizedTTL, unauthorizedTTL, retryBackoff, AuthorizerMetrics{
RecordRequestTotal: noopMetrics{}.RecordRequestTotal,
RecordRequestLatency: noopMetrics{}.RecordRequestLatency,
})
}, cluster)
}

// newWithBackoff allows tests to skip the sleep.
func newWithBackoff(subjectAccessReview subjectAccessReviewer, authorizedTTL, unauthorizedTTL time.Duration, retryBackoff wait.Backoff, metrics AuthorizerMetrics) (*WebhookAuthorizer, error) {
func newWithBackoff(subjectAccessReview subjectAccessReviewer, authorizedTTL, unauthorizedTTL time.Duration, retryBackoff wait.Backoff, metrics AuthorizerMetrics, cluster string) (*WebhookAuthorizer, error) {
return &WebhookAuthorizer{
subjectAccessReview: subjectAccessReview,
responseCache: cache.NewLRUExpireCache(8192),
Expand All @@ -114,6 +115,7 @@ func newWithBackoff(subjectAccessReview subjectAccessReviewer, authorizedTTL, un
retryBackoff: retryBackoff,
decisionOnError: authorizer.DecisionNoOpinion,
metrics: metrics,
cluster: cluster,
}, nil
}

Expand Down Expand Up @@ -272,7 +274,7 @@ func convertToSARExtra(extra map[string][]string) map[string]authorizationv1.Ext
// subjectAccessReviewInterfaceFromKubeconfig builds a client from the specified kubeconfig file,
// and returns a SubjectAccessReviewInterface that uses that client. Note that the client submits SubjectAccessReview
// requests to the exact path specified in the kubeconfig file, so arbitrary non-API servers can be targeted.
func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile string, version string, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (subjectAccessReviewer, error) {
func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile, version, cluster string, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (subjectAccessReviewer, error) {
localScheme := runtime.NewScheme()
if err := scheme.AddToScheme(localScheme); err != nil {
return nil, err
Expand All @@ -288,7 +290,7 @@ func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile string, version s
if err != nil {
return nil, err
}
return &subjectAccessReviewV1ClientGW{gw.RestClient}, nil
return &subjectAccessReviewV1ClientGW{gw.RestClient, cluster}, nil

case authorizationv1beta1.SchemeGroupVersion.Version:
groupVersions := []schema.GroupVersion{authorizationv1beta1.SchemeGroupVersion}
Expand All @@ -299,7 +301,7 @@ func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile string, version s
if err != nil {
return nil, err
}
return &subjectAccessReviewV1beta1ClientGW{gw.RestClient}, nil
return &subjectAccessReviewV1beta1ClientGW{gw.RestClient, cluster}, nil

default:
return nil, fmt.Errorf(
Expand All @@ -312,13 +314,15 @@ func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile string, version s
}

type subjectAccessReviewV1Client struct {
client rest.Interface
client rest.Interface
cluster string
}

func (t *subjectAccessReviewV1Client) Create(ctx context.Context, subjectAccessReview *authorizationv1.SubjectAccessReview, opts metav1.CreateOptions) (result *authorizationv1.SubjectAccessReview, statusCode int, err error) {
result = &authorizationv1.SubjectAccessReview{}

restResult := t.client.Post().
Cluster(t.cluster).
Resource("subjectaccessreviews").
VersionedParams(&opts, scheme.ParameterCodec).
Body(subjectAccessReview).
Expand All @@ -331,14 +335,15 @@ func (t *subjectAccessReviewV1Client) Create(ctx context.Context, subjectAccessR

// subjectAccessReviewV1ClientGW used by the generic webhook, doesn't specify GVR.
type subjectAccessReviewV1ClientGW struct {
client rest.Interface
client rest.Interface
cluster string
}

func (t *subjectAccessReviewV1ClientGW) Create(ctx context.Context, subjectAccessReview *authorizationv1.SubjectAccessReview, _ metav1.CreateOptions) (*authorizationv1.SubjectAccessReview, int, error) {
var statusCode int
result := &authorizationv1.SubjectAccessReview{}

restResult := t.client.Post().Body(subjectAccessReview).Do(ctx)
restResult := t.client.Post().Cluster(t.cluster).Body(subjectAccessReview).Do(ctx)

restResult.StatusCode(&statusCode)
err := restResult.Into(result)
Expand All @@ -348,15 +353,16 @@ func (t *subjectAccessReviewV1ClientGW) Create(ctx context.Context, subjectAcces

// subjectAccessReviewV1beta1ClientGW used by the generic webhook, doesn't specify GVR.
type subjectAccessReviewV1beta1ClientGW struct {
client rest.Interface
client rest.Interface
cluster string
}

func (t *subjectAccessReviewV1beta1ClientGW) Create(ctx context.Context, subjectAccessReview *authorizationv1.SubjectAccessReview, _ metav1.CreateOptions) (*authorizationv1.SubjectAccessReview, int, error) {
var statusCode int
v1beta1Review := &authorizationv1beta1.SubjectAccessReview{Spec: v1SpecToV1beta1Spec(&subjectAccessReview.Spec)}
v1beta1Result := &authorizationv1beta1.SubjectAccessReview{}

restResult := t.client.Post().Body(v1beta1Review).Do(ctx)
restResult := t.client.Post().Cluster(t.cluster).Body(v1beta1Review).Do(ctx)

restResult.StatusCode(&statusCode)
err := restResult.Into(v1beta1Result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,11 @@ current-context: default
return fmt.Errorf("failed to execute test template: %v", err)
}
// Create a new authorizer
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p, "v1", testRetryBackoff, nil)
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p, "v1", "", testRetryBackoff, nil)
if err != nil {
return fmt.Errorf("error building sar client: %v", err)
}
_, err = newWithBackoff(sarClient, 0, 0, testRetryBackoff, noopAuthorizerMetrics())
_, err = newWithBackoff(sarClient, 0, 0, testRetryBackoff, noopAuthorizerMetrics(), "")
return err
}()
if err != nil && !tt.wantErr {
Expand Down Expand Up @@ -333,11 +333,11 @@ func newV1Authorizer(callbackURL string, clientCert, clientKey, ca []byte, cache
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
return nil, err
}
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p, "v1", testRetryBackoff, nil)
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p, "v1", "", testRetryBackoff, nil)
if err != nil {
return nil, fmt.Errorf("error building sar client: %v", err)
}
return newWithBackoff(sarClient, cacheTime, cacheTime, testRetryBackoff, metrics)
return newWithBackoff(sarClient, cacheTime, cacheTime, testRetryBackoff, metrics, "")
}

func TestV1TLSConfig(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,11 @@ current-context: default
return fmt.Errorf("failed to execute test template: %v", err)
}
// Create a new authorizer
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p, "v1beta1", testRetryBackoff, nil)
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p, "v1beta1", "", testRetryBackoff, nil)
if err != nil {
return fmt.Errorf("error building sar client: %v", err)
}
_, err = newWithBackoff(sarClient, 0, 0, testRetryBackoff, noopAuthorizerMetrics())
_, err = newWithBackoff(sarClient, 0, 0, testRetryBackoff, noopAuthorizerMetrics(), "")
return err
}()
if err != nil && !tt.wantErr {
Expand Down Expand Up @@ -325,11 +325,11 @@ func newV1beta1Authorizer(callbackURL string, clientCert, clientKey, ca []byte,
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
return nil, err
}
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p, "v1beta1", testRetryBackoff, nil)
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p, "v1beta1", "", testRetryBackoff, nil)
if err != nil {
return nil, fmt.Errorf("error building sar client: %v", err)
}
return newWithBackoff(sarClient, cacheTime, cacheTime, testRetryBackoff, noopAuthorizerMetrics())
return newWithBackoff(sarClient, cacheTime, cacheTime, testRetryBackoff, noopAuthorizerMetrics(), "")
}

func TestV1beta1TLSConfig(t *testing.T) {
Expand Down

0 comments on commit 64d2e2d

Please sign in to comment.