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  }