k8s.io/apiserver@v0.31.1/plugin/pkg/authorizer/webhook/metrics_test.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package webhook
    18  
    19  import (
    20  	"context"
    21  	"net/http"
    22  	"testing"
    23  
    24  	authorizationv1 "k8s.io/api/authorization/v1"
    25  	"k8s.io/apiserver/pkg/apis/apiserver"
    26  	"k8s.io/apiserver/pkg/authentication/user"
    27  	"k8s.io/apiserver/pkg/authorization/authorizer"
    28  	"k8s.io/apiserver/pkg/authorization/cel"
    29  )
    30  
    31  func TestAuthorizerMetrics(t *testing.T) {
    32  	scenarios := []struct {
    33  		name                            string
    34  		canceledRequest                 bool
    35  		clientCert, clientKey, clientCA []byte
    36  		serverCert, serverKey, serverCA []byte
    37  		authzFakeServiceStatusCode      int
    38  		authFakeServiceDeny             bool
    39  		expectedRegisteredStatusCode    string
    40  		expectEvalutionResult           string
    41  		expectDurationResult            string
    42  		expectFailOpenResult            string
    43  		wantErr                         bool
    44  	}{
    45  		{
    46  			name:       "happy path",
    47  			clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
    48  			serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
    49  			expectedRegisteredStatusCode: "200",
    50  			expectEvalutionResult:        "success",
    51  			expectDurationResult:         "success",
    52  			expectFailOpenResult:         "",
    53  		},
    54  
    55  		{
    56  			name:       "timed out request",
    57  			clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
    58  			serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
    59  			authzFakeServiceStatusCode:   http.StatusGatewayTimeout,
    60  			expectedRegisteredStatusCode: "504",
    61  			expectEvalutionResult:        "timeout",
    62  			expectDurationResult:         "timeout",
    63  			expectFailOpenResult:         "timeout",
    64  		},
    65  
    66  		{
    67  			name:       "canceled request",
    68  			clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
    69  			serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
    70  			canceledRequest:              true,
    71  			expectedRegisteredStatusCode: "<error>",
    72  			expectEvalutionResult:        "canceled",
    73  			expectDurationResult:         "canceled",
    74  			expectFailOpenResult:         "",
    75  		},
    76  
    77  		{
    78  			name:       "an internal error returned from the webhook",
    79  			clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
    80  			serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
    81  			authzFakeServiceStatusCode:   500,
    82  			expectedRegisteredStatusCode: "500",
    83  			expectEvalutionResult:        "error",
    84  			expectDurationResult:         "error",
    85  			expectFailOpenResult:         "error",
    86  		},
    87  
    88  		{
    89  			name:       "incorrect client certificate used, the webhook not called, an error is recorded",
    90  			clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
    91  			serverCert: serverCert, serverKey: serverKey, serverCA: badCACert,
    92  			expectedRegisteredStatusCode: "<error>",
    93  			expectEvalutionResult:        "error",
    94  			expectDurationResult:         "error",
    95  			expectFailOpenResult:         "error",
    96  			wantErr:                      true,
    97  		},
    98  	}
    99  
   100  	for _, scenario := range scenarios {
   101  		t.Run(scenario.name, func(t *testing.T) {
   102  			ctx, cancel := context.WithCancel(context.Background())
   103  			defer cancel()
   104  
   105  			service := new(mockV1Service)
   106  			service.statusCode = scenario.authzFakeServiceStatusCode
   107  			if service.statusCode == 0 {
   108  				service.statusCode = 200
   109  			}
   110  			service.reviewHook = func(*authorizationv1.SubjectAccessReview) {
   111  				if scenario.canceledRequest {
   112  					cancel()
   113  				}
   114  			}
   115  			service.allow = !scenario.authFakeServiceDeny
   116  
   117  			server, err := NewV1TestServer(service, scenario.serverCert, scenario.serverKey, scenario.serverCA)
   118  			if err != nil {
   119  				t.Errorf("%s: failed to create server: %v", scenario.name, err)
   120  				return
   121  			}
   122  			defer server.Close()
   123  
   124  			fakeAuthzMetrics := &fakeAuthorizerMetrics{}
   125  			wh, err := newV1Authorizer(server.URL, scenario.clientCert, scenario.clientKey, scenario.clientCA, 0, fakeAuthzMetrics, []apiserver.WebhookMatchCondition{}, "")
   126  			if err != nil {
   127  				t.Error("failed to create client")
   128  				return
   129  			}
   130  
   131  			attr := authorizer.AttributesRecord{User: &user.DefaultInfo{}}
   132  			_, _, err = wh.Authorize(ctx, attr)
   133  			if scenario.wantErr {
   134  				if err == nil {
   135  					t.Errorf("expected error making authorization request: %v", err)
   136  				}
   137  			}
   138  
   139  			if fakeAuthzMetrics.totalCode != scenario.expectedRegisteredStatusCode {
   140  				t.Errorf("incorrect status code recorded for RecordRequestTotal method, expected = %v, got %v", scenario.expectedRegisteredStatusCode, fakeAuthzMetrics.totalCode)
   141  			}
   142  
   143  			if fakeAuthzMetrics.latencyCode != scenario.expectedRegisteredStatusCode {
   144  				t.Errorf("incorrect status code recorded for RecordRequestLatency method, expected = %v, got %v", scenario.expectedRegisteredStatusCode, fakeAuthzMetrics.latencyCode)
   145  			}
   146  
   147  			if fakeAuthzMetrics.evaluationsResult != scenario.expectEvalutionResult {
   148  				t.Errorf("expected evaluationsResult %q, got %q", scenario.expectEvalutionResult, fakeAuthzMetrics.evaluationsResult)
   149  			}
   150  			if fakeAuthzMetrics.durationResult != scenario.expectDurationResult {
   151  				t.Errorf("expected durationResult %q, got %q", scenario.expectDurationResult, fakeAuthzMetrics.durationResult)
   152  			}
   153  			if fakeAuthzMetrics.failOpenResult != scenario.expectFailOpenResult {
   154  				t.Errorf("expected failOpenResult %q, got %q", scenario.expectFailOpenResult, fakeAuthzMetrics.failOpenResult)
   155  			}
   156  		})
   157  	}
   158  }
   159  
   160  type fakeAuthorizerMetrics struct {
   161  	totalCode string
   162  
   163  	latency     float64
   164  	latencyCode string
   165  
   166  	evaluations       int
   167  	evaluationsResult string
   168  
   169  	duration       float64
   170  	durationResult string
   171  
   172  	failOpen       int
   173  	failOpenResult string
   174  
   175  	cel.NoopMatcherMetrics
   176  }
   177  
   178  func (f *fakeAuthorizerMetrics) RecordRequestTotal(_ context.Context, code string) {
   179  	f.totalCode = code
   180  }
   181  
   182  func (f *fakeAuthorizerMetrics) RecordRequestLatency(_ context.Context, code string, latency float64) {
   183  	f.latency = latency
   184  	f.latencyCode = code
   185  }
   186  
   187  func (f *fakeAuthorizerMetrics) RecordWebhookEvaluation(ctx context.Context, name, result string) {
   188  	f.evaluations += 1
   189  	f.evaluationsResult = result
   190  }
   191  func (f *fakeAuthorizerMetrics) RecordWebhookDuration(ctx context.Context, name, result string, duration float64) {
   192  	f.duration = duration
   193  	f.durationResult = result
   194  }
   195  func (f *fakeAuthorizerMetrics) RecordWebhookFailOpen(ctx context.Context, name, result string) {
   196  	f.failOpen += 1
   197  	f.failOpenResult = result
   198  }