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