k8s.io/apiserver@v0.31.1/pkg/authentication/request/headerrequest/requestheader_test.go (about)

     1  /*
     2  Copyright 2016 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 headerrequest
    18  
    19  import (
    20  	"net/http"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  
    26  	"k8s.io/apiserver/pkg/authentication/user"
    27  )
    28  
    29  func TestRequestHeader(t *testing.T) {
    30  	testcases := map[string]struct {
    31  		nameHeaders        []string
    32  		groupHeaders       []string
    33  		extraPrefixHeaders []string
    34  		requestHeaders     http.Header
    35  		finalHeaders       http.Header
    36  
    37  		expectedUser user.Info
    38  		expectedOk   bool
    39  	}{
    40  		"empty": {},
    41  		"user no match": {
    42  			nameHeaders: []string{"X-Remote-User"},
    43  		},
    44  		"user match": {
    45  			nameHeaders:    []string{"X-Remote-User"},
    46  			requestHeaders: http.Header{"X-Remote-User": {"Bob"}},
    47  			expectedUser: &user.DefaultInfo{
    48  				Name:   "Bob",
    49  				Groups: []string{},
    50  				Extra:  map[string][]string{},
    51  			},
    52  			expectedOk: true,
    53  		},
    54  		"user exact match": {
    55  			nameHeaders: []string{"X-Remote-User"},
    56  			requestHeaders: http.Header{
    57  				"Prefixed-X-Remote-User-With-Suffix": {"Bob"},
    58  				"X-Remote-User-With-Suffix":          {"Bob"},
    59  			},
    60  		},
    61  		"user first match": {
    62  			nameHeaders: []string{
    63  				"X-Remote-User",
    64  				"A-Second-X-Remote-User",
    65  				"Another-X-Remote-User",
    66  			},
    67  			requestHeaders: http.Header{
    68  				"X-Remote-User":          {"", "First header, second value"},
    69  				"A-Second-X-Remote-User": {"Second header, first value", "Second header, second value"},
    70  				"Another-X-Remote-User":  {"Third header, first value"}},
    71  			expectedUser: &user.DefaultInfo{
    72  				Name:   "Second header, first value",
    73  				Groups: []string{},
    74  				Extra:  map[string][]string{},
    75  			},
    76  			expectedOk: true,
    77  		},
    78  		"user case-insensitive": {
    79  			nameHeaders:    []string{"x-REMOTE-user"},             // configured headers can be case-insensitive
    80  			requestHeaders: http.Header{"X-Remote-User": {"Bob"}}, // the parsed headers are normalized by the http package
    81  			expectedUser: &user.DefaultInfo{
    82  				Name:   "Bob",
    83  				Groups: []string{},
    84  				Extra:  map[string][]string{},
    85  			},
    86  			expectedOk: true,
    87  		},
    88  
    89  		"groups none": {
    90  			nameHeaders:  []string{"X-Remote-User"},
    91  			groupHeaders: []string{"X-Remote-Group"},
    92  			requestHeaders: http.Header{
    93  				"X-Remote-User": {"Bob"},
    94  			},
    95  			expectedUser: &user.DefaultInfo{
    96  				Name:   "Bob",
    97  				Groups: []string{},
    98  				Extra:  map[string][]string{},
    99  			},
   100  			expectedOk: true,
   101  		},
   102  		"groups all matches": {
   103  			nameHeaders:  []string{"X-Remote-User"},
   104  			groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
   105  			requestHeaders: http.Header{
   106  				"X-Remote-User":    {"Bob"},
   107  				"X-Remote-Group-1": {"one-a", "one-b"},
   108  				"X-Remote-Group-2": {"two-a", "two-b"},
   109  			},
   110  			expectedUser: &user.DefaultInfo{
   111  				Name:   "Bob",
   112  				Groups: []string{"one-a", "one-b", "two-a", "two-b"},
   113  				Extra:  map[string][]string{},
   114  			},
   115  			expectedOk: true,
   116  		},
   117  		"groups case-insensitive": {
   118  			nameHeaders:  []string{"X-REMOTE-User"},
   119  			groupHeaders: []string{"X-REMOTE-Group"},
   120  			requestHeaders: http.Header{
   121  				"X-Remote-User":  {"Bob"},
   122  				"X-Remote-Group": {"Users"},
   123  			},
   124  			expectedUser: &user.DefaultInfo{
   125  				Name:   "Bob",
   126  				Groups: []string{"Users"},
   127  				Extra:  map[string][]string{},
   128  			},
   129  			expectedOk: true,
   130  		},
   131  
   132  		"extra prefix matches case-insensitive": {
   133  			nameHeaders:        []string{"X-Remote-User"},
   134  			groupHeaders:       []string{"X-Remote-Group-1", "X-Remote-Group-2"},
   135  			extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
   136  			requestHeaders: http.Header{
   137  				"X-Remote-User":         {"Bob"},
   138  				"X-Remote-Group-1":      {"one-a", "one-b"},
   139  				"X-Remote-Group-2":      {"two-a", "two-b"},
   140  				"X-Remote-extra-1-key1": {"alfa", "bravo"},
   141  				"X-Remote-Extra-1-Key2": {"charlie", "delta"},
   142  				"X-Remote-Extra-1-":     {"india", "juliet"},
   143  				"X-Remote-extra-2-":     {"kilo", "lima"},
   144  				"X-Remote-extra-2-Key1": {"echo", "foxtrot"},
   145  				"X-Remote-Extra-2-key2": {"golf", "hotel"},
   146  			},
   147  			expectedUser: &user.DefaultInfo{
   148  				Name:   "Bob",
   149  				Groups: []string{"one-a", "one-b", "two-a", "two-b"},
   150  				Extra: map[string][]string{
   151  					"key1": {"alfa", "bravo", "echo", "foxtrot"},
   152  					"key2": {"charlie", "delta", "golf", "hotel"},
   153  					"":     {"india", "juliet", "kilo", "lima"},
   154  				},
   155  			},
   156  			expectedOk: true,
   157  		},
   158  
   159  		"extra prefix matches case-insensitive with unrelated headers": {
   160  			nameHeaders:        []string{"X-Remote-User"},
   161  			groupHeaders:       []string{"X-Remote-Group-1", "X-Remote-Group-2"},
   162  			extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
   163  			requestHeaders: http.Header{
   164  				"X-Group-Remote":        {"snorlax"}, // unrelated header
   165  				"X-Group-Bear":          {"panda"},   // another unrelated header
   166  				"X-Remote-User":         {"Bob"},
   167  				"X-Remote-Group-1":      {"one-a", "one-b"},
   168  				"X-Remote-Group-2":      {"two-a", "two-b"},
   169  				"X-Remote-extra-1-key1": {"alfa", "bravo"},
   170  				"X-Remote-Extra-1-Key2": {"charlie", "delta"},
   171  				"X-Remote-Extra-1-":     {"india", "juliet"},
   172  				"X-Remote-extra-2-":     {"kilo", "lima"},
   173  				"X-Remote-extra-2-Key1": {"echo", "foxtrot"},
   174  				"X-Remote-Extra-2-key2": {"golf", "hotel"},
   175  			},
   176  			finalHeaders: http.Header{
   177  				"X-Group-Remote": {"snorlax"},
   178  				"X-Group-Bear":   {"panda"},
   179  			},
   180  			expectedUser: &user.DefaultInfo{
   181  				Name:   "Bob",
   182  				Groups: []string{"one-a", "one-b", "two-a", "two-b"},
   183  				Extra: map[string][]string{
   184  					"key1": {"alfa", "bravo", "echo", "foxtrot"},
   185  					"key2": {"charlie", "delta", "golf", "hotel"},
   186  					"":     {"india", "juliet", "kilo", "lima"},
   187  				},
   188  			},
   189  			expectedOk: true,
   190  		},
   191  
   192  		"escaped extra keys": {
   193  			nameHeaders:        []string{"X-Remote-User"},
   194  			groupHeaders:       []string{"X-Remote-Group"},
   195  			extraPrefixHeaders: []string{"X-Remote-Extra-"},
   196  			requestHeaders: http.Header{
   197  				"X-Remote-User":                                            {"Bob"},
   198  				"X-Remote-Group":                                           {"one-a", "one-b"},
   199  				"X-Remote-Extra-Alpha":                                     {"alphabetical"},
   200  				"X-Remote-Extra-Alph4num3r1c":                              {"alphanumeric"},
   201  				"X-Remote-Extra-Percent%20encoded":                         {"percent encoded"},
   202  				"X-Remote-Extra-Almost%zzpercent%xxencoded":                {"not quite percent encoded"},
   203  				"X-Remote-Extra-Example.com%2fpercent%2520encoded":         {"url with double percent encoding"},
   204  				"X-Remote-Extra-Example.com%2F%E4%BB%8A%E6%97%A5%E3%81%AF": {"url with unicode"},
   205  				"X-Remote-Extra-Abc123!#$+.-_*\\^`~|'":                     {"header key legal characters"},
   206  			},
   207  			expectedUser: &user.DefaultInfo{
   208  				Name:   "Bob",
   209  				Groups: []string{"one-a", "one-b"},
   210  				Extra: map[string][]string{
   211  					"alpha":                         {"alphabetical"},
   212  					"alph4num3r1c":                  {"alphanumeric"},
   213  					"percent encoded":               {"percent encoded"},
   214  					"almost%zzpercent%xxencoded":    {"not quite percent encoded"},
   215  					"example.com/percent%20encoded": {"url with double percent encoding"},
   216  					"example.com/今日は":               {"url with unicode"},
   217  					"abc123!#$+.-_*\\^`~|'":         {"header key legal characters"},
   218  				},
   219  			},
   220  			expectedOk: true,
   221  		},
   222  	}
   223  
   224  	for k, testcase := range testcases {
   225  		t.Run(k, func(t *testing.T) {
   226  			auth, err := New(testcase.nameHeaders, testcase.groupHeaders, testcase.extraPrefixHeaders)
   227  			if err != nil {
   228  				t.Fatal(err)
   229  			}
   230  			req := &http.Request{Header: testcase.requestHeaders}
   231  
   232  			resp, ok, _ := auth.AuthenticateRequest(req)
   233  			if testcase.expectedOk != ok {
   234  				t.Errorf("%v: expected %v, got %v", k, testcase.expectedOk, ok)
   235  			}
   236  			if !ok {
   237  				return
   238  			}
   239  			if e, a := testcase.expectedUser, resp.User; !reflect.DeepEqual(e, a) {
   240  				t.Errorf("%v: expected %#v, got %#v", k, e, a)
   241  			}
   242  
   243  			want := testcase.finalHeaders
   244  			if want == nil && testcase.requestHeaders != nil {
   245  				want = http.Header{}
   246  			}
   247  			if diff := cmp.Diff(want, testcase.requestHeaders); len(diff) > 0 {
   248  				t.Errorf("unexpected final headers (-want +got):\n%s", diff)
   249  			}
   250  		})
   251  	}
   252  }