token_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. /*
  2. Copyright 2016 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 cmd
  14. import (
  15. "bytes"
  16. "io/ioutil"
  17. "os"
  18. "path/filepath"
  19. "regexp"
  20. "testing"
  21. "k8s.io/api/core/v1"
  22. apierrors "k8s.io/apimachinery/pkg/api/errors"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/runtime"
  25. "k8s.io/client-go/kubernetes/fake"
  26. core "k8s.io/client-go/testing"
  27. "k8s.io/client-go/tools/clientcmd"
  28. kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
  29. outputapischeme "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/scheme"
  30. outputapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha1"
  31. "k8s.io/kubernetes/cmd/kubeadm/app/util/output"
  32. )
  33. const (
  34. tokenExpectedRegex = "^\\S{6}\\.\\S{16}\n$"
  35. testConfigToken = `apiVersion: v1
  36. clusters:
  37. - cluster:
  38. certificate-authority-data:
  39. server: localhost:8000
  40. name: prod
  41. contexts:
  42. - context:
  43. cluster: prod
  44. namespace: default
  45. user: default-service-account
  46. name: default
  47. current-context: default
  48. kind: Config
  49. preferences: {}
  50. users:
  51. - name: kubernetes-admin
  52. user:
  53. client-certificate-data:
  54. client-key-data:
  55. `
  56. )
  57. func TestRunGenerateToken(t *testing.T) {
  58. var buf bytes.Buffer
  59. err := RunGenerateToken(&buf)
  60. if err != nil {
  61. t.Errorf("RunGenerateToken returned an error: %v", err)
  62. }
  63. output := buf.String()
  64. matched, err := regexp.MatchString(tokenExpectedRegex, output)
  65. if err != nil {
  66. t.Fatalf("Encountered an error while trying to match RunGenerateToken's output: %v", err)
  67. }
  68. if !matched {
  69. t.Errorf("RunGenerateToken's output did not match expected regex; wanted: [%s], got: [%s]", tokenExpectedRegex, output)
  70. }
  71. }
  72. func TestRunCreateToken(t *testing.T) {
  73. var buf bytes.Buffer
  74. fakeClient := &fake.Clientset{}
  75. fakeClient.AddReactor("get", "secrets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  76. return true, nil, apierrors.NewNotFound(v1.Resource("secrets"), "foo")
  77. })
  78. testCases := []struct {
  79. name string
  80. token string
  81. usages []string
  82. extraGroups []string
  83. printJoin bool
  84. expectedError bool
  85. }{
  86. {
  87. name: "valid: empty token",
  88. token: "",
  89. usages: []string{"signing", "authentication"},
  90. extraGroups: []string{"system:bootstrappers:foo"},
  91. expectedError: false,
  92. },
  93. {
  94. name: "valid: non-empty token",
  95. token: "abcdef.1234567890123456",
  96. usages: []string{"signing", "authentication"},
  97. extraGroups: []string{"system:bootstrappers:foo"},
  98. expectedError: false,
  99. },
  100. {
  101. name: "valid: no extraGroups",
  102. token: "abcdef.1234567890123456",
  103. usages: []string{"signing", "authentication"},
  104. extraGroups: []string{},
  105. expectedError: false,
  106. },
  107. {
  108. name: "invalid: incorrect extraGroups",
  109. token: "abcdef.1234567890123456",
  110. usages: []string{"signing", "authentication"},
  111. extraGroups: []string{"foo"},
  112. expectedError: true,
  113. },
  114. {
  115. name: "invalid: specifying --groups when --usages doesn't include authentication",
  116. token: "abcdef.1234567890123456",
  117. usages: []string{"signing"},
  118. extraGroups: []string{"foo"},
  119. expectedError: true,
  120. },
  121. {
  122. name: "invalid: partially incorrect usages",
  123. token: "abcdef.1234567890123456",
  124. usages: []string{"foo", "authentication"},
  125. extraGroups: []string{"system:bootstrappers:foo"},
  126. expectedError: true,
  127. },
  128. {
  129. name: "invalid: all incorrect usages",
  130. token: "abcdef.1234567890123456",
  131. usages: []string{"foo", "bar"},
  132. extraGroups: []string{"system:bootstrappers:foo"},
  133. expectedError: true,
  134. },
  135. {
  136. name: "invalid: print join command",
  137. token: "",
  138. usages: []string{"signing", "authentication"},
  139. extraGroups: []string{"system:bootstrappers:foo"},
  140. printJoin: true,
  141. expectedError: true,
  142. },
  143. }
  144. for _, tc := range testCases {
  145. t.Run(tc.name, func(t *testing.T) {
  146. bts, err := kubeadmapiv1beta2.NewBootstrapTokenString(tc.token)
  147. if err != nil && len(tc.token) != 0 { // if tc.token is "" it's okay as it will be generated later at runtime
  148. t.Fatalf("token couldn't be parsed for testing: %v", err)
  149. }
  150. cfg := &kubeadmapiv1beta2.InitConfiguration{
  151. BootstrapTokens: []kubeadmapiv1beta2.BootstrapToken{
  152. {
  153. Token: bts,
  154. TTL: &metav1.Duration{Duration: 0},
  155. Usages: tc.usages,
  156. Groups: tc.extraGroups,
  157. },
  158. },
  159. }
  160. err = RunCreateToken(&buf, fakeClient, "", cfg, tc.printJoin, "", "")
  161. if tc.expectedError && err == nil {
  162. t.Error("unexpected success")
  163. } else if !tc.expectedError && err != nil {
  164. t.Errorf("unexpected error: %v", err)
  165. }
  166. })
  167. }
  168. }
  169. func TestNewCmdTokenGenerate(t *testing.T) {
  170. var buf bytes.Buffer
  171. args := []string{}
  172. cmd := NewCmdTokenGenerate(&buf)
  173. cmd.SetArgs(args)
  174. if err := cmd.Execute(); err != nil {
  175. t.Errorf("Cannot execute token command: %v", err)
  176. }
  177. }
  178. func TestNewCmdToken(t *testing.T) {
  179. var buf, bufErr bytes.Buffer
  180. testConfigTokenFile := "test-config-file"
  181. tmpDir, err := ioutil.TempDir("", "kubeadm-token-test")
  182. if err != nil {
  183. t.Errorf("Unable to create temporary directory: %v", err)
  184. }
  185. defer os.RemoveAll(tmpDir)
  186. fullPath := filepath.Join(tmpDir, testConfigTokenFile)
  187. f, err := os.Create(fullPath)
  188. if err != nil {
  189. t.Errorf("Unable to create test file %q: %v", fullPath, err)
  190. }
  191. defer f.Close()
  192. testCases := []struct {
  193. name string
  194. args []string
  195. configToWrite string
  196. kubeConfigEnv string
  197. expectedError bool
  198. }{
  199. {
  200. name: "valid: generate",
  201. args: []string{"generate"},
  202. configToWrite: "",
  203. expectedError: false,
  204. },
  205. {
  206. name: "valid: delete from --kubeconfig",
  207. args: []string{"delete", "abcdef.1234567890123456", "--dry-run", "--kubeconfig=" + fullPath},
  208. configToWrite: testConfigToken,
  209. expectedError: false,
  210. },
  211. {
  212. name: "valid: delete from " + clientcmd.RecommendedConfigPathEnvVar,
  213. args: []string{"delete", "abcdef.1234567890123456", "--dry-run"},
  214. configToWrite: testConfigToken,
  215. kubeConfigEnv: fullPath,
  216. expectedError: false,
  217. },
  218. }
  219. for _, tc := range testCases {
  220. t.Run(tc.name, func(t *testing.T) {
  221. // the command is created for each test so that the kubeConfigFile
  222. // variable in NewCmdToken() is reset.
  223. cmd := NewCmdToken(&buf, &bufErr)
  224. if _, err = f.WriteString(tc.configToWrite); err != nil {
  225. t.Errorf("Unable to write test file %q: %v", fullPath, err)
  226. }
  227. // store the current value of the environment variable.
  228. storedEnv := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
  229. if tc.kubeConfigEnv != "" {
  230. os.Setenv(clientcmd.RecommendedConfigPathEnvVar, tc.kubeConfigEnv)
  231. }
  232. cmd.SetArgs(tc.args)
  233. err := cmd.Execute()
  234. if (err != nil) != tc.expectedError {
  235. t.Errorf("Test case %q: NewCmdToken expected error: %v, saw: %v", tc.name, tc.expectedError, (err != nil))
  236. }
  237. // restore the environment variable.
  238. os.Setenv(clientcmd.RecommendedConfigPathEnvVar, storedEnv)
  239. })
  240. }
  241. }
  242. func TestGetClientset(t *testing.T) {
  243. testConfigTokenFile := "test-config-file"
  244. tmpDir, err := ioutil.TempDir("", "kubeadm-token-test")
  245. if err != nil {
  246. t.Errorf("Unable to create temporary directory: %v", err)
  247. }
  248. defer os.RemoveAll(tmpDir)
  249. fullPath := filepath.Join(tmpDir, testConfigTokenFile)
  250. // test dryRun = false on a non-exisiting file
  251. if _, err = getClientset(fullPath, false); err == nil {
  252. t.Errorf("getClientset(); dry-run: false; did no fail for test file %q: %v", fullPath, err)
  253. }
  254. // test dryRun = true on a non-exisiting file
  255. if _, err = getClientset(fullPath, true); err == nil {
  256. t.Errorf("getClientset(); dry-run: true; did no fail for test file %q: %v", fullPath, err)
  257. }
  258. f, err := os.Create(fullPath)
  259. if err != nil {
  260. t.Errorf("Unable to create test file %q: %v", fullPath, err)
  261. }
  262. defer f.Close()
  263. if _, err = f.WriteString(testConfigToken); err != nil {
  264. t.Errorf("Unable to write test file %q: %v", fullPath, err)
  265. }
  266. // test dryRun = true on an exisiting file
  267. if _, err = getClientset(fullPath, true); err != nil {
  268. t.Errorf("getClientset(); dry-run: true; failed for test file %q: %v", fullPath, err)
  269. }
  270. }
  271. func TestRunDeleteTokens(t *testing.T) {
  272. var buf bytes.Buffer
  273. tmpDir, err := ioutil.TempDir("", "kubeadm-token-test")
  274. if err != nil {
  275. t.Errorf("Unable to create temporary directory: %v", err)
  276. }
  277. defer os.RemoveAll(tmpDir)
  278. fullPath := filepath.Join(tmpDir, "test-config-file")
  279. f, err := os.Create(fullPath)
  280. if err != nil {
  281. t.Errorf("Unable to create test file %q: %v", fullPath, err)
  282. }
  283. defer f.Close()
  284. if _, err = f.WriteString(testConfigToken); err != nil {
  285. t.Errorf("Unable to write test file %q: %v", fullPath, err)
  286. }
  287. client, err := getClientset(fullPath, true)
  288. if err != nil {
  289. t.Errorf("Unable to run getClientset() for test file %q: %v", fullPath, err)
  290. }
  291. // test valid; should not fail
  292. // for some reason Secrets().Delete() does not fail even for this dummy config
  293. if err = RunDeleteTokens(&buf, client, []string{"abcdef.1234567890123456", "abcdef.2345678901234567"}); err != nil {
  294. t.Errorf("RunDeleteToken() failed for a valid token: %v", err)
  295. }
  296. // test invalid token; should fail
  297. if err = RunDeleteTokens(&buf, client, []string{"invalid-token"}); err == nil {
  298. t.Errorf("RunDeleteToken() succeeded for an invalid token: %v", err)
  299. }
  300. }
  301. func TestTokenOutput(t *testing.T) {
  302. testCases := []struct {
  303. name string
  304. id string
  305. secret string
  306. description string
  307. usages []string
  308. extraGroups []string
  309. outputFormat string
  310. expected string
  311. }{
  312. {
  313. name: "JSON output",
  314. id: "abcdef",
  315. secret: "1234567890123456",
  316. description: "valid bootstrap tooken",
  317. usages: []string{"signing", "authentication"},
  318. extraGroups: []string{"system:bootstrappers:kubeadm:default-node-token"},
  319. outputFormat: "json",
  320. expected: `{
  321. "kind": "BootstrapToken",
  322. "apiVersion": "output.kubeadm.k8s.io/v1alpha1",
  323. "token": "abcdef.1234567890123456",
  324. "description": "valid bootstrap tooken",
  325. "usages": [
  326. "signing",
  327. "authentication"
  328. ],
  329. "groups": [
  330. "system:bootstrappers:kubeadm:default-node-token"
  331. ]
  332. }
  333. `,
  334. },
  335. {
  336. name: "YAML output",
  337. id: "abcdef",
  338. secret: "1234567890123456",
  339. description: "valid bootstrap tooken",
  340. usages: []string{"signing", "authentication"},
  341. extraGroups: []string{"system:bootstrappers:kubeadm:default-node-token"},
  342. outputFormat: "yaml",
  343. expected: `apiVersion: output.kubeadm.k8s.io/v1alpha1
  344. description: valid bootstrap tooken
  345. groups:
  346. - system:bootstrappers:kubeadm:default-node-token
  347. kind: BootstrapToken
  348. token: abcdef.1234567890123456
  349. usages:
  350. - signing
  351. - authentication
  352. `,
  353. },
  354. {
  355. name: "Go template output",
  356. id: "abcdef",
  357. secret: "1234567890123456",
  358. description: "valid bootstrap tooken",
  359. usages: []string{"signing", "authentication"},
  360. extraGroups: []string{"system:bootstrappers:kubeadm:default-node-token"},
  361. outputFormat: "go-template={{println .token .description .usages .groups}}",
  362. expected: `abcdef.1234567890123456 valid bootstrap tooken [signing authentication] [system:bootstrappers:kubeadm:default-node-token]
  363. `,
  364. },
  365. {
  366. name: "text output",
  367. id: "abcdef",
  368. secret: "1234567890123456",
  369. description: "valid bootstrap tooken",
  370. usages: []string{"signing", "authentication"},
  371. extraGroups: []string{"system:bootstrappers:kubeadm:default-node-token"},
  372. outputFormat: "text",
  373. expected: `TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
  374. abcdef.1234567890123456 <forever> <never> signing,authentication valid bootstrap tooken system:bootstrappers:kubeadm:default-node-token
  375. `,
  376. },
  377. {
  378. name: "jsonpath output",
  379. id: "abcdef",
  380. secret: "1234567890123456",
  381. description: "valid bootstrap tooken",
  382. usages: []string{"signing", "authentication"},
  383. extraGroups: []string{"system:bootstrappers:kubeadm:default-node-token"},
  384. outputFormat: "jsonpath={.token} {.groups}",
  385. expected: "abcdef.1234567890123456 [system:bootstrappers:kubeadm:default-node-token]",
  386. },
  387. }
  388. for _, tc := range testCases {
  389. t.Run(tc.name, func(t *testing.T) {
  390. token := outputapiv1alpha1.BootstrapToken{
  391. BootstrapToken: kubeadmapiv1beta2.BootstrapToken{
  392. Token: &kubeadmapiv1beta2.BootstrapTokenString{ID: tc.id, Secret: tc.secret},
  393. Description: tc.description,
  394. Usages: tc.usages,
  395. Groups: tc.extraGroups,
  396. },
  397. }
  398. buf := bytes.Buffer{}
  399. outputFlags := output.NewOutputFlags(&tokenTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(tc.outputFormat)
  400. printer, err := outputFlags.ToPrinter()
  401. if err != nil {
  402. t.Errorf("can't create printer for output format %s: %+v", tc.outputFormat, err)
  403. }
  404. if err := printer.PrintObj(&token, &buf); err != nil {
  405. t.Errorf("unable to print token %s: %+v", token.Token, err)
  406. }
  407. actual := buf.String()
  408. if actual != tc.expected {
  409. t.Errorf(
  410. "failed TestTokenOutput:\n\nexpected:\n%s\n\nactual:\n%s", tc.expected, actual)
  411. }
  412. })
  413. }
  414. }