1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package server 18 19import ( 20 "fmt" 21 "io/ioutil" 22 "net" 23 "net/http" 24 "net/http/httptest" 25 "net/http/httputil" 26 "reflect" 27 "testing" 28 "time" 29 30 "github.com/google/go-cmp/cmp" 31 "k8s.io/apimachinery/pkg/util/json" 32 "k8s.io/apimachinery/pkg/util/sets" 33 "k8s.io/apimachinery/pkg/util/waitgroup" 34 auditinternal "k8s.io/apiserver/pkg/apis/audit" 35 "k8s.io/apiserver/pkg/audit" 36 "k8s.io/apiserver/pkg/audit/policy" 37 "k8s.io/apiserver/pkg/authentication/authenticator" 38 "k8s.io/apiserver/pkg/authentication/user" 39 "k8s.io/apiserver/pkg/endpoints/request" 40 "k8s.io/apiserver/pkg/server/healthz" 41 "k8s.io/client-go/informers" 42 "k8s.io/client-go/kubernetes/fake" 43 "k8s.io/client-go/rest" 44) 45 46func TestAuthorizeClientBearerTokenNoops(t *testing.T) { 47 // All of these should do nothing (not panic, no side-effects) 48 cfgGens := []func() *rest.Config{ 49 func() *rest.Config { return nil }, 50 func() *rest.Config { return &rest.Config{} }, 51 func() *rest.Config { return &rest.Config{BearerToken: "mu"} }, 52 } 53 authcGens := []func() *AuthenticationInfo{ 54 func() *AuthenticationInfo { return nil }, 55 func() *AuthenticationInfo { return &AuthenticationInfo{} }, 56 } 57 authzGens := []func() *AuthorizationInfo{ 58 func() *AuthorizationInfo { return nil }, 59 func() *AuthorizationInfo { return &AuthorizationInfo{} }, 60 } 61 for _, cfgGen := range cfgGens { 62 for _, authcGen := range authcGens { 63 for _, authzGen := range authzGens { 64 pConfig := cfgGen() 65 pAuthc := authcGen() 66 pAuthz := authzGen() 67 AuthorizeClientBearerToken(pConfig, pAuthc, pAuthz) 68 if before, after := authcGen(), pAuthc; !reflect.DeepEqual(before, after) { 69 t.Errorf("AuthorizeClientBearerToken(%v, %#+v, %v) changed %#+v", pConfig, pAuthc, pAuthz, *before) 70 } 71 if before, after := authzGen(), pAuthz; !reflect.DeepEqual(before, after) { 72 t.Errorf("AuthorizeClientBearerToken(%v, %v, %#+v) changed %#+v", pConfig, pAuthc, pAuthz, *before) 73 } 74 } 75 } 76 } 77} 78 79func TestNewWithDelegate(t *testing.T) { 80 delegateConfig := NewConfig(codecs) 81 delegateConfig.ExternalAddress = "192.168.10.4:443" 82 delegateConfig.PublicAddress = net.ParseIP("192.168.10.4") 83 delegateConfig.LegacyAPIGroupPrefixes = sets.NewString("/api") 84 delegateConfig.LoopbackClientConfig = &rest.Config{} 85 clientset := fake.NewSimpleClientset() 86 if clientset == nil { 87 t.Fatal("unable to create fake client set") 88 } 89 90 delegateConfig.HealthzChecks = append(delegateConfig.HealthzChecks, healthz.NamedCheck("delegate-health", func(r *http.Request) error { 91 return fmt.Errorf("delegate failed healthcheck") 92 })) 93 94 sharedInformers := informers.NewSharedInformerFactory(clientset, delegateConfig.LoopbackClientConfig.Timeout) 95 delegateServer, err := delegateConfig.Complete(sharedInformers).New("test", NewEmptyDelegate()) 96 if err != nil { 97 t.Fatal(err) 98 } 99 delegateServer.Handler.NonGoRestfulMux.HandleFunc("/foo", func(w http.ResponseWriter, _ *http.Request) { 100 w.WriteHeader(http.StatusForbidden) 101 }) 102 103 delegatePostStartHookChan := make(chan struct{}) 104 delegateServer.AddPostStartHookOrDie("delegate-post-start-hook", func(context PostStartHookContext) error { 105 defer close(delegatePostStartHookChan) 106 return nil 107 }) 108 109 // this wires up swagger 110 delegateServer.PrepareRun() 111 112 wrappingConfig := NewConfig(codecs) 113 wrappingConfig.ExternalAddress = "192.168.10.4:443" 114 wrappingConfig.PublicAddress = net.ParseIP("192.168.10.4") 115 wrappingConfig.LegacyAPIGroupPrefixes = sets.NewString("/api") 116 wrappingConfig.LoopbackClientConfig = &rest.Config{} 117 118 wrappingConfig.HealthzChecks = append(wrappingConfig.HealthzChecks, healthz.NamedCheck("wrapping-health", func(r *http.Request) error { 119 return fmt.Errorf("wrapping failed healthcheck") 120 })) 121 122 sharedInformers = informers.NewSharedInformerFactory(clientset, wrappingConfig.LoopbackClientConfig.Timeout) 123 wrappingServer, err := wrappingConfig.Complete(sharedInformers).New("test", delegateServer) 124 if err != nil { 125 t.Fatal(err) 126 } 127 wrappingServer.Handler.NonGoRestfulMux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { 128 w.WriteHeader(http.StatusUnauthorized) 129 }) 130 131 wrappingPostStartHookChan := make(chan struct{}) 132 wrappingServer.AddPostStartHookOrDie("wrapping-post-start-hook", func(context PostStartHookContext) error { 133 defer close(wrappingPostStartHookChan) 134 return nil 135 }) 136 137 stopCh := make(chan struct{}) 138 defer close(stopCh) 139 wrappingServer.PrepareRun() 140 wrappingServer.RunPostStartHooks(stopCh) 141 142 server := httptest.NewServer(wrappingServer.Handler) 143 defer server.Close() 144 145 // Wait for the hooks to finish before checking the response 146 <-delegatePostStartHookChan 147 <-wrappingPostStartHookChan 148 expectedPaths := []string{ 149 "/apis", 150 "/bar", 151 "/foo", 152 "/healthz", 153 "/healthz/delegate-health", 154 "/healthz/log", 155 "/healthz/ping", 156 "/healthz/poststarthook/delegate-post-start-hook", 157 "/healthz/poststarthook/generic-apiserver-start-informers", 158 "/healthz/poststarthook/max-in-flight-filter", 159 "/healthz/poststarthook/wrapping-post-start-hook", 160 "/healthz/wrapping-health", 161 "/livez", 162 "/livez/delegate-health", 163 "/livez/log", 164 "/livez/ping", 165 "/livez/poststarthook/delegate-post-start-hook", 166 "/livez/poststarthook/generic-apiserver-start-informers", 167 "/livez/poststarthook/max-in-flight-filter", 168 "/livez/poststarthook/wrapping-post-start-hook", 169 "/metrics", 170 "/readyz", 171 "/readyz/delegate-health", 172 "/readyz/informer-sync", 173 "/readyz/log", 174 "/readyz/ping", 175 "/readyz/poststarthook/delegate-post-start-hook", 176 "/readyz/poststarthook/generic-apiserver-start-informers", 177 "/readyz/poststarthook/max-in-flight-filter", 178 "/readyz/poststarthook/wrapping-post-start-hook", 179 "/readyz/shutdown", 180 } 181 checkExpectedPathsAtRoot(server.URL, expectedPaths, t) 182 checkPath(server.URL+"/healthz", http.StatusInternalServerError, `[+]ping ok 183[+]log ok 184[-]wrapping-health failed: reason withheld 185[-]delegate-health failed: reason withheld 186[+]poststarthook/generic-apiserver-start-informers ok 187[+]poststarthook/max-in-flight-filter ok 188[+]poststarthook/delegate-post-start-hook ok 189[+]poststarthook/wrapping-post-start-hook ok 190healthz check failed 191`, t) 192 193 checkPath(server.URL+"/healthz/delegate-health", http.StatusInternalServerError, `internal server error: delegate failed healthcheck 194`, t) 195 checkPath(server.URL+"/healthz/wrapping-health", http.StatusInternalServerError, `internal server error: wrapping failed healthcheck 196`, t) 197 checkPath(server.URL+"/healthz/poststarthook/delegate-post-start-hook", http.StatusOK, `ok`, t) 198 checkPath(server.URL+"/healthz/poststarthook/wrapping-post-start-hook", http.StatusOK, `ok`, t) 199 checkPath(server.URL+"/foo", http.StatusForbidden, ``, t) 200 checkPath(server.URL+"/bar", http.StatusUnauthorized, ``, t) 201} 202 203func checkPath(url string, expectedStatusCode int, expectedBody string, t *testing.T) { 204 t.Run(url, func(t *testing.T) { 205 resp, err := http.Get(url) 206 if err != nil { 207 t.Fatal(err) 208 } 209 dump, _ := httputil.DumpResponse(resp, true) 210 t.Log(string(dump)) 211 212 body, err := ioutil.ReadAll(resp.Body) 213 if err != nil { 214 t.Fatal(err) 215 } 216 217 if e, a := expectedBody, string(body); e != a { 218 t.Errorf("%q expected %v, got %v", url, e, a) 219 } 220 if e, a := expectedStatusCode, resp.StatusCode; e != a { 221 t.Errorf("%q expected %v, got %v", url, e, a) 222 } 223 }) 224} 225 226func checkExpectedPathsAtRoot(url string, expectedPaths []string, t *testing.T) { 227 t.Run(url, func(t *testing.T) { 228 resp, err := http.Get(url) 229 if err != nil { 230 t.Fatal(err) 231 } 232 dump, _ := httputil.DumpResponse(resp, true) 233 t.Log(string(dump)) 234 235 body, err := ioutil.ReadAll(resp.Body) 236 if err != nil { 237 t.Fatal(err) 238 } 239 var result map[string]interface{} 240 json.Unmarshal(body, &result) 241 paths, ok := result["paths"].([]interface{}) 242 if !ok { 243 t.Errorf("paths not found") 244 } 245 pathset := sets.NewString() 246 for _, p := range paths { 247 pathset.Insert(p.(string)) 248 } 249 expectedset := sets.NewString(expectedPaths...) 250 for p := range pathset.Difference(expectedset) { 251 t.Errorf("Got %v path, which we did not expect", p) 252 } 253 for p := range expectedset.Difference(pathset) { 254 t.Errorf(" Expected %v path which we did not get", p) 255 } 256 }) 257} 258 259func TestAuthenticationAuditAnnotationsDefaultChain(t *testing.T) { 260 authn := authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) { 261 // confirm that we can set an audit annotation in a handler before WithAudit 262 audit.AddAuditAnnotation(req.Context(), "pandas", "are awesome") 263 264 // confirm that trying to use the audit event directly would never work 265 if ae := request.AuditEventFrom(req.Context()); ae != nil { 266 t.Errorf("expected nil audit event, got %v", ae) 267 } 268 269 return &authenticator.Response{User: &user.DefaultInfo{}}, true, nil 270 }) 271 backend := &testBackend{} 272 c := &Config{ 273 Authentication: AuthenticationInfo{Authenticator: authn}, 274 AuditBackend: backend, 275 AuditPolicyChecker: policy.FakeChecker(auditinternal.LevelMetadata, nil), 276 277 // avoid nil panics 278 HandlerChainWaitGroup: &waitgroup.SafeWaitGroup{}, 279 RequestInfoResolver: &request.RequestInfoFactory{}, 280 RequestTimeout: 10 * time.Second, 281 LongRunningFunc: func(_ *http.Request, _ *request.RequestInfo) bool { return false }, 282 } 283 284 h := DefaultBuildHandlerChain(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 285 // confirm this is a no-op 286 if r.Context() != audit.WithAuditAnnotations(r.Context()) { 287 t.Error("unexpected double wrapping of context") 288 } 289 290 // confirm that we have an audit event 291 ae := request.AuditEventFrom(r.Context()) 292 if ae == nil { 293 t.Error("unexpected nil audit event") 294 } 295 296 // confirm that the direct way of setting audit annotations later in the chain works as expected 297 audit.LogAnnotation(ae, "snorlax", "is cool too") 298 299 // confirm that the indirect way of setting audit annotations later in the chain also works 300 audit.AddAuditAnnotation(r.Context(), "dogs", "are okay") 301 302 if _, err := w.Write([]byte("done")); err != nil { 303 t.Errorf("failed to write response: %v", err) 304 } 305 }), c) 306 w := httptest.NewRecorder() 307 308 h.ServeHTTP(w, httptest.NewRequest("GET", "https://ignored.com", nil)) 309 310 r := w.Result() 311 if ok := r.StatusCode == http.StatusOK && w.Body.String() == "done" && len(r.Header.Get(auditinternal.HeaderAuditID)) > 0; !ok { 312 t.Errorf("invalid response: %#v", w) 313 } 314 if len(backend.events) == 0 { 315 t.Error("expected audit events, got none") 316 } 317 // these should all be the same because the handler chain mutates the event in place 318 want := map[string]string{"pandas": "are awesome", "snorlax": "is cool too", "dogs": "are okay"} 319 for _, event := range backend.events { 320 if event.Stage != auditinternal.StageResponseComplete { 321 t.Errorf("expected event stage to be complete, got: %s", event.Stage) 322 } 323 if diff := cmp.Diff(want, event.Annotations); diff != "" { 324 t.Errorf("event has unexpected annotations (-want +got): %s", diff) 325 } 326 } 327} 328 329type testBackend struct { 330 events []*auditinternal.Event 331 332 audit.Backend // nil panic if anything other than ProcessEvents called 333} 334 335func (b *testBackend) ProcessEvents(events ...*auditinternal.Event) bool { 336 b.events = append(b.events, events...) 337 return true 338} 339