svcaccttoken_test.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. /*
  2. Copyright 2017 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 auth
  14. import (
  15. "bytes"
  16. "context"
  17. "crypto/ecdsa"
  18. "encoding/base64"
  19. "encoding/json"
  20. "fmt"
  21. "io/ioutil"
  22. "net/http"
  23. "net/url"
  24. "reflect"
  25. "strings"
  26. "testing"
  27. "time"
  28. jose "gopkg.in/square/go-jose.v2"
  29. "gopkg.in/square/go-jose.v2/jwt"
  30. authenticationv1 "k8s.io/api/authentication/v1"
  31. v1 "k8s.io/api/core/v1"
  32. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  33. "k8s.io/apimachinery/pkg/types"
  34. "k8s.io/apiserver/pkg/authentication/authenticator"
  35. "k8s.io/apiserver/pkg/authentication/request/bearertoken"
  36. apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
  37. "k8s.io/apiserver/pkg/authorization/authorizerfactory"
  38. utilfeature "k8s.io/apiserver/pkg/util/feature"
  39. clientset "k8s.io/client-go/kubernetes"
  40. v1listers "k8s.io/client-go/listers/core/v1"
  41. "k8s.io/client-go/rest"
  42. "k8s.io/client-go/tools/cache"
  43. "k8s.io/client-go/util/keyutil"
  44. featuregatetesting "k8s.io/component-base/featuregate/testing"
  45. "k8s.io/kubernetes/pkg/apis/core"
  46. serviceaccountgetter "k8s.io/kubernetes/pkg/controller/serviceaccount"
  47. "k8s.io/kubernetes/pkg/features"
  48. "k8s.io/kubernetes/pkg/serviceaccount"
  49. "k8s.io/kubernetes/test/integration/framework"
  50. )
  51. const ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY-----
  52. MHcCAQEEIEZmTmUhuanLjPA2CLquXivuwBDHTt5XYwgIr/kA1LtRoAoGCCqGSM49
  53. AwEHoUQDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPLX2i8uIp/C/ASqiIGUeeKQtX0
  54. /IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg==
  55. -----END EC PRIVATE KEY-----`
  56. func TestServiceAccountTokenCreate(t *testing.T) {
  57. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.TokenRequest, true)()
  58. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountIssuerDiscovery, true)()
  59. // Build client config, clientset, and informers
  60. sk, err := keyutil.ParsePrivateKeyPEM([]byte(ecdsaPrivateKey))
  61. if err != nil {
  62. t.Fatalf("err: %v", err)
  63. }
  64. pk := sk.(*ecdsa.PrivateKey).PublicKey
  65. const iss = "https://foo.bar.example.com"
  66. aud := authenticator.Audiences{"api"}
  67. maxExpirationSeconds := int64(60 * 60)
  68. maxExpirationDuration, err := time.ParseDuration(fmt.Sprintf("%ds", maxExpirationSeconds))
  69. if err != nil {
  70. t.Fatalf("err: %v", err)
  71. }
  72. gcs := &clientset.Clientset{}
  73. // Start the server
  74. masterConfig := framework.NewIntegrationTestMasterConfig()
  75. masterConfig.GenericConfig.Authorization.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
  76. masterConfig.GenericConfig.Authentication.APIAudiences = aud
  77. masterConfig.GenericConfig.Authentication.Authenticator = bearertoken.New(
  78. serviceaccount.JWTTokenAuthenticator(
  79. iss,
  80. []interface{}{&pk},
  81. aud,
  82. serviceaccount.NewValidator(serviceaccountgetter.NewGetterFromClient(
  83. gcs,
  84. v1listers.NewSecretLister(newIndexer(func(namespace, name string) (interface{}, error) {
  85. return gcs.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
  86. })),
  87. v1listers.NewServiceAccountLister(newIndexer(func(namespace, name string) (interface{}, error) {
  88. return gcs.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{})
  89. })),
  90. v1listers.NewPodLister(newIndexer(func(namespace, name string) (interface{}, error) {
  91. return gcs.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
  92. })),
  93. )),
  94. ),
  95. )
  96. tokenGenerator, err := serviceaccount.JWTTokenGenerator(iss, sk)
  97. if err != nil {
  98. t.Fatalf("err: %v", err)
  99. }
  100. masterConfig.ExtraConfig.ServiceAccountIssuer = tokenGenerator
  101. masterConfig.ExtraConfig.ServiceAccountMaxExpiration = maxExpirationDuration
  102. masterConfig.GenericConfig.Authentication.APIAudiences = aud
  103. masterConfig.ExtraConfig.ServiceAccountIssuerURL = iss
  104. masterConfig.ExtraConfig.ServiceAccountJWKSURI = ""
  105. masterConfig.ExtraConfig.ServiceAccountPublicKeys = []interface{}{&pk}
  106. master, _, closeFn := framework.RunAMaster(masterConfig)
  107. defer closeFn()
  108. cs, err := clientset.NewForConfig(master.GenericAPIServer.LoopbackClientConfig)
  109. if err != nil {
  110. t.Fatalf("err: %v", err)
  111. }
  112. *gcs = *cs
  113. rc, err := rest.UnversionedRESTClientFor(master.GenericAPIServer.LoopbackClientConfig)
  114. if err != nil {
  115. t.Fatal(err)
  116. }
  117. var (
  118. sa = &v1.ServiceAccount{
  119. ObjectMeta: metav1.ObjectMeta{
  120. Name: "test-svcacct",
  121. Namespace: "myns",
  122. },
  123. }
  124. pod = &v1.Pod{
  125. ObjectMeta: metav1.ObjectMeta{
  126. Name: "test-pod",
  127. Namespace: sa.Namespace,
  128. },
  129. Spec: v1.PodSpec{
  130. ServiceAccountName: sa.Name,
  131. Containers: []v1.Container{{Name: "test-container", Image: "nginx"}},
  132. },
  133. }
  134. otherpod = &v1.Pod{
  135. ObjectMeta: metav1.ObjectMeta{
  136. Name: "other-test-pod",
  137. Namespace: sa.Namespace,
  138. },
  139. Spec: v1.PodSpec{
  140. ServiceAccountName: "other-" + sa.Name,
  141. Containers: []v1.Container{{Name: "test-container", Image: "nginx"}},
  142. },
  143. }
  144. secret = &v1.Secret{
  145. ObjectMeta: metav1.ObjectMeta{
  146. Name: "test-secret",
  147. Namespace: sa.Namespace,
  148. },
  149. }
  150. wrongUID = types.UID("wrong")
  151. noUID = types.UID("")
  152. )
  153. t.Run("bound to service account", func(t *testing.T) {
  154. treq := &authenticationv1.TokenRequest{
  155. Spec: authenticationv1.TokenRequestSpec{
  156. Audiences: []string{"api"},
  157. },
  158. }
  159. if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err == nil {
  160. t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp)
  161. }
  162. sa, delSvcAcct := createDeleteSvcAcct(t, cs, sa)
  163. defer delSvcAcct()
  164. treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{})
  165. if err != nil {
  166. t.Fatalf("err: %v", err)
  167. }
  168. checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
  169. checkPayload(t, treq.Status.Token, `["api"]`, "aud")
  170. checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "pod")
  171. checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret")
  172. checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
  173. checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
  174. info := doTokenReview(t, cs, treq, false)
  175. if info.Extra != nil {
  176. t.Fatalf("expected Extra to be nil but got: %#v", info.Extra)
  177. }
  178. delSvcAcct()
  179. doTokenReview(t, cs, treq, true)
  180. })
  181. t.Run("bound to service account and pod", func(t *testing.T) {
  182. treq := &authenticationv1.TokenRequest{
  183. Spec: authenticationv1.TokenRequestSpec{
  184. Audiences: []string{"api"},
  185. BoundObjectRef: &authenticationv1.BoundObjectReference{
  186. Kind: "Pod",
  187. APIVersion: "v1",
  188. Name: pod.Name,
  189. },
  190. },
  191. }
  192. if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err == nil {
  193. t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp)
  194. }
  195. sa, del := createDeleteSvcAcct(t, cs, sa)
  196. defer del()
  197. if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err == nil {
  198. t.Fatalf("expected err creating token bound to nonexistant pod but got: %#v", resp)
  199. }
  200. pod, delPod := createDeletePod(t, cs, pod)
  201. defer delPod()
  202. // right uid
  203. treq.Spec.BoundObjectRef.UID = pod.UID
  204. if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err != nil {
  205. t.Fatalf("err: %v", err)
  206. }
  207. // wrong uid
  208. treq.Spec.BoundObjectRef.UID = wrongUID
  209. if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err == nil {
  210. t.Fatalf("expected err creating token bound to pod with wrong uid but got: %#v", resp)
  211. }
  212. // no uid
  213. treq.Spec.BoundObjectRef.UID = noUID
  214. treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{})
  215. if err != nil {
  216. t.Fatalf("err: %v", err)
  217. }
  218. checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
  219. checkPayload(t, treq.Status.Token, `["api"]`, "aud")
  220. checkPayload(t, treq.Status.Token, `"test-pod"`, "kubernetes.io", "pod", "name")
  221. checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret")
  222. checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
  223. checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
  224. info := doTokenReview(t, cs, treq, false)
  225. if len(info.Extra) != 2 {
  226. t.Fatalf("expected Extra have length of 2 but was length %d: %#v", len(info.Extra), info.Extra)
  227. }
  228. if expected := map[string]authenticationv1.ExtraValue{
  229. "authentication.kubernetes.io/pod-name": {pod.ObjectMeta.Name},
  230. "authentication.kubernetes.io/pod-uid": {string(pod.ObjectMeta.UID)},
  231. }; !reflect.DeepEqual(info.Extra, expected) {
  232. t.Fatalf("unexpected Extra:\ngot:\t%#v\nwant:\t%#v", info.Extra, expected)
  233. }
  234. delPod()
  235. doTokenReview(t, cs, treq, true)
  236. })
  237. t.Run("bound to service account and secret", func(t *testing.T) {
  238. treq := &authenticationv1.TokenRequest{
  239. Spec: authenticationv1.TokenRequestSpec{
  240. Audiences: []string{"api"},
  241. BoundObjectRef: &authenticationv1.BoundObjectReference{
  242. Kind: "Secret",
  243. APIVersion: "v1",
  244. Name: secret.Name,
  245. UID: secret.UID,
  246. },
  247. },
  248. }
  249. if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err == nil {
  250. t.Fatalf("expected err creating token for nonexistant svcacct but got: %#v", resp)
  251. }
  252. sa, del := createDeleteSvcAcct(t, cs, sa)
  253. defer del()
  254. if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err == nil {
  255. t.Fatalf("expected err creating token bound to nonexistant secret but got: %#v", resp)
  256. }
  257. secret, delSecret := createDeleteSecret(t, cs, secret)
  258. defer delSecret()
  259. // right uid
  260. treq.Spec.BoundObjectRef.UID = secret.UID
  261. if _, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err != nil {
  262. t.Fatalf("err: %v", err)
  263. }
  264. // wrong uid
  265. treq.Spec.BoundObjectRef.UID = wrongUID
  266. if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err == nil {
  267. t.Fatalf("expected err creating token bound to secret with wrong uid but got: %#v", resp)
  268. }
  269. // no uid
  270. treq.Spec.BoundObjectRef.UID = noUID
  271. treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{})
  272. if err != nil {
  273. t.Fatalf("err: %v", err)
  274. }
  275. checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
  276. checkPayload(t, treq.Status.Token, `["api"]`, "aud")
  277. checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod")
  278. checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name")
  279. checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
  280. checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
  281. doTokenReview(t, cs, treq, false)
  282. delSecret()
  283. doTokenReview(t, cs, treq, true)
  284. })
  285. t.Run("bound to service account and pod running as different service account", func(t *testing.T) {
  286. treq := &authenticationv1.TokenRequest{
  287. Spec: authenticationv1.TokenRequestSpec{
  288. Audiences: []string{"api"},
  289. BoundObjectRef: &authenticationv1.BoundObjectReference{
  290. Kind: "Pod",
  291. APIVersion: "v1",
  292. Name: otherpod.Name,
  293. },
  294. },
  295. }
  296. sa, del := createDeleteSvcAcct(t, cs, sa)
  297. defer del()
  298. _, del = createDeletePod(t, cs, otherpod)
  299. defer del()
  300. if resp, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err == nil {
  301. t.Fatalf("expected err but got: %#v", resp)
  302. }
  303. })
  304. t.Run("expired token", func(t *testing.T) {
  305. treq := &authenticationv1.TokenRequest{
  306. Spec: authenticationv1.TokenRequestSpec{
  307. Audiences: []string{"api"},
  308. },
  309. }
  310. sa, del := createDeleteSvcAcct(t, cs, sa)
  311. defer del()
  312. treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{})
  313. if err != nil {
  314. t.Fatalf("err: %v", err)
  315. }
  316. doTokenReview(t, cs, treq, false)
  317. // backdate the token
  318. then := time.Now().Add(-2 * time.Hour)
  319. sc := &jwt.Claims{
  320. Subject: apiserverserviceaccount.MakeUsername(sa.Namespace, sa.Name),
  321. Audience: jwt.Audience([]string{"api"}),
  322. IssuedAt: jwt.NewNumericDate(then),
  323. NotBefore: jwt.NewNumericDate(then),
  324. Expiry: jwt.NewNumericDate(then.Add(time.Duration(60*60) * time.Second)),
  325. }
  326. coresa := core.ServiceAccount{
  327. ObjectMeta: sa.ObjectMeta,
  328. }
  329. _, pc := serviceaccount.Claims(coresa, nil, nil, 0, nil)
  330. tok, err := masterConfig.ExtraConfig.ServiceAccountIssuer.GenerateToken(sc, pc)
  331. if err != nil {
  332. t.Fatalf("err signing expired token: %v", err)
  333. }
  334. treq.Status.Token = tok
  335. doTokenReview(t, cs, treq, true)
  336. })
  337. t.Run("a token without an api audience is invalid", func(t *testing.T) {
  338. treq := &authenticationv1.TokenRequest{
  339. Spec: authenticationv1.TokenRequestSpec{
  340. Audiences: []string{"not-the-api"},
  341. },
  342. }
  343. sa, del := createDeleteSvcAcct(t, cs, sa)
  344. defer del()
  345. treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{})
  346. if err != nil {
  347. t.Fatalf("err: %v", err)
  348. }
  349. doTokenReview(t, cs, treq, true)
  350. })
  351. t.Run("a tokenrequest without an audience is valid against the api", func(t *testing.T) {
  352. treq := &authenticationv1.TokenRequest{
  353. Spec: authenticationv1.TokenRequestSpec{},
  354. }
  355. sa, del := createDeleteSvcAcct(t, cs, sa)
  356. defer del()
  357. treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{})
  358. if err != nil {
  359. t.Fatalf("err: %v", err)
  360. }
  361. checkPayload(t, treq.Status.Token, `["api"]`, "aud")
  362. doTokenReview(t, cs, treq, false)
  363. })
  364. t.Run("a token should be invalid after recreating same name pod", func(t *testing.T) {
  365. treq := &authenticationv1.TokenRequest{
  366. Spec: authenticationv1.TokenRequestSpec{
  367. Audiences: []string{"api"},
  368. BoundObjectRef: &authenticationv1.BoundObjectReference{
  369. Kind: "Pod",
  370. APIVersion: "v1",
  371. Name: pod.Name,
  372. },
  373. },
  374. }
  375. sa, del := createDeleteSvcAcct(t, cs, sa)
  376. defer del()
  377. originalPod, originalDelPod := createDeletePod(t, cs, pod)
  378. defer originalDelPod()
  379. treq.Spec.BoundObjectRef.UID = originalPod.UID
  380. if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err != nil {
  381. t.Fatalf("err: %v", err)
  382. }
  383. checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
  384. checkPayload(t, treq.Status.Token, `["api"]`, "aud")
  385. checkPayload(t, treq.Status.Token, `"test-pod"`, "kubernetes.io", "pod", "name")
  386. checkPayload(t, treq.Status.Token, "null", "kubernetes.io", "secret")
  387. checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
  388. checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
  389. doTokenReview(t, cs, treq, false)
  390. originalDelPod()
  391. doTokenReview(t, cs, treq, true)
  392. _, recreateDelPod := createDeletePod(t, cs, pod)
  393. defer recreateDelPod()
  394. doTokenReview(t, cs, treq, true)
  395. })
  396. t.Run("a token should be invalid after recreating same name secret", func(t *testing.T) {
  397. treq := &authenticationv1.TokenRequest{
  398. Spec: authenticationv1.TokenRequestSpec{
  399. Audiences: []string{"api"},
  400. BoundObjectRef: &authenticationv1.BoundObjectReference{
  401. Kind: "Secret",
  402. APIVersion: "v1",
  403. Name: secret.Name,
  404. UID: secret.UID,
  405. },
  406. },
  407. }
  408. sa, del := createDeleteSvcAcct(t, cs, sa)
  409. defer del()
  410. originalSecret, originalDelSecret := createDeleteSecret(t, cs, secret)
  411. defer originalDelSecret()
  412. treq.Spec.BoundObjectRef.UID = originalSecret.UID
  413. if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err != nil {
  414. t.Fatalf("err: %v", err)
  415. }
  416. checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
  417. checkPayload(t, treq.Status.Token, `["api"]`, "aud")
  418. checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod")
  419. checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name")
  420. checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
  421. checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
  422. doTokenReview(t, cs, treq, false)
  423. originalDelSecret()
  424. doTokenReview(t, cs, treq, true)
  425. _, recreateDelSecret := createDeleteSecret(t, cs, secret)
  426. defer recreateDelSecret()
  427. doTokenReview(t, cs, treq, true)
  428. })
  429. t.Run("a token request within expiration time", func(t *testing.T) {
  430. normalExpirationTime := maxExpirationSeconds - 10*60
  431. treq := &authenticationv1.TokenRequest{
  432. Spec: authenticationv1.TokenRequestSpec{
  433. Audiences: []string{"api"},
  434. ExpirationSeconds: &normalExpirationTime,
  435. BoundObjectRef: &authenticationv1.BoundObjectReference{
  436. Kind: "Secret",
  437. APIVersion: "v1",
  438. Name: secret.Name,
  439. UID: secret.UID,
  440. },
  441. },
  442. }
  443. sa, del := createDeleteSvcAcct(t, cs, sa)
  444. defer del()
  445. originalSecret, originalDelSecret := createDeleteSecret(t, cs, secret)
  446. defer originalDelSecret()
  447. treq.Spec.BoundObjectRef.UID = originalSecret.UID
  448. if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err != nil {
  449. t.Fatalf("err: %v", err)
  450. }
  451. checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
  452. checkPayload(t, treq.Status.Token, `["api"]`, "aud")
  453. checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod")
  454. checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name")
  455. checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
  456. checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
  457. checkExpiration(t, treq, normalExpirationTime)
  458. doTokenReview(t, cs, treq, false)
  459. originalDelSecret()
  460. doTokenReview(t, cs, treq, true)
  461. _, recreateDelSecret := createDeleteSecret(t, cs, secret)
  462. defer recreateDelSecret()
  463. doTokenReview(t, cs, treq, true)
  464. })
  465. t.Run("a token request with out-of-range expiration", func(t *testing.T) {
  466. tooLongExpirationTime := maxExpirationSeconds + 10*60
  467. treq := &authenticationv1.TokenRequest{
  468. Spec: authenticationv1.TokenRequestSpec{
  469. Audiences: []string{"api"},
  470. ExpirationSeconds: &tooLongExpirationTime,
  471. BoundObjectRef: &authenticationv1.BoundObjectReference{
  472. Kind: "Secret",
  473. APIVersion: "v1",
  474. Name: secret.Name,
  475. UID: secret.UID,
  476. },
  477. },
  478. }
  479. sa, del := createDeleteSvcAcct(t, cs, sa)
  480. defer del()
  481. originalSecret, originalDelSecret := createDeleteSecret(t, cs, secret)
  482. defer originalDelSecret()
  483. treq.Spec.BoundObjectRef.UID = originalSecret.UID
  484. if treq, err = cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(context.TODO(), sa.Name, treq, metav1.CreateOptions{}); err != nil {
  485. t.Fatalf("err: %v", err)
  486. }
  487. checkPayload(t, treq.Status.Token, `"system:serviceaccount:myns:test-svcacct"`, "sub")
  488. checkPayload(t, treq.Status.Token, `["api"]`, "aud")
  489. checkPayload(t, treq.Status.Token, `null`, "kubernetes.io", "pod")
  490. checkPayload(t, treq.Status.Token, `"test-secret"`, "kubernetes.io", "secret", "name")
  491. checkPayload(t, treq.Status.Token, `"myns"`, "kubernetes.io", "namespace")
  492. checkPayload(t, treq.Status.Token, `"test-svcacct"`, "kubernetes.io", "serviceaccount", "name")
  493. checkExpiration(t, treq, maxExpirationSeconds)
  494. doTokenReview(t, cs, treq, false)
  495. originalDelSecret()
  496. doTokenReview(t, cs, treq, true)
  497. _, recreateDelSecret := createDeleteSecret(t, cs, secret)
  498. defer recreateDelSecret()
  499. doTokenReview(t, cs, treq, true)
  500. })
  501. t.Run("a token is valid against the HTTP-provided service account issuer metadata", func(t *testing.T) {
  502. sa, del := createDeleteSvcAcct(t, cs, sa)
  503. defer del()
  504. t.Log("get token")
  505. tokenRequest, err := cs.CoreV1().ServiceAccounts(sa.Namespace).CreateToken(
  506. context.TODO(),
  507. sa.Name,
  508. &authenticationv1.TokenRequest{
  509. Spec: authenticationv1.TokenRequestSpec{
  510. Audiences: []string{"api"},
  511. },
  512. }, metav1.CreateOptions{})
  513. if err != nil {
  514. t.Fatalf("unexpected error creating token: %v", err)
  515. }
  516. token := tokenRequest.Status.Token
  517. if token == "" {
  518. t.Fatal("no token")
  519. }
  520. t.Log("get discovery doc")
  521. discoveryDoc := struct {
  522. Issuer string `json:"issuer"`
  523. JWKS string `json:"jwks_uri"`
  524. }{}
  525. // A little convoluted, but the base path is hidden inside the RESTClient.
  526. // We can't just use the RESTClient, because it throws away the headers
  527. // before returning a result, and we need to check the headers.
  528. discoveryURL := rc.Get().AbsPath("/.well-known/openid-configuration").URL().String()
  529. resp, err := rc.Client.Get(discoveryURL)
  530. if err != nil {
  531. t.Fatalf("error getting metadata: %v", err)
  532. }
  533. defer resp.Body.Close()
  534. if resp.StatusCode != http.StatusOK {
  535. t.Errorf("got status: %v, want: %v", resp.StatusCode, http.StatusOK)
  536. }
  537. if got, want := resp.Header.Get("Content-Type"), "application/json"; got != want {
  538. t.Errorf("got Content-Type: %v, want: %v", got, want)
  539. }
  540. if got, want := resp.Header.Get("Cache-Control"), "public, max-age=3600"; got != want {
  541. t.Errorf("got Cache-Control: %v, want: %v", got, want)
  542. }
  543. b, err := ioutil.ReadAll(resp.Body)
  544. if err != nil {
  545. t.Fatal(err)
  546. }
  547. md := bytes.NewBuffer(b)
  548. t.Logf("raw discovery doc response:\n---%s\n---", md.String())
  549. if md.Len() == 0 {
  550. t.Fatal("empty response for discovery doc")
  551. }
  552. if err := json.NewDecoder(md).Decode(&discoveryDoc); err != nil {
  553. t.Fatalf("could not decode metadata: %v", err)
  554. }
  555. if discoveryDoc.Issuer != iss {
  556. t.Fatalf("invalid issuer in discovery doc: got %s, want %s",
  557. discoveryDoc.Issuer, iss)
  558. }
  559. // Parse the JWKSURI see if the path is what we expect. Since the
  560. // integration test framework hardcodes 192.168.10.4 as the PublicAddress,
  561. // which results in the same for ExternalAddress, we expect the JWKS URI
  562. // to be 192.168.10.4:443, even if that's not necessarily the external
  563. // IP of the test machine.
  564. expectJWKSURI := (&url.URL{
  565. Scheme: "https",
  566. Host: "192.168.10.4:443",
  567. Path: serviceaccount.JWKSPath,
  568. }).String()
  569. if discoveryDoc.JWKS != expectJWKSURI {
  570. t.Fatalf("unexpected jwks_uri in discovery doc: got %s, want %s",
  571. discoveryDoc.JWKS, expectJWKSURI)
  572. }
  573. // Since the test framework hardcodes the host, we combine our client's
  574. // scheme and host with serviceaccount.JWKSPath. We know that this is what was
  575. // in the discovery doc because we checked that it matched above.
  576. jwksURI := rc.Get().AbsPath(serviceaccount.JWKSPath).URL().String()
  577. t.Log("get jwks from", jwksURI)
  578. resp, err = rc.Client.Get(jwksURI)
  579. if err != nil {
  580. t.Fatalf("error getting jwks: %v", err)
  581. }
  582. defer resp.Body.Close()
  583. if resp.StatusCode != http.StatusOK {
  584. t.Errorf("got status: %v, want: %v", resp.StatusCode, http.StatusOK)
  585. }
  586. if got, want := resp.Header.Get("Content-Type"), "application/jwk-set+json"; got != want {
  587. t.Errorf("got Content-Type: %v, want: %v", got, want)
  588. }
  589. if got, want := resp.Header.Get("Cache-Control"), "public, max-age=3600"; got != want {
  590. t.Errorf("got Cache-Control: %v, want: %v", got, want)
  591. }
  592. b, err = ioutil.ReadAll(resp.Body)
  593. if err != nil {
  594. t.Fatal(err)
  595. }
  596. ks := bytes.NewBuffer(b)
  597. if ks.Len() == 0 {
  598. t.Fatal("empty jwks")
  599. }
  600. t.Logf("raw JWKS: \n---\n%s\n---", ks.String())
  601. jwks := jose.JSONWebKeySet{}
  602. if err := json.NewDecoder(ks).Decode(&jwks); err != nil {
  603. t.Fatalf("could not decode JWKS: %v", err)
  604. }
  605. if len(jwks.Keys) != 1 {
  606. t.Fatalf("len(jwks.Keys) = %d, want 1", len(jwks.Keys))
  607. }
  608. key := jwks.Keys[0]
  609. tok, err := jwt.ParseSigned(token)
  610. if err != nil {
  611. t.Fatalf("could not parse token %q: %v", token, err)
  612. }
  613. var claims jwt.Claims
  614. if err := tok.Claims(key, &claims); err != nil {
  615. t.Fatalf("could not validate claims on token: %v", err)
  616. }
  617. if err := claims.Validate(jwt.Expected{Issuer: discoveryDoc.Issuer}); err != nil {
  618. t.Fatalf("invalid claims: %v", err)
  619. }
  620. })
  621. }
  622. func doTokenReview(t *testing.T, cs clientset.Interface, treq *authenticationv1.TokenRequest, expectErr bool) authenticationv1.UserInfo {
  623. t.Helper()
  624. trev, err := cs.AuthenticationV1().TokenReviews().Create(context.TODO(), &authenticationv1.TokenReview{
  625. Spec: authenticationv1.TokenReviewSpec{
  626. Token: treq.Status.Token,
  627. },
  628. }, metav1.CreateOptions{})
  629. if err != nil {
  630. t.Fatalf("err: %v", err)
  631. }
  632. t.Logf("status: %+v", trev.Status)
  633. if (trev.Status.Error != "") && !expectErr {
  634. t.Fatalf("expected no error but got: %v", trev.Status.Error)
  635. }
  636. if (trev.Status.Error == "") && expectErr {
  637. t.Fatalf("expected error but got: %+v", trev.Status)
  638. }
  639. if !trev.Status.Authenticated && !expectErr {
  640. t.Fatal("expected token to be authenticated but it wasn't")
  641. }
  642. return trev.Status.User
  643. }
  644. func checkPayload(t *testing.T, tok string, want string, parts ...string) {
  645. t.Helper()
  646. got := getSubObject(t, getPayload(t, tok), parts...)
  647. if got != want {
  648. t.Errorf("unexpected payload.\nsaw:\t%v\nwant:\t%v", got, want)
  649. }
  650. }
  651. func checkExpiration(t *testing.T, treq *authenticationv1.TokenRequest, expectedExpiration int64) {
  652. t.Helper()
  653. if treq.Spec.ExpirationSeconds == nil {
  654. t.Errorf("unexpected nil expiration seconds.")
  655. }
  656. if *treq.Spec.ExpirationSeconds != expectedExpiration {
  657. t.Errorf("unexpected expiration seconds.\nsaw:\t%d\nwant:\t%d", treq.Spec.ExpirationSeconds, expectedExpiration)
  658. }
  659. }
  660. func getSubObject(t *testing.T, b string, parts ...string) string {
  661. t.Helper()
  662. var obj interface{}
  663. obj = make(map[string]interface{})
  664. if err := json.Unmarshal([]byte(b), &obj); err != nil {
  665. t.Fatalf("err: %v", err)
  666. }
  667. for _, part := range parts {
  668. obj = obj.(map[string]interface{})[part]
  669. }
  670. out, err := json.Marshal(obj)
  671. if err != nil {
  672. t.Fatalf("err: %v", err)
  673. }
  674. return string(out)
  675. }
  676. func getPayload(t *testing.T, b string) string {
  677. t.Helper()
  678. parts := strings.Split(b, ".")
  679. if len(parts) != 3 {
  680. t.Fatalf("token did not have three parts: %v", b)
  681. }
  682. payload, err := base64.RawURLEncoding.DecodeString(parts[1])
  683. if err != nil {
  684. t.Fatalf("failed to base64 decode token: %v", err)
  685. }
  686. return string(payload)
  687. }
  688. func createDeleteSvcAcct(t *testing.T, cs clientset.Interface, sa *v1.ServiceAccount) (*v1.ServiceAccount, func()) {
  689. t.Helper()
  690. sa, err := cs.CoreV1().ServiceAccounts(sa.Namespace).Create(context.TODO(), sa, metav1.CreateOptions{})
  691. if err != nil {
  692. t.Fatalf("err: %v", err)
  693. }
  694. done := false
  695. return sa, func() {
  696. t.Helper()
  697. if done {
  698. return
  699. }
  700. done = true
  701. if err := cs.CoreV1().ServiceAccounts(sa.Namespace).Delete(context.TODO(), sa.Name, nil); err != nil {
  702. t.Fatalf("err: %v", err)
  703. }
  704. }
  705. }
  706. func createDeletePod(t *testing.T, cs clientset.Interface, pod *v1.Pod) (*v1.Pod, func()) {
  707. t.Helper()
  708. pod, err := cs.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
  709. if err != nil {
  710. t.Fatalf("err: %v", err)
  711. }
  712. done := false
  713. return pod, func() {
  714. t.Helper()
  715. if done {
  716. return
  717. }
  718. done = true
  719. if err := cs.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, nil); err != nil {
  720. t.Fatalf("err: %v", err)
  721. }
  722. }
  723. }
  724. func createDeleteSecret(t *testing.T, cs clientset.Interface, sec *v1.Secret) (*v1.Secret, func()) {
  725. t.Helper()
  726. sec, err := cs.CoreV1().Secrets(sec.Namespace).Create(context.TODO(), sec, metav1.CreateOptions{})
  727. if err != nil {
  728. t.Fatalf("err: %v", err)
  729. }
  730. done := false
  731. return sec, func() {
  732. t.Helper()
  733. if done {
  734. return
  735. }
  736. done = true
  737. if err := cs.CoreV1().Secrets(sec.Namespace).Delete(context.TODO(), sec.Name, nil); err != nil {
  738. t.Fatalf("err: %v", err)
  739. }
  740. }
  741. }
  742. func newIndexer(get func(namespace, name string) (interface{}, error)) cache.Indexer {
  743. return &fakeIndexer{get: get}
  744. }
  745. type fakeIndexer struct {
  746. cache.Indexer
  747. get func(namespace, name string) (interface{}, error)
  748. }
  749. func (f *fakeIndexer) GetByKey(key string) (interface{}, bool, error) {
  750. parts := strings.SplitN(key, "/", 2)
  751. namespace := parts[0]
  752. name := ""
  753. if len(parts) == 2 {
  754. name = parts[1]
  755. }
  756. obj, err := f.get(namespace, name)
  757. return obj, err == nil, err
  758. }