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 }