cluster_authentication_trust_controller_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. /*
  2. Copyright 2019 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 clusterauthenticationtrust
  14. import (
  15. "reflect"
  16. "testing"
  17. "github.com/davecgh/go-spew/spew"
  18. corev1 "k8s.io/api/core/v1"
  19. apierrors "k8s.io/apimachinery/pkg/api/errors"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/runtime/schema"
  23. "k8s.io/apimachinery/pkg/util/diff"
  24. "k8s.io/apimachinery/pkg/util/validation/field"
  25. "k8s.io/apiserver/pkg/authentication/request/headerrequest"
  26. "k8s.io/apiserver/pkg/server/dynamiccertificates"
  27. "k8s.io/client-go/kubernetes/fake"
  28. corev1listers "k8s.io/client-go/listers/core/v1"
  29. clienttesting "k8s.io/client-go/testing"
  30. "k8s.io/client-go/tools/cache"
  31. )
  32. var (
  33. someRandomCA = []byte(`-----BEGIN CERTIFICATE-----
  34. MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
  35. GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
  36. MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
  37. BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
  38. KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
  39. BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
  40. K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
  41. a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
  42. MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
  43. -----END CERTIFICATE-----
  44. `)
  45. anotherRandomCA = []byte(`-----BEGIN CERTIFICATE-----
  46. MIIDQDCCAiigAwIBAgIJANWw74P5KJk2MA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
  47. BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
  48. DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjAjMSEwHwYDVQQDExh3ZWJo
  49. b29rLXRlc3QuZGVmYXVsdC5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
  50. AoIBAQDXd/nQ89a5H8ifEsigmMd01Ib6NVR3bkJjtkvYnTbdfYEBj7UzqOQtHoLa
  51. dIVmefny5uIHvj93WD8WDVPB3jX2JHrXkDTXd/6o6jIXHcsUfFTVLp6/bZ+Anqe0
  52. r/7hAPkzA2A7APyTWM3ZbEeo1afXogXhOJ1u/wz0DflgcB21gNho4kKTONXO3NHD
  53. XLpspFqSkxfEfKVDJaYAoMnYZJtFNsa2OvsmLnhYF8bjeT3i07lfwrhUZvP+7Gsp
  54. 7UgUwc06WuNHjfx1s5e6ySzH0QioMD1rjYneqOvk0pKrMIhuAEWXqq7jlXcDtx1E
  55. j+wnYbVqqVYheHZ8BCJoVAAQGs9/AgMBAAGjZDBiMAkGA1UdEwQCMAAwCwYDVR0P
  56. BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATApBgNVHREEIjAg
  57. hwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVsdC5zdmMwDQYJKoZIhvcNAQELBQAD
  58. ggEBAD/GKSPNyQuAOw/jsYZesb+RMedbkzs18sSwlxAJQMUrrXwlVdHrA8q5WhE6
  59. ABLqU1b8lQ8AWun07R8k5tqTmNvCARrAPRUqls/ryER+3Y9YEcxEaTc3jKNZFLbc
  60. T6YtcnkdhxsiO136wtiuatpYL91RgCmuSpR8+7jEHhuFU01iaASu7ypFrUzrKHTF
  61. bKwiLRQi1cMzVcLErq5CDEKiKhUkoDucyARFszrGt9vNIl/YCcBOkcNvM3c05Hn3
  62. M++C29JwS3Hwbubg6WO3wjFjoEhpCwU6qRYUz3MRp4tHO4kxKXx+oQnUiFnR7vW0
  63. YkNtGc1RUDHwecCTFpJtPb7Yu/E=
  64. -----END CERTIFICATE-----
  65. `)
  66. someRandomCAProvider dynamiccertificates.CAContentProvider
  67. anotherRandomCAProvider dynamiccertificates.CAContentProvider
  68. )
  69. func init() {
  70. var err error
  71. someRandomCAProvider, err = dynamiccertificates.NewStaticCAContent("foo", someRandomCA)
  72. if err != nil {
  73. panic(err)
  74. }
  75. anotherRandomCAProvider, err = dynamiccertificates.NewStaticCAContent("bar", anotherRandomCA)
  76. if err != nil {
  77. panic(err)
  78. }
  79. }
  80. func TestWriteClientCAs(t *testing.T) {
  81. tests := []struct {
  82. name string
  83. clusterAuthInfo ClusterAuthenticationInfo
  84. preexistingObjs []runtime.Object
  85. expectedConfigMaps map[string]*corev1.ConfigMap
  86. expectCreate bool
  87. }{
  88. {
  89. name: "basic",
  90. clusterAuthInfo: ClusterAuthenticationInfo{
  91. ClientCA: someRandomCAProvider,
  92. RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{"alfa", "bravo", "charlie"},
  93. RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{"delta"},
  94. RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{"echo", "foxtrot"},
  95. RequestHeaderCA: anotherRandomCAProvider,
  96. RequestHeaderAllowedNames: headerrequest.StaticStringSlice{"first", "second"},
  97. },
  98. expectedConfigMaps: map[string]*corev1.ConfigMap{
  99. "extension-apiserver-authentication": {
  100. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  101. Data: map[string]string{
  102. "client-ca-file": string(someRandomCA),
  103. "requestheader-username-headers": `["alfa","bravo","charlie"]`,
  104. "requestheader-group-headers": `["delta"]`,
  105. "requestheader-extra-headers-prefix": `["echo","foxtrot"]`,
  106. "requestheader-client-ca-file": string(anotherRandomCA),
  107. "requestheader-allowed-names": `["first","second"]`,
  108. },
  109. },
  110. },
  111. expectCreate: true,
  112. },
  113. {
  114. name: "skip extension-apiserver-authentication",
  115. clusterAuthInfo: ClusterAuthenticationInfo{
  116. RequestHeaderCA: anotherRandomCAProvider,
  117. RequestHeaderAllowedNames: headerrequest.StaticStringSlice{"first", "second"},
  118. },
  119. expectedConfigMaps: map[string]*corev1.ConfigMap{
  120. "extension-apiserver-authentication": {
  121. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  122. Data: map[string]string{
  123. "requestheader-username-headers": `[]`,
  124. "requestheader-group-headers": `[]`,
  125. "requestheader-extra-headers-prefix": `[]`,
  126. "requestheader-client-ca-file": string(anotherRandomCA),
  127. "requestheader-allowed-names": `["first","second"]`,
  128. },
  129. },
  130. },
  131. expectCreate: true,
  132. },
  133. {
  134. name: "skip extension-apiserver-authentication",
  135. clusterAuthInfo: ClusterAuthenticationInfo{
  136. ClientCA: someRandomCAProvider,
  137. },
  138. expectedConfigMaps: map[string]*corev1.ConfigMap{
  139. "extension-apiserver-authentication": {
  140. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  141. Data: map[string]string{
  142. "client-ca-file": string(someRandomCA),
  143. },
  144. },
  145. },
  146. expectCreate: true,
  147. },
  148. {
  149. name: "empty allowed names",
  150. clusterAuthInfo: ClusterAuthenticationInfo{
  151. RequestHeaderCA: anotherRandomCAProvider,
  152. },
  153. expectedConfigMaps: map[string]*corev1.ConfigMap{
  154. "extension-apiserver-authentication": {
  155. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  156. Data: map[string]string{
  157. "requestheader-username-headers": `[]`,
  158. "requestheader-group-headers": `[]`,
  159. "requestheader-extra-headers-prefix": `[]`,
  160. "requestheader-client-ca-file": string(anotherRandomCA),
  161. "requestheader-allowed-names": `[]`,
  162. },
  163. },
  164. },
  165. expectCreate: true,
  166. },
  167. {
  168. name: "overwrite extension-apiserver-authentication",
  169. clusterAuthInfo: ClusterAuthenticationInfo{
  170. ClientCA: someRandomCAProvider,
  171. },
  172. preexistingObjs: []runtime.Object{
  173. &corev1.ConfigMap{
  174. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  175. Data: map[string]string{
  176. "client-ca-file": string(anotherRandomCA),
  177. },
  178. },
  179. },
  180. expectedConfigMaps: map[string]*corev1.ConfigMap{
  181. "extension-apiserver-authentication": {
  182. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  183. Data: map[string]string{
  184. "client-ca-file": string(anotherRandomCA) + string(someRandomCA),
  185. },
  186. },
  187. },
  188. },
  189. {
  190. name: "overwrite extension-apiserver-authentication requestheader",
  191. clusterAuthInfo: ClusterAuthenticationInfo{
  192. RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
  193. RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
  194. RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
  195. RequestHeaderCA: anotherRandomCAProvider,
  196. RequestHeaderAllowedNames: headerrequest.StaticStringSlice{},
  197. },
  198. preexistingObjs: []runtime.Object{
  199. &corev1.ConfigMap{
  200. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  201. Data: map[string]string{
  202. "requestheader-username-headers": `[]`,
  203. "requestheader-group-headers": `[]`,
  204. "requestheader-extra-headers-prefix": `[]`,
  205. "requestheader-client-ca-file": string(someRandomCA),
  206. "requestheader-allowed-names": `[]`,
  207. },
  208. },
  209. },
  210. expectedConfigMaps: map[string]*corev1.ConfigMap{
  211. "extension-apiserver-authentication": {
  212. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  213. Data: map[string]string{
  214. "requestheader-username-headers": `[]`,
  215. "requestheader-group-headers": `[]`,
  216. "requestheader-extra-headers-prefix": `[]`,
  217. "requestheader-client-ca-file": string(someRandomCA) + string(anotherRandomCA),
  218. "requestheader-allowed-names": `[]`,
  219. },
  220. },
  221. },
  222. },
  223. {
  224. name: "namespace exists",
  225. clusterAuthInfo: ClusterAuthenticationInfo{
  226. ClientCA: someRandomCAProvider,
  227. },
  228. preexistingObjs: []runtime.Object{
  229. &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: metav1.NamespaceSystem}},
  230. },
  231. expectedConfigMaps: map[string]*corev1.ConfigMap{
  232. "extension-apiserver-authentication": {
  233. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  234. Data: map[string]string{
  235. "client-ca-file": string(someRandomCA),
  236. },
  237. },
  238. },
  239. expectCreate: true,
  240. },
  241. {
  242. name: "skip on no change",
  243. clusterAuthInfo: ClusterAuthenticationInfo{
  244. RequestHeaderUsernameHeaders: headerrequest.StaticStringSlice{},
  245. RequestHeaderGroupHeaders: headerrequest.StaticStringSlice{},
  246. RequestHeaderExtraHeaderPrefixes: headerrequest.StaticStringSlice{},
  247. RequestHeaderCA: anotherRandomCAProvider,
  248. RequestHeaderAllowedNames: headerrequest.StaticStringSlice{},
  249. },
  250. preexistingObjs: []runtime.Object{
  251. &corev1.ConfigMap{
  252. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  253. Data: map[string]string{
  254. "requestheader-username-headers": `[]`,
  255. "requestheader-group-headers": `[]`,
  256. "requestheader-extra-headers-prefix": `[]`,
  257. "requestheader-client-ca-file": string(anotherRandomCA),
  258. "requestheader-allowed-names": `[]`,
  259. },
  260. },
  261. },
  262. expectedConfigMaps: map[string]*corev1.ConfigMap{},
  263. expectCreate: false,
  264. },
  265. }
  266. for _, test := range tests {
  267. t.Run(test.name, func(t *testing.T) {
  268. client := fake.NewSimpleClientset(test.preexistingObjs...)
  269. configMapIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
  270. for _, obj := range test.preexistingObjs {
  271. configMapIndexer.Add(obj)
  272. }
  273. configmapLister := corev1listers.NewConfigMapLister(configMapIndexer)
  274. c := &Controller{
  275. configMapLister: configmapLister,
  276. configMapClient: client.CoreV1(),
  277. namespaceClient: client.CoreV1(),
  278. requiredAuthenticationData: test.clusterAuthInfo,
  279. }
  280. err := c.syncConfigMap()
  281. if err != nil {
  282. t.Fatal(err)
  283. }
  284. actualConfigMaps, updated := getFinalConfigMaps(t, client)
  285. if !reflect.DeepEqual(test.expectedConfigMaps, actualConfigMaps) {
  286. t.Fatalf("%s: %v", test.name, diff.ObjectReflectDiff(test.expectedConfigMaps, actualConfigMaps))
  287. }
  288. if test.expectCreate != updated {
  289. t.Fatalf("%s: expected %v, got %v", test.name, test.expectCreate, updated)
  290. }
  291. })
  292. }
  293. }
  294. func getFinalConfigMaps(t *testing.T, client *fake.Clientset) (map[string]*corev1.ConfigMap, bool) {
  295. ret := map[string]*corev1.ConfigMap{}
  296. created := false
  297. for _, action := range client.Actions() {
  298. t.Log(spew.Sdump(action))
  299. if action.Matches("create", "configmaps") {
  300. created = true
  301. obj := action.(clienttesting.CreateAction).GetObject().(*corev1.ConfigMap)
  302. ret[obj.Name] = obj
  303. }
  304. if action.Matches("update", "configmaps") {
  305. obj := action.(clienttesting.UpdateAction).GetObject().(*corev1.ConfigMap)
  306. ret[obj.Name] = obj
  307. }
  308. }
  309. return ret, created
  310. }
  311. func TestWriteConfigMapDeleted(t *testing.T) {
  312. // the basics are tested above, this checks the deletion logic when the ca bundles are too large
  313. cm := &corev1.ConfigMap{
  314. ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
  315. Data: map[string]string{
  316. "requestheader-username-headers": `[]`,
  317. "requestheader-group-headers": `[]`,
  318. "requestheader-extra-headers-prefix": `[]`,
  319. "requestheader-client-ca-file": string(anotherRandomCA),
  320. "requestheader-allowed-names": `[]`,
  321. },
  322. }
  323. t.Run("request entity too large", func(t *testing.T) {
  324. client := fake.NewSimpleClientset()
  325. client.PrependReactor("update", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  326. return true, nil, apierrors.NewRequestEntityTooLargeError("way too big")
  327. })
  328. client.PrependReactor("delete", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  329. return true, nil, nil
  330. })
  331. err := writeConfigMap(client.CoreV1(), cm)
  332. if err == nil || err.Error() != "Request entity too large: way too big" {
  333. t.Fatal(err)
  334. }
  335. if len(client.Actions()) != 2 {
  336. t.Fatal(client.Actions())
  337. }
  338. _, ok := client.Actions()[1].(clienttesting.DeleteAction)
  339. if !ok {
  340. t.Fatal(client.Actions())
  341. }
  342. })
  343. t.Run("ca bundle too large", func(t *testing.T) {
  344. client := fake.NewSimpleClientset()
  345. client.PrependReactor("update", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  346. return true, nil, apierrors.NewInvalid(schema.GroupKind{Kind: "ConfigMap"}, cm.Name, field.ErrorList{field.TooLong(field.NewPath(""), cm, corev1.MaxSecretSize)})
  347. })
  348. client.PrependReactor("delete", "configmaps", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  349. return true, nil, nil
  350. })
  351. err := writeConfigMap(client.CoreV1(), cm)
  352. if err == nil || err.Error() != `ConfigMap "extension-apiserver-authentication" is invalid: []: Too long: must have at most 1048576 bytes` {
  353. t.Fatal(err)
  354. }
  355. if len(client.Actions()) != 2 {
  356. t.Fatal(client.Actions())
  357. }
  358. _, ok := client.Actions()[1].(clienttesting.DeleteAction)
  359. if !ok {
  360. t.Fatal(client.Actions())
  361. }
  362. })
  363. }