k8s.io/apiserver@v0.31.1/pkg/endpoints/filters/metrics_test.go (about) 1 /* 2 Copyright 2020 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 filters 18 19 import ( 20 "errors" 21 "net/http" 22 "net/http/httptest" 23 "strings" 24 "testing" 25 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/runtime/serializer" 28 auditinternal "k8s.io/apiserver/pkg/apis/audit" 29 "k8s.io/apiserver/pkg/authentication/authenticator" 30 "k8s.io/apiserver/pkg/authentication/user" 31 "k8s.io/apiserver/pkg/authorization/authorizer" 32 "k8s.io/component-base/metrics/legacyregistry" 33 "k8s.io/component-base/metrics/testutil" 34 ) 35 36 func TestMetrics(t *testing.T) { 37 // Excluding authentication_duration_seconds since it is difficult to predict its values. 38 metrics := []string{ 39 "authenticated_user_requests", 40 "authentication_attempts", 41 } 42 43 testCases := []struct { 44 desc string 45 response *authenticator.Response 46 status bool 47 err error 48 apiAudience authenticator.Audiences 49 want string 50 }{ 51 { 52 desc: "auth ok", 53 response: &authenticator.Response{ 54 User: &user.DefaultInfo{Name: "admin"}, 55 }, 56 status: true, 57 want: ` 58 # HELP authenticated_user_requests [ALPHA] Counter of authenticated requests broken out by username. 59 # TYPE authenticated_user_requests counter 60 authenticated_user_requests{username="admin"} 1 61 # HELP authentication_attempts [ALPHA] Counter of authenticated attempts. 62 # TYPE authentication_attempts counter 63 authentication_attempts{result="success"} 1 64 `, 65 }, 66 { 67 desc: "auth failed with error", 68 err: errors.New("some error"), 69 want: ` 70 # HELP authentication_attempts [ALPHA] Counter of authenticated attempts. 71 # TYPE authentication_attempts counter 72 authentication_attempts{result="error"} 1 73 `, 74 }, 75 { 76 desc: "auth failed with status false", 77 want: ` 78 # HELP authentication_attempts [ALPHA] Counter of authenticated attempts. 79 # TYPE authentication_attempts counter 80 authentication_attempts{result="failure"} 1 81 `, 82 }, 83 { 84 desc: "auth failed due to audiences not intersecting", 85 response: &authenticator.Response{ 86 User: &user.DefaultInfo{Name: "admin"}, 87 Audiences: authenticator.Audiences{"audience-x"}, 88 }, 89 status: true, 90 apiAudience: authenticator.Audiences{"audience-y"}, 91 want: ` 92 # HELP authentication_attempts [ALPHA] Counter of authenticated attempts. 93 # TYPE authentication_attempts counter 94 authentication_attempts{result="error"} 1 95 `, 96 }, 97 { 98 desc: "audiences not supplied in the response", 99 response: &authenticator.Response{ 100 User: &user.DefaultInfo{Name: "admin"}, 101 }, 102 status: true, 103 apiAudience: authenticator.Audiences{"audience-y"}, 104 want: ` 105 # HELP authenticated_user_requests [ALPHA] Counter of authenticated requests broken out by username. 106 # TYPE authenticated_user_requests counter 107 authenticated_user_requests{username="admin"} 1 108 # HELP authentication_attempts [ALPHA] Counter of authenticated attempts. 109 # TYPE authentication_attempts counter 110 authentication_attempts{result="success"} 1 111 `, 112 }, 113 { 114 desc: "audiences not supplied to the handler", 115 response: &authenticator.Response{ 116 User: &user.DefaultInfo{Name: "admin"}, 117 Audiences: authenticator.Audiences{"audience-x"}, 118 }, 119 status: true, 120 want: ` 121 # HELP authenticated_user_requests [ALPHA] Counter of authenticated requests broken out by username. 122 # TYPE authenticated_user_requests counter 123 authenticated_user_requests{username="admin"} 1 124 # HELP authentication_attempts [ALPHA] Counter of authenticated attempts. 125 # TYPE authentication_attempts counter 126 authentication_attempts{result="success"} 1 127 `, 128 }, 129 } 130 131 // Since prometheus' gatherer is global, other tests may have updated metrics already, so 132 // we need to reset them prior running this test. 133 // This also implies that we can't run this test in parallel with other auth tests. 134 authenticatedUserCounter.Reset() 135 authenticatedAttemptsCounter.Reset() 136 137 for _, tt := range testCases { 138 t.Run(tt.desc, func(t *testing.T) { 139 defer authenticatedUserCounter.Reset() 140 defer authenticatedAttemptsCounter.Reset() 141 done := make(chan struct{}) 142 auth := WithAuthentication( 143 http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { 144 close(done) 145 }), 146 authenticator.RequestFunc(func(_ *http.Request) (*authenticator.Response, bool, error) { 147 return tt.response, tt.status, tt.err 148 }), 149 http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { 150 close(done) 151 }), 152 tt.apiAudience, 153 nil, 154 ) 155 156 auth.ServeHTTP(httptest.NewRecorder(), &http.Request{}) 157 <-done 158 159 if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tt.want), metrics...); err != nil { 160 t.Fatal(err) 161 } 162 }) 163 } 164 } 165 166 func TestRecordAuthorizationMetricsMetrics(t *testing.T) { 167 // Excluding authorization_duration_seconds since it is difficult to predict its values. 168 metrics := []string{ 169 "authorization_attempts_total", 170 "authorization_decision_annotations_total", 171 } 172 173 testCases := []struct { 174 desc string 175 authorizer fakeAuthorizer 176 want string 177 }{ 178 { 179 desc: "auth ok", 180 authorizer: fakeAuthorizer{ 181 authorizer.DecisionAllow, 182 "RBAC: allowed to patch pod", 183 nil, 184 }, 185 want: ` 186 # HELP authorization_attempts_total [ALPHA] Counter of authorization attempts broken down by result. It can be either 'allowed', 'denied', 'no-opinion' or 'error'. 187 # TYPE authorization_attempts_total counter 188 authorization_attempts_total{result="allowed"} 1 189 `, 190 }, 191 { 192 desc: "decision forbid", 193 authorizer: fakeAuthorizer{ 194 authorizer.DecisionDeny, 195 "RBAC: not allowed to patch pod", 196 nil, 197 }, 198 want: ` 199 # HELP authorization_attempts_total [ALPHA] Counter of authorization attempts broken down by result. It can be either 'allowed', 'denied', 'no-opinion' or 'error'. 200 # TYPE authorization_attempts_total counter 201 authorization_attempts_total{result="denied"} 1 202 `, 203 }, 204 { 205 desc: "authorizer failed with error", 206 authorizer: fakeAuthorizer{ 207 authorizer.DecisionNoOpinion, 208 "", 209 errors.New("can't parse user info"), 210 }, 211 want: ` 212 # HELP authorization_attempts_total [ALPHA] Counter of authorization attempts broken down by result. It can be either 'allowed', 'denied', 'no-opinion' or 'error'. 213 # TYPE authorization_attempts_total counter 214 authorization_attempts_total{result="error"} 1 215 `, 216 }, 217 { 218 desc: "authorizer decided allow with error", 219 authorizer: fakeAuthorizer{ 220 authorizer.DecisionAllow, 221 "", 222 errors.New("can't parse user info"), 223 }, 224 want: ` 225 # HELP authorization_attempts_total [ALPHA] Counter of authorization attempts broken down by result. It can be either 'allowed', 'denied', 'no-opinion' or 'error'. 226 # TYPE authorization_attempts_total counter 227 authorization_attempts_total{result="allowed"} 1 228 `, 229 }, 230 { 231 desc: "authorizer failed with error", 232 authorizer: fakeAuthorizer{ 233 authorizer.DecisionNoOpinion, 234 "", 235 nil, 236 }, 237 want: ` 238 # HELP authorization_attempts_total [ALPHA] Counter of authorization attempts broken down by result. It can be either 'allowed', 'denied', 'no-opinion' or 'error'. 239 # TYPE authorization_attempts_total counter 240 authorization_attempts_total{result="no-opinion"} 1 241 `, 242 }, 243 } 244 245 // Since prometheus' gatherer is global, other tests may have updated metrics already, so 246 // we need to reset them prior running this test. 247 // This also implies that we can't run this test in parallel with other auth tests. 248 authorizationAttemptsCounter.Reset() 249 250 scheme := runtime.NewScheme() 251 negotiatedSerializer := serializer.NewCodecFactory(scheme).WithoutConversion() 252 253 for _, tt := range testCases { 254 t.Run(tt.desc, func(t *testing.T) { 255 defer authorizationAttemptsCounter.Reset() 256 257 audit := &auditinternal.Event{Level: auditinternal.LevelMetadata} 258 handler := WithAuthorization(&fakeHTTPHandler{}, tt.authorizer, negotiatedSerializer) 259 // TODO: fake audit injector 260 261 req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil) 262 req = withTestContext(req, nil, audit) 263 req.RemoteAddr = "127.0.0.1" 264 handler.ServeHTTP(httptest.NewRecorder(), req) 265 266 if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tt.want), metrics...); err != nil { 267 t.Fatal(err) 268 } 269 }) 270 } 271 }