serving_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package serving
  14. import (
  15. "crypto/tls"
  16. "crypto/x509"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "net/http"
  21. "os"
  22. "path"
  23. "strings"
  24. "testing"
  25. "k8s.io/apiserver/pkg/server"
  26. "k8s.io/apiserver/pkg/server/options"
  27. "k8s.io/cloud-provider"
  28. "k8s.io/cloud-provider/fake"
  29. cloudctrlmgrtesting "k8s.io/kubernetes/cmd/cloud-controller-manager/app/testing"
  30. kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
  31. kubectrlmgrtesting "k8s.io/kubernetes/cmd/kube-controller-manager/app/testing"
  32. kubeschedulertesting "k8s.io/kubernetes/cmd/kube-scheduler/app/testing"
  33. "k8s.io/kubernetes/test/integration/framework"
  34. )
  35. type componentTester interface {
  36. StartTestServer(t kubectrlmgrtesting.Logger, customFlags []string) (*options.SecureServingOptionsWithLoopback, *server.SecureServingInfo, *server.DeprecatedInsecureServingInfo, func(), error)
  37. }
  38. type kubeControllerManagerTester struct{}
  39. func (kubeControllerManagerTester) StartTestServer(t kubectrlmgrtesting.Logger, customFlags []string) (*options.SecureServingOptionsWithLoopback, *server.SecureServingInfo, *server.DeprecatedInsecureServingInfo, func(), error) {
  40. // avoid starting any controller loops, we're just testing serving
  41. customFlags = append([]string{"--controllers="}, customFlags...)
  42. gotResult, err := kubectrlmgrtesting.StartTestServer(t, customFlags)
  43. if err != nil {
  44. return nil, nil, nil, nil, err
  45. }
  46. return gotResult.Options.SecureServing, gotResult.Config.SecureServing, gotResult.Config.InsecureServing, gotResult.TearDownFn, err
  47. }
  48. type cloudControllerManagerTester struct{}
  49. func (cloudControllerManagerTester) StartTestServer(t kubectrlmgrtesting.Logger, customFlags []string) (*options.SecureServingOptionsWithLoopback, *server.SecureServingInfo, *server.DeprecatedInsecureServingInfo, func(), error) {
  50. gotResult, err := cloudctrlmgrtesting.StartTestServer(t, customFlags)
  51. if err != nil {
  52. return nil, nil, nil, nil, err
  53. }
  54. return gotResult.Options.SecureServing, gotResult.Config.SecureServing, gotResult.Config.InsecureServing, gotResult.TearDownFn, err
  55. }
  56. type kubeSchedulerTester struct{}
  57. func (kubeSchedulerTester) StartTestServer(t kubectrlmgrtesting.Logger, customFlags []string) (*options.SecureServingOptionsWithLoopback, *server.SecureServingInfo, *server.DeprecatedInsecureServingInfo, func(), error) {
  58. gotResult, err := kubeschedulertesting.StartTestServer(t, customFlags)
  59. if err != nil {
  60. return nil, nil, nil, nil, err
  61. }
  62. return gotResult.Options.SecureServing, gotResult.Config.SecureServing, gotResult.Config.InsecureServing, gotResult.TearDownFn, err
  63. }
  64. func TestComponentSecureServingAndAuth(t *testing.T) {
  65. if !cloudprovider.IsCloudProvider("fake") {
  66. cloudprovider.RegisterCloudProvider("fake", fakeCloudProviderFactory)
  67. }
  68. // Insulate this test from picking up in-cluster config when run inside a pod
  69. // We can't assume we have permissions to write to /var/run/secrets/... from a unit test to mock in-cluster config for testing
  70. originalHost := os.Getenv("KUBERNETES_SERVICE_HOST")
  71. if len(originalHost) > 0 {
  72. os.Setenv("KUBERNETES_SERVICE_HOST", "")
  73. defer os.Setenv("KUBERNETES_SERVICE_HOST", originalHost)
  74. }
  75. // authenticate to apiserver via bearer token
  76. token := "flwqkenfjasasdfmwerasd" // Fake token for testing.
  77. tokenFile, err := ioutil.TempFile("", "kubeconfig")
  78. if err != nil {
  79. t.Fatal(err)
  80. }
  81. tokenFile.WriteString(fmt.Sprintf(`
  82. %s,system:kube-controller-manager,system:kube-controller-manager,""
  83. `, token))
  84. tokenFile.Close()
  85. // start apiserver
  86. server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{
  87. "--token-auth-file", tokenFile.Name(),
  88. "--authorization-mode", "RBAC",
  89. }, framework.SharedEtcd())
  90. defer server.TearDownFn()
  91. // create kubeconfig for the apiserver
  92. apiserverConfig, err := ioutil.TempFile("", "kubeconfig")
  93. if err != nil {
  94. t.Fatal(err)
  95. }
  96. apiserverConfig.WriteString(fmt.Sprintf(`
  97. apiVersion: v1
  98. kind: Config
  99. clusters:
  100. - cluster:
  101. server: %s
  102. certificate-authority: %s
  103. name: integration
  104. contexts:
  105. - context:
  106. cluster: integration
  107. user: controller-manager
  108. name: default-context
  109. current-context: default-context
  110. users:
  111. - name: controller-manager
  112. user:
  113. token: %s
  114. `, server.ClientConfig.Host, server.ServerOpts.SecureServing.ServerCert.CertKey.CertFile, token))
  115. apiserverConfig.Close()
  116. // create BROKEN kubeconfig for the apiserver
  117. brokenApiserverConfig, err := ioutil.TempFile("", "kubeconfig")
  118. if err != nil {
  119. t.Fatal(err)
  120. }
  121. brokenApiserverConfig.WriteString(fmt.Sprintf(`
  122. apiVersion: v1
  123. kind: Config
  124. clusters:
  125. - cluster:
  126. server: %s
  127. certificate-authority: %s
  128. name: integration
  129. contexts:
  130. - context:
  131. cluster: integration
  132. user: controller-manager
  133. name: default-context
  134. current-context: default-context
  135. users:
  136. - name: controller-manager
  137. user:
  138. token: WRONGTOKEN
  139. `, server.ClientConfig.Host, server.ServerOpts.SecureServing.ServerCert.CertKey.CertFile))
  140. brokenApiserverConfig.Close()
  141. tests := []struct {
  142. name string
  143. tester componentTester
  144. extraFlags []string
  145. }{
  146. {"kube-controller-manager", kubeControllerManagerTester{}, nil},
  147. {"cloud-controller-manager", cloudControllerManagerTester{}, []string{"--cloud-provider=fake"}},
  148. {"kube-scheduler", kubeSchedulerTester{}, nil},
  149. }
  150. for _, tt := range tests {
  151. t.Run(tt.name, func(t *testing.T) {
  152. testComponent(t, tt.tester, apiserverConfig.Name(), brokenApiserverConfig.Name(), token, tt.extraFlags)
  153. })
  154. }
  155. }
  156. func testComponent(t *testing.T, tester componentTester, kubeconfig, brokenKubeconfig, token string, extraFlags []string) {
  157. tests := []struct {
  158. name string
  159. flags []string
  160. path string
  161. anonymous bool // to use the token or not
  162. wantErr bool
  163. wantSecureCode, wantInsecureCode *int
  164. }{
  165. {"no-flags", nil, "/healthz", false, true, nil, nil},
  166. {"insecurely /healthz", []string{
  167. "--secure-port=0",
  168. "--port=10253",
  169. "--kubeconfig", kubeconfig,
  170. "--leader-elect=false",
  171. }, "/healthz", true, false, nil, intPtr(http.StatusOK)},
  172. {"insecurely /metrics", []string{
  173. "--secure-port=0",
  174. "--port=10253",
  175. "--kubeconfig", kubeconfig,
  176. "--leader-elect=false",
  177. }, "/metrics", true, false, nil, intPtr(http.StatusOK)},
  178. {"/healthz without authn/authz", []string{
  179. "--port=0",
  180. "--kubeconfig", kubeconfig,
  181. "--leader-elect=false",
  182. }, "/healthz", true, false, intPtr(http.StatusOK), nil},
  183. {"/metrics without authn/authz", []string{
  184. "--kubeconfig", kubeconfig,
  185. "--leader-elect=false",
  186. "--port=10253",
  187. }, "/metrics", true, false, intPtr(http.StatusForbidden), intPtr(http.StatusOK)},
  188. {"authorization skipped for /healthz with authn/authz", []string{
  189. "--port=0",
  190. "--authentication-kubeconfig", kubeconfig,
  191. "--authorization-kubeconfig", kubeconfig,
  192. "--kubeconfig", kubeconfig,
  193. "--leader-elect=false",
  194. }, "/healthz", false, false, intPtr(http.StatusOK), nil},
  195. {"authorization skipped for /healthz with BROKEN authn/authz", []string{
  196. "--port=0",
  197. "--authentication-skip-lookup", // to survive unaccessible extensions-apiserver-authentication configmap
  198. "--authentication-kubeconfig", brokenKubeconfig,
  199. "--authorization-kubeconfig", brokenKubeconfig,
  200. "--kubeconfig", kubeconfig,
  201. "--leader-elect=false",
  202. }, "/healthz", false, false, intPtr(http.StatusOK), nil},
  203. {"not authorized /metrics", []string{
  204. "--port=0",
  205. "--authentication-kubeconfig", kubeconfig,
  206. "--authorization-kubeconfig", kubeconfig,
  207. "--kubeconfig", kubeconfig,
  208. "--leader-elect=false",
  209. }, "/metrics", false, false, intPtr(http.StatusForbidden), nil},
  210. {"not authorized /metrics with BROKEN authn/authz", []string{
  211. "--port=10253",
  212. "--authentication-kubeconfig", kubeconfig,
  213. "--authorization-kubeconfig", brokenKubeconfig,
  214. "--kubeconfig", kubeconfig,
  215. "--leader-elect=false",
  216. }, "/metrics", false, false, intPtr(http.StatusInternalServerError), intPtr(http.StatusOK)},
  217. {"always-allowed /metrics with BROKEN authn/authz", []string{
  218. "--port=0",
  219. "--authentication-skip-lookup", // to survive unaccessible extensions-apiserver-authentication configmap
  220. "--authentication-kubeconfig", kubeconfig,
  221. "--authorization-kubeconfig", kubeconfig,
  222. "--authorization-always-allow-paths", "/healthz,/metrics",
  223. "--kubeconfig", kubeconfig,
  224. "--leader-elect=false",
  225. }, "/metrics", false, false, intPtr(http.StatusOK), nil},
  226. }
  227. for _, tt := range tests {
  228. t.Run(tt.name, func(t *testing.T) {
  229. secureOptions, secureInfo, insecureInfo, tearDownFn, err := tester.StartTestServer(t, append(append([]string{}, tt.flags...), extraFlags...))
  230. if tearDownFn != nil {
  231. defer tearDownFn()
  232. }
  233. if (err != nil) != tt.wantErr {
  234. t.Fatalf("StartTestServer() error = %v, wantErr %v", err, tt.wantErr)
  235. }
  236. if err != nil {
  237. return
  238. }
  239. if want, got := tt.wantSecureCode != nil, secureInfo != nil; want != got {
  240. t.Errorf("SecureServing enabled: expected=%v got=%v", want, got)
  241. } else if want {
  242. url := fmt.Sprintf("https://%s%s", secureInfo.Listener.Addr().String(), tt.path)
  243. url = strings.Replace(url, "[::]", "127.0.0.1", -1) // switch to IPv4 because the self-signed cert does not support [::]
  244. // read self-signed server cert disk
  245. pool := x509.NewCertPool()
  246. serverCertPath := path.Join(secureOptions.ServerCert.CertDirectory, secureOptions.ServerCert.PairName+".crt")
  247. serverCert, err := ioutil.ReadFile(serverCertPath)
  248. if err != nil {
  249. t.Fatalf("Failed to read component server cert %q: %v", serverCertPath, err)
  250. }
  251. pool.AppendCertsFromPEM(serverCert)
  252. tr := &http.Transport{
  253. TLSClientConfig: &tls.Config{
  254. RootCAs: pool,
  255. },
  256. }
  257. client := &http.Client{Transport: tr}
  258. req, err := http.NewRequest("GET", url, nil)
  259. if err != nil {
  260. t.Fatal(err)
  261. }
  262. if !tt.anonymous {
  263. req.Header.Add("Authorization", fmt.Sprintf("Token %s", token))
  264. }
  265. r, err := client.Do(req)
  266. if err != nil {
  267. t.Fatalf("failed to GET %s from component: %v", tt.path, err)
  268. }
  269. body, err := ioutil.ReadAll(r.Body)
  270. if err != nil {
  271. t.Fatalf("failed to read response body: %v", err)
  272. }
  273. defer r.Body.Close()
  274. if got, expected := r.StatusCode, *tt.wantSecureCode; got != expected {
  275. t.Fatalf("expected http %d at %s of component, got: %d %q", expected, tt.path, got, string(body))
  276. }
  277. }
  278. if want, got := tt.wantInsecureCode != nil, insecureInfo != nil; want != got {
  279. t.Errorf("InsecureServing enabled: expected=%v got=%v", want, got)
  280. } else if want {
  281. url := fmt.Sprintf("http://%s%s", insecureInfo.Listener.Addr().String(), tt.path)
  282. r, err := http.Get(url)
  283. if err != nil {
  284. t.Fatalf("failed to GET %s from component: %v", tt.path, err)
  285. }
  286. body, err := ioutil.ReadAll(r.Body)
  287. if err != nil {
  288. t.Fatalf("failed to read response body: %v", err)
  289. }
  290. defer r.Body.Close()
  291. if got, expected := r.StatusCode, *tt.wantInsecureCode; got != expected {
  292. t.Fatalf("expected http %d at %s of component, got: %d %q", expected, tt.path, got, string(body))
  293. }
  294. }
  295. })
  296. }
  297. }
  298. func intPtr(x int) *int {
  299. return &x
  300. }
  301. func fakeCloudProviderFactory(io.Reader) (cloudprovider.Interface, error) {
  302. return &fake.Cloud{}, nil
  303. }