admission_test.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  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 imagepolicy
  14. import (
  15. "context"
  16. "crypto/tls"
  17. "crypto/x509"
  18. "encoding/json"
  19. "math/rand"
  20. "net/http"
  21. "net/http/httptest"
  22. "reflect"
  23. "strconv"
  24. "testing"
  25. "time"
  26. "k8s.io/api/imagepolicy/v1alpha1"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. "k8s.io/apiserver/pkg/admission"
  29. "k8s.io/apiserver/pkg/authentication/user"
  30. v1 "k8s.io/client-go/tools/clientcmd/api/v1"
  31. api "k8s.io/kubernetes/pkg/apis/core"
  32. "fmt"
  33. "io/ioutil"
  34. "os"
  35. "path/filepath"
  36. "text/template"
  37. _ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
  38. )
  39. const defaultConfigTmplJSON = `
  40. {
  41. "imagePolicy": {
  42. "kubeConfigFile": "{{ .KubeConfig }}",
  43. "allowTTL": {{ .AllowTTL }},
  44. "denyTTL": {{ .DenyTTL }},
  45. "retryBackoff": {{ .RetryBackoff }},
  46. "defaultAllow": {{ .DefaultAllow }}
  47. }
  48. }
  49. `
  50. const defaultConfigTmplYAML = `
  51. imagePolicy:
  52. kubeConfigFile: "{{ .KubeConfig }}"
  53. allowTTL: {{ .AllowTTL }}
  54. denyTTL: {{ .DenyTTL }}
  55. retryBackoff: {{ .RetryBackoff }}
  56. defaultAllow: {{ .DefaultAllow }}
  57. `
  58. func TestNewFromConfig(t *testing.T) {
  59. dir, err := ioutil.TempDir("", "")
  60. if err != nil {
  61. t.Fatal(err)
  62. }
  63. defer os.RemoveAll(dir)
  64. data := struct {
  65. CA string
  66. Cert string
  67. Key string
  68. }{
  69. CA: filepath.Join(dir, "ca.pem"),
  70. Cert: filepath.Join(dir, "clientcert.pem"),
  71. Key: filepath.Join(dir, "clientkey.pem"),
  72. }
  73. files := []struct {
  74. name string
  75. data []byte
  76. }{
  77. {data.CA, caCert},
  78. {data.Cert, clientCert},
  79. {data.Key, clientKey},
  80. }
  81. for _, file := range files {
  82. if err := ioutil.WriteFile(file.name, file.data, 0400); err != nil {
  83. t.Fatal(err)
  84. }
  85. }
  86. tests := []struct {
  87. msg string
  88. kubeConfigTmpl string
  89. wantErr bool
  90. }{
  91. {
  92. msg: "a single cluster and single user",
  93. kubeConfigTmpl: `
  94. clusters:
  95. - cluster:
  96. certificate-authority: {{ .CA }}
  97. server: https://admission.example.com
  98. name: foobar
  99. users:
  100. - name: a cluster
  101. user:
  102. client-certificate: {{ .Cert }}
  103. client-key: {{ .Key }}
  104. `,
  105. wantErr: true,
  106. },
  107. {
  108. msg: "multiple clusters with no context",
  109. kubeConfigTmpl: `
  110. clusters:
  111. - cluster:
  112. certificate-authority: {{ .CA }}
  113. server: https://admission.example.com
  114. name: foobar
  115. - cluster:
  116. certificate-authority: a bad certificate path
  117. server: https://admission.example.com
  118. name: barfoo
  119. users:
  120. - name: a name
  121. user:
  122. client-certificate: {{ .Cert }}
  123. client-key: {{ .Key }}
  124. `,
  125. wantErr: true,
  126. },
  127. {
  128. msg: "multiple clusters with a context",
  129. kubeConfigTmpl: `
  130. clusters:
  131. - cluster:
  132. certificate-authority: a bad certificate path
  133. server: https://admission.example.com
  134. name: foobar
  135. - cluster:
  136. certificate-authority: {{ .CA }}
  137. server: https://admission.example.com
  138. name: barfoo
  139. users:
  140. - name: a name
  141. user:
  142. client-certificate: {{ .Cert }}
  143. client-key: {{ .Key }}
  144. contexts:
  145. - name: default
  146. context:
  147. cluster: barfoo
  148. user: a name
  149. current-context: default
  150. `,
  151. wantErr: false,
  152. },
  153. {
  154. msg: "cluster with bad certificate path specified",
  155. kubeConfigTmpl: `
  156. clusters:
  157. - cluster:
  158. certificate-authority: a bad certificate path
  159. server: https://admission.example.com
  160. name: foobar
  161. - cluster:
  162. certificate-authority: {{ .CA }}
  163. server: https://admission.example.com
  164. name: barfoo
  165. users:
  166. - name: a name
  167. user:
  168. client-certificate: {{ .Cert }}
  169. client-key: {{ .Key }}
  170. contexts:
  171. - name: default
  172. context:
  173. cluster: foobar
  174. user: a name
  175. current-context: default
  176. `,
  177. wantErr: true,
  178. },
  179. }
  180. for _, tt := range tests {
  181. // Use a closure so defer statements trigger between loop iterations.
  182. t.Run(tt.msg, func(t *testing.T) {
  183. err := func() error {
  184. tempfile, err := ioutil.TempFile("", "")
  185. if err != nil {
  186. return err
  187. }
  188. p := tempfile.Name()
  189. defer os.Remove(p)
  190. tmpl, err := template.New("test").Parse(tt.kubeConfigTmpl)
  191. if err != nil {
  192. return fmt.Errorf("failed to parse test template: %v", err)
  193. }
  194. if err := tmpl.Execute(tempfile, data); err != nil {
  195. return fmt.Errorf("failed to execute test template: %v", err)
  196. }
  197. tempconfigfile, err := ioutil.TempFile("", "")
  198. if err != nil {
  199. return err
  200. }
  201. pc := tempconfigfile.Name()
  202. defer os.Remove(pc)
  203. configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplJSON)
  204. if err != nil {
  205. return fmt.Errorf("failed to parse test template: %v", err)
  206. }
  207. dataConfig := struct {
  208. KubeConfig string
  209. AllowTTL int
  210. DenyTTL int
  211. RetryBackoff int
  212. DefaultAllow bool
  213. }{
  214. KubeConfig: p,
  215. AllowTTL: 500,
  216. DenyTTL: 500,
  217. RetryBackoff: 500,
  218. DefaultAllow: true,
  219. }
  220. if err := configTmpl.Execute(tempconfigfile, dataConfig); err != nil {
  221. return fmt.Errorf("failed to execute test template: %v", err)
  222. }
  223. // Create a new admission controller
  224. configFile, err := os.Open(pc)
  225. if err != nil {
  226. return fmt.Errorf("failed to read test config: %v", err)
  227. }
  228. defer configFile.Close()
  229. _, err = NewImagePolicyWebhook(configFile)
  230. return err
  231. }()
  232. if err != nil && !tt.wantErr {
  233. t.Errorf("failed to load plugin from config %q: %v", tt.msg, err)
  234. }
  235. if err == nil && tt.wantErr {
  236. t.Errorf("wanted an error when loading config, did not get one: %q", tt.msg)
  237. }
  238. })
  239. }
  240. }
  241. // Service mocks a remote service.
  242. type Service interface {
  243. Review(*v1alpha1.ImageReview)
  244. HTTPStatusCode() int
  245. }
  246. // NewTestServer wraps a Service as an httptest.Server.
  247. func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error) {
  248. var tlsConfig *tls.Config
  249. if cert != nil {
  250. cert, err := tls.X509KeyPair(cert, key)
  251. if err != nil {
  252. return nil, err
  253. }
  254. tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
  255. }
  256. if caCert != nil {
  257. rootCAs := x509.NewCertPool()
  258. rootCAs.AppendCertsFromPEM(caCert)
  259. if tlsConfig == nil {
  260. tlsConfig = &tls.Config{}
  261. }
  262. tlsConfig.ClientCAs = rootCAs
  263. tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
  264. }
  265. serveHTTP := func(w http.ResponseWriter, r *http.Request) {
  266. var review v1alpha1.ImageReview
  267. if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
  268. http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
  269. return
  270. }
  271. if s.HTTPStatusCode() < 200 || s.HTTPStatusCode() >= 300 {
  272. http.Error(w, "HTTP Error", s.HTTPStatusCode())
  273. return
  274. }
  275. s.Review(&review)
  276. type status struct {
  277. Allowed bool `json:"allowed"`
  278. Reason string `json:"reason"`
  279. AuditAnnotations map[string]string `json:"auditAnnotations"`
  280. }
  281. resp := struct {
  282. APIVersion string `json:"apiVersion"`
  283. Kind string `json:"kind"`
  284. Status status `json:"status"`
  285. }{
  286. APIVersion: v1alpha1.SchemeGroupVersion.String(),
  287. Kind: "ImageReview",
  288. Status: status{
  289. review.Status.Allowed,
  290. review.Status.Reason,
  291. review.Status.AuditAnnotations,
  292. },
  293. }
  294. w.Header().Set("Content-Type", "application/json")
  295. json.NewEncoder(w).Encode(resp)
  296. }
  297. server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
  298. server.TLS = tlsConfig
  299. server.StartTLS()
  300. return server, nil
  301. }
  302. // A service that can be set to allow all or deny all authorization requests.
  303. type mockService struct {
  304. allow bool
  305. statusCode int
  306. outAnnotations map[string]string
  307. }
  308. func (m *mockService) Review(r *v1alpha1.ImageReview) {
  309. r.Status.Allowed = m.allow
  310. // hardcoded overrides
  311. if r.Spec.Containers[0].Image == "good" {
  312. r.Status.Allowed = true
  313. }
  314. for _, c := range r.Spec.Containers {
  315. if c.Image == "bad" {
  316. r.Status.Allowed = false
  317. }
  318. }
  319. if !r.Status.Allowed {
  320. r.Status.Reason = "not allowed"
  321. }
  322. r.Status.AuditAnnotations = m.outAnnotations
  323. }
  324. func (m *mockService) Allow() { m.allow = true }
  325. func (m *mockService) Deny() { m.allow = false }
  326. func (m *mockService) HTTPStatusCode() int { return m.statusCode }
  327. // newImagePolicyWebhook creates a temporary kubeconfig file from the provided arguments and attempts to load
  328. // a new newImagePolicyWebhook from it.
  329. func newImagePolicyWebhook(callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, defaultAllow bool) (*Plugin, error) {
  330. tempfile, err := ioutil.TempFile("", "")
  331. if err != nil {
  332. return nil, err
  333. }
  334. p := tempfile.Name()
  335. defer os.Remove(p)
  336. config := v1.Config{
  337. Clusters: []v1.NamedCluster{
  338. {
  339. Cluster: v1.Cluster{Server: callbackURL, CertificateAuthorityData: ca},
  340. },
  341. },
  342. AuthInfos: []v1.NamedAuthInfo{
  343. {
  344. AuthInfo: v1.AuthInfo{ClientCertificateData: clientCert, ClientKeyData: clientKey},
  345. },
  346. },
  347. }
  348. if err := json.NewEncoder(tempfile).Encode(config); err != nil {
  349. return nil, err
  350. }
  351. tempconfigfile, err := ioutil.TempFile("", "")
  352. if err != nil {
  353. return nil, err
  354. }
  355. pc := tempconfigfile.Name()
  356. defer os.Remove(pc)
  357. configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplYAML)
  358. if err != nil {
  359. return nil, fmt.Errorf("failed to parse test template: %v", err)
  360. }
  361. dataConfig := struct {
  362. KubeConfig string
  363. AllowTTL int64
  364. DenyTTL int64
  365. RetryBackoff int64
  366. DefaultAllow bool
  367. }{
  368. KubeConfig: p,
  369. AllowTTL: cacheTime.Nanoseconds(),
  370. DenyTTL: cacheTime.Nanoseconds(),
  371. RetryBackoff: 0,
  372. DefaultAllow: defaultAllow,
  373. }
  374. if err := configTmpl.Execute(tempconfigfile, dataConfig); err != nil {
  375. return nil, fmt.Errorf("failed to execute test template: %v", err)
  376. }
  377. // Create a new admission controller
  378. configFile, err := os.Open(pc)
  379. if err != nil {
  380. return nil, fmt.Errorf("failed to read test config: %v", err)
  381. }
  382. defer configFile.Close()
  383. wh, err := NewImagePolicyWebhook(configFile)
  384. if err != nil {
  385. return nil, err
  386. }
  387. return wh, err
  388. }
  389. func TestTLSConfig(t *testing.T) {
  390. tests := []struct {
  391. test string
  392. clientCert, clientKey, clientCA []byte
  393. serverCert, serverKey, serverCA []byte
  394. wantAllowed, wantErr bool
  395. }{
  396. {
  397. test: "TLS setup between client and server",
  398. clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
  399. serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
  400. wantAllowed: true,
  401. },
  402. {
  403. test: "Server does not require client auth",
  404. clientCA: caCert,
  405. serverCert: serverCert, serverKey: serverKey,
  406. wantAllowed: true,
  407. },
  408. {
  409. test: "Server does not require client auth, client provides it",
  410. clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
  411. serverCert: serverCert, serverKey: serverKey,
  412. wantAllowed: true,
  413. },
  414. {
  415. test: "Client does not trust server",
  416. clientCert: clientCert, clientKey: clientKey,
  417. serverCert: serverCert, serverKey: serverKey,
  418. wantErr: true,
  419. },
  420. {
  421. test: "Server does not trust client",
  422. clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
  423. serverCert: serverCert, serverKey: serverKey, serverCA: badCACert,
  424. wantErr: true,
  425. },
  426. {
  427. // Plugin does not support insecure configurations.
  428. test: "Server is using insecure connection",
  429. wantErr: true,
  430. },
  431. }
  432. for _, tt := range tests {
  433. // Use a closure so defer statements trigger between loop iterations.
  434. t.Run(tt.test, func(t *testing.T) {
  435. service := new(mockService)
  436. service.statusCode = 200
  437. server, err := NewTestServer(service, tt.serverCert, tt.serverKey, tt.serverCA)
  438. if err != nil {
  439. t.Errorf("%s: failed to create server: %v", tt.test, err)
  440. return
  441. }
  442. defer server.Close()
  443. wh, err := newImagePolicyWebhook(server.URL, tt.clientCert, tt.clientKey, tt.clientCA, -1, false)
  444. if err != nil {
  445. t.Errorf("%s: failed to create client: %v", tt.test, err)
  446. return
  447. }
  448. pod := goodPod(strconv.Itoa(rand.Intn(1000)))
  449. attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{})
  450. // Allow all and see if we get an error.
  451. service.Allow()
  452. err = wh.Validate(context.TODO(), attr, nil)
  453. if tt.wantAllowed {
  454. if err != nil {
  455. t.Errorf("expected successful admission")
  456. }
  457. } else {
  458. if err == nil {
  459. t.Errorf("expected failed admission")
  460. }
  461. }
  462. if tt.wantErr {
  463. if err == nil {
  464. t.Errorf("expected error making admission request: %v", err)
  465. }
  466. return
  467. }
  468. if err != nil {
  469. t.Errorf("%s: failed to admit with AllowAll policy: %v", tt.test, err)
  470. return
  471. }
  472. service.Deny()
  473. if err := wh.Validate(context.TODO(), attr, nil); err == nil {
  474. t.Errorf("%s: incorrectly admitted with DenyAll policy", tt.test)
  475. }
  476. })
  477. }
  478. }
  479. type webhookCacheTestCase struct {
  480. statusCode int
  481. expectedErr bool
  482. expectedAuthorized bool
  483. expectedCached bool
  484. }
  485. func testWebhookCacheCases(t *testing.T, serv *mockService, wh *Plugin, attr admission.Attributes, tests []webhookCacheTestCase) {
  486. for _, test := range tests {
  487. serv.statusCode = test.statusCode
  488. err := wh.Validate(context.TODO(), attr, nil)
  489. authorized := err == nil
  490. if test.expectedErr && err == nil {
  491. t.Errorf("Expected error")
  492. } else if !test.expectedErr && err != nil {
  493. t.Fatal(err)
  494. }
  495. if test.expectedAuthorized && !authorized {
  496. if test.expectedCached {
  497. t.Errorf("Webhook should have successful response cached, but authorizer reported unauthorized.")
  498. } else {
  499. t.Errorf("Webhook returned HTTP %d, but authorizer reported unauthorized.", test.statusCode)
  500. }
  501. } else if !test.expectedAuthorized && authorized {
  502. t.Errorf("Webhook returned HTTP %d, but authorizer reported success.", test.statusCode)
  503. }
  504. }
  505. }
  506. // TestWebhookCache verifies that error responses from the server are not
  507. // cached, but successful responses are.
  508. func TestWebhookCache(t *testing.T) {
  509. serv := new(mockService)
  510. s, err := NewTestServer(serv, serverCert, serverKey, caCert)
  511. if err != nil {
  512. t.Fatal(err)
  513. }
  514. defer s.Close()
  515. // Create an admission controller that caches successful responses.
  516. wh, err := newImagePolicyWebhook(s.URL, clientCert, clientKey, caCert, 200, false)
  517. if err != nil {
  518. t.Fatal(err)
  519. }
  520. tests := []webhookCacheTestCase{
  521. {statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
  522. {statusCode: 404, expectedErr: true, expectedAuthorized: false, expectedCached: false},
  523. {statusCode: 403, expectedErr: true, expectedAuthorized: false, expectedCached: false},
  524. {statusCode: 401, expectedErr: true, expectedAuthorized: false, expectedCached: false},
  525. {statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
  526. {statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
  527. }
  528. attr := admission.NewAttributesRecord(goodPod("test"), nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{})
  529. serv.allow = true
  530. testWebhookCacheCases(t, serv, wh, attr, tests)
  531. // For a different request, webhook should be called again.
  532. tests = []webhookCacheTestCase{
  533. {statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
  534. {statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
  535. {statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
  536. }
  537. attr = admission.NewAttributesRecord(goodPod("test2"), nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{})
  538. testWebhookCacheCases(t, serv, wh, attr, tests)
  539. }
  540. func TestContainerCombinations(t *testing.T) {
  541. tests := []struct {
  542. test string
  543. pod *api.Pod
  544. wantAllowed, wantErr bool
  545. }{
  546. {
  547. test: "Single container allowed",
  548. pod: goodPod("good"),
  549. wantAllowed: true,
  550. },
  551. {
  552. test: "Single container denied",
  553. pod: goodPod("bad"),
  554. wantAllowed: false,
  555. wantErr: true,
  556. },
  557. {
  558. test: "One good container, one bad",
  559. pod: &api.Pod{
  560. Spec: api.PodSpec{
  561. ServiceAccountName: "default",
  562. SecurityContext: &api.PodSecurityContext{},
  563. Containers: []api.Container{
  564. {
  565. Image: "bad",
  566. SecurityContext: &api.SecurityContext{},
  567. },
  568. {
  569. Image: "good",
  570. SecurityContext: &api.SecurityContext{},
  571. },
  572. },
  573. },
  574. },
  575. wantAllowed: false,
  576. wantErr: true,
  577. },
  578. {
  579. test: "Multiple good containers",
  580. pod: &api.Pod{
  581. Spec: api.PodSpec{
  582. ServiceAccountName: "default",
  583. SecurityContext: &api.PodSecurityContext{},
  584. Containers: []api.Container{
  585. {
  586. Image: "good",
  587. SecurityContext: &api.SecurityContext{},
  588. },
  589. {
  590. Image: "good",
  591. SecurityContext: &api.SecurityContext{},
  592. },
  593. },
  594. },
  595. },
  596. wantAllowed: true,
  597. wantErr: false,
  598. },
  599. {
  600. test: "Multiple bad containers",
  601. pod: &api.Pod{
  602. Spec: api.PodSpec{
  603. ServiceAccountName: "default",
  604. SecurityContext: &api.PodSecurityContext{},
  605. Containers: []api.Container{
  606. {
  607. Image: "bad",
  608. SecurityContext: &api.SecurityContext{},
  609. },
  610. {
  611. Image: "bad",
  612. SecurityContext: &api.SecurityContext{},
  613. },
  614. },
  615. },
  616. },
  617. wantAllowed: false,
  618. wantErr: true,
  619. },
  620. {
  621. test: "Good container, bad init container",
  622. pod: &api.Pod{
  623. Spec: api.PodSpec{
  624. ServiceAccountName: "default",
  625. SecurityContext: &api.PodSecurityContext{},
  626. Containers: []api.Container{
  627. {
  628. Image: "good",
  629. SecurityContext: &api.SecurityContext{},
  630. },
  631. },
  632. InitContainers: []api.Container{
  633. {
  634. Image: "bad",
  635. SecurityContext: &api.SecurityContext{},
  636. },
  637. },
  638. },
  639. },
  640. wantAllowed: false,
  641. wantErr: true,
  642. },
  643. {
  644. test: "Bad container, good init container",
  645. pod: &api.Pod{
  646. Spec: api.PodSpec{
  647. ServiceAccountName: "default",
  648. SecurityContext: &api.PodSecurityContext{},
  649. Containers: []api.Container{
  650. {
  651. Image: "bad",
  652. SecurityContext: &api.SecurityContext{},
  653. },
  654. },
  655. InitContainers: []api.Container{
  656. {
  657. Image: "good",
  658. SecurityContext: &api.SecurityContext{},
  659. },
  660. },
  661. },
  662. },
  663. wantAllowed: false,
  664. wantErr: true,
  665. },
  666. {
  667. test: "Good container, good init container",
  668. pod: &api.Pod{
  669. Spec: api.PodSpec{
  670. ServiceAccountName: "default",
  671. SecurityContext: &api.PodSecurityContext{},
  672. Containers: []api.Container{
  673. {
  674. Image: "good",
  675. SecurityContext: &api.SecurityContext{},
  676. },
  677. },
  678. InitContainers: []api.Container{
  679. {
  680. Image: "good",
  681. SecurityContext: &api.SecurityContext{},
  682. },
  683. },
  684. },
  685. },
  686. wantAllowed: true,
  687. wantErr: false,
  688. },
  689. }
  690. for _, tt := range tests {
  691. // Use a closure so defer statements trigger between loop iterations.
  692. t.Run(tt.test, func(t *testing.T) {
  693. service := new(mockService)
  694. service.statusCode = 200
  695. server, err := NewTestServer(service, serverCert, serverKey, caCert)
  696. if err != nil {
  697. t.Errorf("%s: failed to create server: %v", tt.test, err)
  698. return
  699. }
  700. defer server.Close()
  701. wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, false)
  702. if err != nil {
  703. t.Errorf("%s: failed to create client: %v", tt.test, err)
  704. return
  705. }
  706. attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{})
  707. err = wh.Validate(context.TODO(), attr, nil)
  708. if tt.wantAllowed {
  709. if err != nil {
  710. t.Errorf("expected successful admission: %s", tt.test)
  711. }
  712. } else {
  713. if err == nil {
  714. t.Errorf("expected failed admission: %s", tt.test)
  715. }
  716. }
  717. if tt.wantErr {
  718. if err == nil {
  719. t.Errorf("expected error making admission request: %v", err)
  720. }
  721. return
  722. }
  723. if err != nil {
  724. t.Errorf("%s: failed to admit: %v", tt.test, err)
  725. return
  726. }
  727. })
  728. }
  729. }
  730. // fakeAttributes decorate kadmission.Attributes. It's used to trace the added annotations.
  731. type fakeAttributes struct {
  732. admission.Attributes
  733. annotations map[string]string
  734. }
  735. func (f fakeAttributes) AddAnnotation(k, v string) error {
  736. f.annotations[k] = v
  737. return f.Attributes.AddAnnotation(k, v)
  738. }
  739. func TestDefaultAllow(t *testing.T) {
  740. tests := []struct {
  741. test string
  742. pod *api.Pod
  743. defaultAllow bool
  744. wantAllowed, wantErr, wantFailOpen bool
  745. }{
  746. {
  747. test: "DefaultAllow = true, backend unreachable, bad image",
  748. pod: goodPod("bad"),
  749. defaultAllow: true,
  750. wantAllowed: true,
  751. wantFailOpen: true,
  752. },
  753. {
  754. test: "DefaultAllow = true, backend unreachable, good image",
  755. pod: goodPod("good"),
  756. defaultAllow: true,
  757. wantAllowed: true,
  758. wantFailOpen: true,
  759. },
  760. {
  761. test: "DefaultAllow = false, backend unreachable, good image",
  762. pod: goodPod("good"),
  763. defaultAllow: false,
  764. wantAllowed: false,
  765. wantErr: true,
  766. wantFailOpen: false,
  767. },
  768. {
  769. test: "DefaultAllow = false, backend unreachable, bad image",
  770. pod: goodPod("bad"),
  771. defaultAllow: false,
  772. wantAllowed: false,
  773. wantErr: true,
  774. wantFailOpen: false,
  775. },
  776. }
  777. for _, tt := range tests {
  778. // Use a closure so defer statements trigger between loop iterations.
  779. t.Run(tt.test, func(t *testing.T) {
  780. service := new(mockService)
  781. service.statusCode = 500
  782. server, err := NewTestServer(service, serverCert, serverKey, caCert)
  783. if err != nil {
  784. t.Errorf("%s: failed to create server: %v", tt.test, err)
  785. return
  786. }
  787. defer server.Close()
  788. wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, tt.defaultAllow)
  789. if err != nil {
  790. t.Errorf("%s: failed to create client: %v", tt.test, err)
  791. return
  792. }
  793. attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{})
  794. annotations := make(map[string]string)
  795. attr = &fakeAttributes{attr, annotations}
  796. err = wh.Validate(context.TODO(), attr, nil)
  797. if tt.wantAllowed {
  798. if err != nil {
  799. t.Errorf("expected successful admission")
  800. }
  801. } else {
  802. if err == nil {
  803. t.Errorf("expected failed admission")
  804. }
  805. }
  806. if tt.wantErr {
  807. if err == nil {
  808. t.Errorf("expected error making admission request: %v", err)
  809. }
  810. return
  811. }
  812. if err != nil {
  813. t.Errorf("%s: failed to admit: %v", tt.test, err)
  814. return
  815. }
  816. podAnnotations := tt.pod.GetAnnotations()
  817. if tt.wantFailOpen {
  818. if podAnnotations == nil || podAnnotations[api.ImagePolicyFailedOpenKey] != "true" {
  819. t.Errorf("missing expected fail open pod annotation")
  820. }
  821. if annotations[AuditKeyPrefix+ImagePolicyFailedOpenKeySuffix] != "true" {
  822. t.Errorf("missing expected fail open attributes annotation")
  823. }
  824. } else {
  825. if podAnnotations != nil && podAnnotations[api.ImagePolicyFailedOpenKey] == "true" {
  826. t.Errorf("found unexpected fail open pod annotation")
  827. }
  828. if annotations[AuditKeyPrefix+ImagePolicyFailedOpenKeySuffix] == "true" {
  829. t.Errorf("found unexpected fail open attributes annotation")
  830. }
  831. }
  832. })
  833. }
  834. }
  835. // A service that can record annotations sent to it
  836. type annotationService struct {
  837. annotations map[string]string
  838. }
  839. func (a *annotationService) Review(r *v1alpha1.ImageReview) {
  840. a.annotations = make(map[string]string)
  841. for k, v := range r.Spec.Annotations {
  842. a.annotations[k] = v
  843. }
  844. r.Status.Allowed = true
  845. }
  846. func (a *annotationService) HTTPStatusCode() int { return 200 }
  847. func (a *annotationService) Annotations() map[string]string { return a.annotations }
  848. func TestAnnotationFiltering(t *testing.T) {
  849. tests := []struct {
  850. test string
  851. annotations map[string]string
  852. outAnnotations map[string]string
  853. }{
  854. {
  855. test: "all annotations filtered out",
  856. annotations: map[string]string{
  857. "test": "test",
  858. "another": "annotation",
  859. "": "",
  860. },
  861. outAnnotations: map[string]string{},
  862. },
  863. {
  864. test: "image-policy annotations allowed",
  865. annotations: map[string]string{
  866. "my.image-policy.k8s.io/test": "test",
  867. "other.image-policy.k8s.io/test2": "annotation",
  868. "test": "test",
  869. "another": "another",
  870. "": "",
  871. },
  872. outAnnotations: map[string]string{
  873. "my.image-policy.k8s.io/test": "test",
  874. "other.image-policy.k8s.io/test2": "annotation",
  875. },
  876. },
  877. }
  878. for _, tt := range tests {
  879. // Use a closure so defer statements trigger between loop iterations.
  880. t.Run(tt.test, func(t *testing.T) {
  881. service := new(annotationService)
  882. server, err := NewTestServer(service, serverCert, serverKey, caCert)
  883. if err != nil {
  884. t.Errorf("%s: failed to create server: %v", tt.test, err)
  885. return
  886. }
  887. defer server.Close()
  888. wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, true)
  889. if err != nil {
  890. t.Errorf("%s: failed to create client: %v", tt.test, err)
  891. return
  892. }
  893. pod := goodPod("test")
  894. pod.Annotations = tt.annotations
  895. attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{})
  896. err = wh.Validate(context.TODO(), attr, nil)
  897. if err != nil {
  898. t.Errorf("expected successful admission")
  899. }
  900. if !reflect.DeepEqual(tt.outAnnotations, service.Annotations()) {
  901. t.Errorf("expected annotations sent to webhook: %v to match expected: %v", service.Annotations(), tt.outAnnotations)
  902. }
  903. })
  904. }
  905. }
  906. func TestReturnedAnnotationAdd(t *testing.T) {
  907. tests := []struct {
  908. test string
  909. pod *api.Pod
  910. verifierAnnotations map[string]string
  911. expectedAnnotations map[string]string
  912. wantErr bool
  913. }{
  914. {
  915. test: "Add valid response annotations",
  916. pod: goodPod("good"),
  917. verifierAnnotations: map[string]string{
  918. "foo-test": "true",
  919. "bar-test": "false",
  920. },
  921. expectedAnnotations: map[string]string{
  922. "imagepolicywebhook.image-policy.k8s.io/foo-test": "true",
  923. "imagepolicywebhook.image-policy.k8s.io/bar-test": "false",
  924. },
  925. },
  926. {
  927. test: "No returned annotations are ignored",
  928. pod: goodPod("good"),
  929. verifierAnnotations: map[string]string{},
  930. expectedAnnotations: map[string]string{},
  931. },
  932. {
  933. test: "Handles nil annotations",
  934. pod: goodPod("good"),
  935. verifierAnnotations: nil,
  936. expectedAnnotations: map[string]string{},
  937. },
  938. {
  939. test: "Adds annotations for bad request",
  940. pod: &api.Pod{
  941. Spec: api.PodSpec{
  942. ServiceAccountName: "default",
  943. SecurityContext: &api.PodSecurityContext{},
  944. Containers: []api.Container{
  945. {
  946. Image: "bad",
  947. SecurityContext: &api.SecurityContext{},
  948. },
  949. },
  950. },
  951. },
  952. verifierAnnotations: map[string]string{
  953. "foo-test": "false",
  954. },
  955. expectedAnnotations: map[string]string{
  956. "imagepolicywebhook.image-policy.k8s.io/foo-test": "false",
  957. },
  958. wantErr: true,
  959. },
  960. }
  961. for _, tt := range tests {
  962. // Use a closure so defer statements trigger between loop iterations.
  963. t.Run(tt.test, func(t *testing.T) {
  964. service := new(mockService)
  965. service.statusCode = 200
  966. service.outAnnotations = tt.verifierAnnotations
  967. server, err := NewTestServer(service, serverCert, serverKey, caCert)
  968. if err != nil {
  969. t.Errorf("%s: failed to create server: %v", tt.test, err)
  970. return
  971. }
  972. defer server.Close()
  973. wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, true)
  974. if err != nil {
  975. t.Errorf("%s: failed to create client: %v", tt.test, err)
  976. return
  977. }
  978. pod := tt.pod
  979. attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{})
  980. annotations := make(map[string]string)
  981. attr = &fakeAttributes{attr, annotations}
  982. err = wh.Validate(context.TODO(), attr, nil)
  983. if tt.wantErr {
  984. if err == nil {
  985. t.Errorf("%s: expected error making admission request: %v", tt.test, err)
  986. }
  987. } else if err != nil {
  988. t.Errorf("%s: failed to admit: %v", tt.test, err)
  989. }
  990. if !reflect.DeepEqual(annotations, tt.expectedAnnotations) {
  991. t.Errorf("got audit annotations: %v; want: %v", annotations, tt.expectedAnnotations)
  992. }
  993. })
  994. }
  995. }
  996. func goodPod(containerID string) *api.Pod {
  997. return &api.Pod{
  998. Spec: api.PodSpec{
  999. ServiceAccountName: "default",
  1000. SecurityContext: &api.PodSecurityContext{},
  1001. Containers: []api.Container{
  1002. {
  1003. Image: containerID,
  1004. SecurityContext: &api.SecurityContext{},
  1005. },
  1006. },
  1007. },
  1008. }
  1009. }