admission_test.go 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383
  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 admissionwebhook
  14. import (
  15. "crypto/tls"
  16. "crypto/x509"
  17. "encoding/json"
  18. "fmt"
  19. "io/ioutil"
  20. "net/http"
  21. "net/http/httptest"
  22. "sort"
  23. "strings"
  24. "sync"
  25. "testing"
  26. "time"
  27. "k8s.io/api/admission/v1beta1"
  28. admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1"
  29. appsv1beta1 "k8s.io/api/apps/v1beta1"
  30. corev1 "k8s.io/api/core/v1"
  31. v1 "k8s.io/api/core/v1"
  32. extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
  33. policyv1beta1 "k8s.io/api/policy/v1beta1"
  34. apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
  35. "k8s.io/apimachinery/pkg/api/errors"
  36. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  37. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  38. "k8s.io/apimachinery/pkg/runtime"
  39. "k8s.io/apimachinery/pkg/runtime/schema"
  40. "k8s.io/apimachinery/pkg/types"
  41. "k8s.io/apimachinery/pkg/util/sets"
  42. "k8s.io/apimachinery/pkg/util/wait"
  43. dynamic "k8s.io/client-go/dynamic"
  44. clientset "k8s.io/client-go/kubernetes"
  45. "k8s.io/client-go/rest"
  46. "k8s.io/client-go/util/retry"
  47. kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
  48. "k8s.io/kubernetes/test/integration/etcd"
  49. "k8s.io/kubernetes/test/integration/framework"
  50. )
  51. const (
  52. testNamespace = "webhook-integration"
  53. testClientUsername = "webhook-integration-client"
  54. mutation = "mutation"
  55. validation = "validation"
  56. )
  57. type testContext struct {
  58. t *testing.T
  59. admissionHolder *holder
  60. client dynamic.Interface
  61. clientset clientset.Interface
  62. verb string
  63. gvr schema.GroupVersionResource
  64. resource metav1.APIResource
  65. resources map[schema.GroupVersionResource]metav1.APIResource
  66. }
  67. type testFunc func(*testContext)
  68. var (
  69. // defaultResourceFuncs holds the default test functions.
  70. // may be overridden for specific resources by customTestFuncs.
  71. defaultResourceFuncs = map[string]testFunc{
  72. "create": testResourceCreate,
  73. "update": testResourceUpdate,
  74. "patch": testResourcePatch,
  75. "delete": testResourceDelete,
  76. "deletecollection": testResourceDeletecollection,
  77. }
  78. // defaultSubresourceFuncs holds default subresource test functions.
  79. // may be overridden for specific resources by customTestFuncs.
  80. defaultSubresourceFuncs = map[string]testFunc{
  81. "update": testSubresourceUpdate,
  82. "patch": testSubresourcePatch,
  83. }
  84. // customTestFuncs holds custom test functions by resource and verb.
  85. customTestFuncs = map[schema.GroupVersionResource]map[string]testFunc{
  86. gvr("", "v1", "namespaces"): {"delete": testNamespaceDelete},
  87. gvr("apps", "v1beta1", "deployments/rollback"): {"create": testDeploymentRollback},
  88. gvr("extensions", "v1beta1", "deployments/rollback"): {"create": testDeploymentRollback},
  89. gvr("", "v1", "pods/attach"): {"create": testPodConnectSubresource},
  90. gvr("", "v1", "pods/exec"): {"create": testPodConnectSubresource},
  91. gvr("", "v1", "pods/portforward"): {"create": testPodConnectSubresource},
  92. gvr("", "v1", "bindings"): {"create": testPodBindingEviction},
  93. gvr("", "v1", "pods/binding"): {"create": testPodBindingEviction},
  94. gvr("", "v1", "pods/eviction"): {"create": testPodBindingEviction},
  95. gvr("", "v1", "nodes/proxy"): {"*": testSubresourceProxy},
  96. gvr("", "v1", "pods/proxy"): {"*": testSubresourceProxy},
  97. gvr("", "v1", "services/proxy"): {"*": testSubresourceProxy},
  98. gvr("random.numbers.com", "v1", "integers"): {"create": testPruningRandomNumbers},
  99. gvr("custom.fancy.com", "v2", "pants"): {"create": testNoPruningCustomFancy},
  100. }
  101. // admissionExemptResources lists objects which are exempt from admission validation/mutation,
  102. // only resources exempted from admission processing by API server should be listed here.
  103. admissionExemptResources = map[schema.GroupVersionResource]bool{
  104. gvr("admissionregistration.k8s.io", "v1beta1", "mutatingwebhookconfigurations"): true,
  105. gvr("admissionregistration.k8s.io", "v1beta1", "validatingwebhookconfigurations"): true,
  106. }
  107. parentResources = map[schema.GroupVersionResource]schema.GroupVersionResource{
  108. gvr("extensions", "v1beta1", "replicationcontrollers/scale"): gvr("", "v1", "replicationcontrollers"),
  109. }
  110. // stubDataOverrides holds either non persistent resources' definitions or resources where default stub needs to be overridden.
  111. stubDataOverrides = map[schema.GroupVersionResource]string{
  112. // Non persistent Reviews resource
  113. gvr("authentication.k8s.io", "v1", "tokenreviews"): `{"metadata": {"name": "tokenreview"}, "spec": {"token": "token", "audience": ["audience1","audience2"]}}`,
  114. gvr("authentication.k8s.io", "v1beta1", "tokenreviews"): `{"metadata": {"name": "tokenreview"}, "spec": {"token": "token", "audience": ["audience1","audience2"]}}`,
  115. gvr("authorization.k8s.io", "v1", "localsubjectaccessreviews"): `{"metadata": {"name": "", "namespace":"` + testNamespace + `"}, "spec": {"uid": "token", "user": "user1","groups": ["group1","group2"],"resourceAttributes": {"name":"name1","namespace":"` + testNamespace + `"}}}`,
  116. gvr("authorization.k8s.io", "v1", "subjectaccessreviews"): `{"metadata": {"name": "", "namespace":""}, "spec": {"user":"user1","resourceAttributes": {"name":"name1", "namespace":"` + testNamespace + `"}}}`,
  117. gvr("authorization.k8s.io", "v1", "selfsubjectaccessreviews"): `{"metadata": {"name": "", "namespace":""}, "spec": {"resourceAttributes": {"name":"name1", "namespace":""}}}`,
  118. gvr("authorization.k8s.io", "v1", "selfsubjectrulesreviews"): `{"metadata": {"name": "", "namespace":"` + testNamespace + `"}, "spec": {"namespace":"` + testNamespace + `"}}`,
  119. gvr("authorization.k8s.io", "v1beta1", "localsubjectaccessreviews"): `{"metadata": {"name": "", "namespace":"` + testNamespace + `"}, "spec": {"uid": "token", "user": "user1","groups": ["group1","group2"],"resourceAttributes": {"name":"name1","namespace":"` + testNamespace + `"}}}`,
  120. gvr("authorization.k8s.io", "v1beta1", "subjectaccessreviews"): `{"metadata": {"name": "", "namespace":""}, "spec": {"user":"user1","resourceAttributes": {"name":"name1", "namespace":"` + testNamespace + `"}}}`,
  121. gvr("authorization.k8s.io", "v1beta1", "selfsubjectaccessreviews"): `{"metadata": {"name": "", "namespace":""}, "spec": {"resourceAttributes": {"name":"name1", "namespace":""}}}`,
  122. gvr("authorization.k8s.io", "v1beta1", "selfsubjectrulesreviews"): `{"metadata": {"name": "", "namespace":"` + testNamespace + `"}, "spec": {"namespace":"` + testNamespace + `"}}`,
  123. // Other Non persistent resources
  124. }
  125. )
  126. type webhookOptions struct {
  127. // phase indicates whether this is a mutating or validating webhook
  128. phase string
  129. // converted indicates if this webhook makes use of matchPolicy:equivalent and expects conversion.
  130. // if true, recordGVR and expectGVK are mapped through gvrToConvertedGVR/gvrToConvertedGVK.
  131. // if false, recordGVR and expectGVK are compared directly to the admission review.
  132. converted bool
  133. }
  134. type holder struct {
  135. lock sync.RWMutex
  136. t *testing.T
  137. recordGVR metav1.GroupVersionResource
  138. recordOperation v1beta1.Operation
  139. recordNamespace string
  140. recordName string
  141. expectGVK schema.GroupVersionKind
  142. expectObject bool
  143. expectOldObject bool
  144. expectOptionsGVK schema.GroupVersionKind
  145. expectOptions bool
  146. // gvrToConvertedGVR maps the GVR submitted to the API server to the expected GVR when converted to the webhook-recognized resource.
  147. // When a converted request is recorded, gvrToConvertedGVR[recordGVR] is compared to the GVR seen by the webhook.
  148. gvrToConvertedGVR map[metav1.GroupVersionResource]metav1.GroupVersionResource
  149. // gvrToConvertedGVR maps the GVR submitted to the API server to the expected GVK when converted to the webhook-recognized resource.
  150. // When a converted request is recorded, gvrToConvertedGVR[expectGVK] is compared to the GVK seen by the webhook.
  151. gvrToConvertedGVK map[metav1.GroupVersionResource]schema.GroupVersionKind
  152. recorded map[webhookOptions]*v1beta1.AdmissionRequest
  153. }
  154. func (h *holder) reset(t *testing.T) {
  155. h.lock.Lock()
  156. defer h.lock.Unlock()
  157. h.t = t
  158. h.recordGVR = metav1.GroupVersionResource{}
  159. h.expectGVK = schema.GroupVersionKind{}
  160. h.recordOperation = ""
  161. h.recordName = ""
  162. h.recordNamespace = ""
  163. h.expectObject = false
  164. h.expectOldObject = false
  165. h.expectOptionsGVK = schema.GroupVersionKind{}
  166. h.expectOptions = false
  167. // Set up the recorded map with nil records for all combinations
  168. h.recorded = map[webhookOptions]*v1beta1.AdmissionRequest{}
  169. for _, phase := range []string{mutation, validation} {
  170. for _, converted := range []bool{true, false} {
  171. h.recorded[webhookOptions{phase: phase, converted: converted}] = nil
  172. }
  173. }
  174. }
  175. func (h *holder) expect(gvr schema.GroupVersionResource, gvk, optionsGVK schema.GroupVersionKind, operation v1beta1.Operation, name, namespace string, object, oldObject, options bool) {
  176. // Special-case namespaces, since the object name shows up in request attributes for update/delete requests
  177. if len(namespace) == 0 && gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Namespace" && operation != v1beta1.Create {
  178. namespace = name
  179. }
  180. h.lock.Lock()
  181. defer h.lock.Unlock()
  182. h.recordGVR = metav1.GroupVersionResource{Group: gvr.Group, Version: gvr.Version, Resource: gvr.Resource}
  183. h.expectGVK = gvk
  184. h.recordOperation = operation
  185. h.recordName = name
  186. h.recordNamespace = namespace
  187. h.expectObject = object
  188. h.expectOldObject = oldObject
  189. h.expectOptionsGVK = optionsGVK
  190. h.expectOptions = options
  191. // Set up the recorded map with nil records for all combinations
  192. h.recorded = map[webhookOptions]*v1beta1.AdmissionRequest{}
  193. for _, phase := range []string{mutation, validation} {
  194. for _, converted := range []bool{true, false} {
  195. h.recorded[webhookOptions{phase: phase, converted: converted}] = nil
  196. }
  197. }
  198. }
  199. func (h *holder) record(phase string, converted bool, request *v1beta1.AdmissionRequest) {
  200. h.lock.Lock()
  201. defer h.lock.Unlock()
  202. // this is useful to turn on if items aren't getting recorded and you need to figure out why
  203. debug := false
  204. if debug {
  205. h.t.Logf("%s %#v %v", request.Operation, request.Resource, request.SubResource)
  206. }
  207. resource := request.Resource
  208. if len(request.SubResource) > 0 {
  209. resource.Resource += "/" + request.SubResource
  210. }
  211. // See if we should record this
  212. gvrToRecord := h.recordGVR
  213. if converted {
  214. // If this is a converted webhook, map to the GVR we expect the webhook to see
  215. gvrToRecord = h.gvrToConvertedGVR[h.recordGVR]
  216. }
  217. if resource != gvrToRecord {
  218. if debug {
  219. h.t.Log(resource, "!=", gvrToRecord)
  220. }
  221. return
  222. }
  223. if request.Operation != h.recordOperation {
  224. if debug {
  225. h.t.Log(request.Operation, "!=", h.recordOperation)
  226. }
  227. return
  228. }
  229. if request.Namespace != h.recordNamespace {
  230. if debug {
  231. h.t.Log(request.Namespace, "!=", h.recordNamespace)
  232. }
  233. return
  234. }
  235. name := request.Name
  236. if name == "" && request.Object.Object != nil {
  237. name = request.Object.Object.(*unstructured.Unstructured).GetName()
  238. }
  239. if name != h.recordName {
  240. if debug {
  241. h.t.Log(name, "!=", h.recordName)
  242. }
  243. return
  244. }
  245. if debug {
  246. h.t.Logf("recording: %#v = %s %#v %v", webhookOptions{phase: phase, converted: converted}, request.Operation, request.Resource, request.SubResource)
  247. }
  248. h.recorded[webhookOptions{phase: phase, converted: converted}] = request
  249. }
  250. func (h *holder) verify(t *testing.T) {
  251. h.lock.Lock()
  252. defer h.lock.Unlock()
  253. for options, value := range h.recorded {
  254. if err := h.verifyRequest(options.converted, value); err != nil {
  255. t.Errorf("phase:%v, converted:%v error: %v", options.phase, options.converted, err)
  256. }
  257. }
  258. }
  259. func (h *holder) verifyRequest(converted bool, request *v1beta1.AdmissionRequest) error {
  260. // Check if current resource should be exempted from Admission processing
  261. if admissionExemptResources[gvr(h.recordGVR.Group, h.recordGVR.Version, h.recordGVR.Resource)] {
  262. if request == nil {
  263. return nil
  264. }
  265. return fmt.Errorf("admission webhook was called, but not supposed to")
  266. }
  267. if request == nil {
  268. return fmt.Errorf("no request received")
  269. }
  270. if h.expectObject {
  271. if err := h.verifyObject(converted, request.Object.Object); err != nil {
  272. return fmt.Errorf("object error: %v", err)
  273. }
  274. } else if request.Object.Object != nil {
  275. return fmt.Errorf("unexpected object: %#v", request.Object.Object)
  276. }
  277. if h.expectOldObject {
  278. if err := h.verifyObject(converted, request.OldObject.Object); err != nil {
  279. return fmt.Errorf("old object error: %v", err)
  280. }
  281. } else if request.OldObject.Object != nil {
  282. return fmt.Errorf("unexpected old object: %#v", request.OldObject.Object)
  283. }
  284. if h.expectOptions {
  285. if err := h.verifyOptions(request.Options.Object); err != nil {
  286. return fmt.Errorf("options error: %v", err)
  287. }
  288. } else if request.Options.Object != nil {
  289. return fmt.Errorf("unexpected options: %#v", request.Options.Object)
  290. }
  291. return nil
  292. }
  293. func (h *holder) verifyObject(converted bool, obj runtime.Object) error {
  294. if obj == nil {
  295. return fmt.Errorf("no object sent")
  296. }
  297. expectGVK := h.expectGVK
  298. if converted {
  299. expectGVK = h.gvrToConvertedGVK[h.recordGVR]
  300. }
  301. if obj.GetObjectKind().GroupVersionKind() != expectGVK {
  302. return fmt.Errorf("expected %#v, got %#v", expectGVK, obj.GetObjectKind().GroupVersionKind())
  303. }
  304. return nil
  305. }
  306. func (h *holder) verifyOptions(options runtime.Object) error {
  307. if options == nil {
  308. return fmt.Errorf("no options sent")
  309. }
  310. if options.GetObjectKind().GroupVersionKind() != h.expectOptionsGVK {
  311. return fmt.Errorf("expected %#v, got %#v", h.expectOptionsGVK, options.GetObjectKind().GroupVersionKind())
  312. }
  313. return nil
  314. }
  315. // TestWebhookV1beta1 tests communication between API server and webhook process.
  316. func TestWebhookV1beta1(t *testing.T) {
  317. // holder communicates expectations to webhooks, and results from webhooks
  318. holder := &holder{
  319. t: t,
  320. gvrToConvertedGVR: map[metav1.GroupVersionResource]metav1.GroupVersionResource{},
  321. gvrToConvertedGVK: map[metav1.GroupVersionResource]schema.GroupVersionKind{},
  322. }
  323. // set up webhook server
  324. roots := x509.NewCertPool()
  325. if !roots.AppendCertsFromPEM(localhostCert) {
  326. t.Fatal("Failed to append Cert from PEM")
  327. }
  328. cert, err := tls.X509KeyPair(localhostCert, localhostKey)
  329. if err != nil {
  330. t.Fatalf("Failed to build cert with error: %+v", err)
  331. }
  332. webhookMux := http.NewServeMux()
  333. webhookMux.Handle("/"+mutation, newWebhookHandler(t, holder, mutation, false))
  334. webhookMux.Handle("/convert/"+mutation, newWebhookHandler(t, holder, mutation, true))
  335. webhookMux.Handle("/"+validation, newWebhookHandler(t, holder, validation, false))
  336. webhookMux.Handle("/convert/"+validation, newWebhookHandler(t, holder, validation, true))
  337. webhookServer := httptest.NewUnstartedServer(webhookMux)
  338. webhookServer.TLS = &tls.Config{
  339. RootCAs: roots,
  340. Certificates: []tls.Certificate{cert},
  341. }
  342. webhookServer.StartTLS()
  343. defer webhookServer.Close()
  344. // start API server
  345. etcdConfig := framework.SharedEtcd()
  346. server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{
  347. // turn off admission plugins that add finalizers
  348. "--disable-admission-plugins=ServiceAccount,StorageObjectInUseProtection",
  349. // force enable all resources so we can check storage.
  350. // TODO: drop these once we stop allowing them to be served.
  351. "--runtime-config=api/all=true,extensions/v1beta1/deployments=true,extensions/v1beta1/daemonsets=true,extensions/v1beta1/replicasets=true,extensions/v1beta1/podsecuritypolicies=true,extensions/v1beta1/networkpolicies=true",
  352. }, etcdConfig)
  353. defer server.TearDownFn()
  354. // Configure a client with a distinct user name so that it is easy to distinguish requests
  355. // made by the client from requests made by controllers. We use this to filter out requests
  356. // before recording them to ensure we don't accidentally mistake requests from controllers
  357. // as requests made by the client.
  358. clientConfig := rest.CopyConfig(server.ClientConfig)
  359. clientConfig.Impersonate.UserName = testClientUsername
  360. clientConfig.Impersonate.Groups = []string{"system:masters", "system:authenticated"}
  361. client, err := clientset.NewForConfig(clientConfig)
  362. if err != nil {
  363. t.Fatalf("unexpected error: %v", err)
  364. }
  365. // create CRDs
  366. etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, etcd.GetCustomResourceDefinitionData()...)
  367. if _, err := client.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}); err != nil {
  368. t.Fatal(err)
  369. }
  370. // gather resources to test
  371. dynamicClient, err := dynamic.NewForConfig(clientConfig)
  372. if err != nil {
  373. t.Fatal(err)
  374. }
  375. _, resources, err := client.Discovery().ServerGroupsAndResources()
  376. if err != nil {
  377. t.Fatalf("Failed to get ServerGroupsAndResources with error: %+v", err)
  378. }
  379. gvrsToTest := []schema.GroupVersionResource{}
  380. resourcesByGVR := map[schema.GroupVersionResource]metav1.APIResource{}
  381. for _, list := range resources {
  382. defaultGroupVersion, err := schema.ParseGroupVersion(list.GroupVersion)
  383. if err != nil {
  384. t.Errorf("Failed to get GroupVersion for: %+v", list)
  385. continue
  386. }
  387. for _, resource := range list.APIResources {
  388. if resource.Group == "" {
  389. resource.Group = defaultGroupVersion.Group
  390. }
  391. if resource.Version == "" {
  392. resource.Version = defaultGroupVersion.Version
  393. }
  394. gvr := defaultGroupVersion.WithResource(resource.Name)
  395. resourcesByGVR[gvr] = resource
  396. if shouldTestResource(gvr, resource) {
  397. gvrsToTest = append(gvrsToTest, gvr)
  398. }
  399. }
  400. }
  401. sort.SliceStable(gvrsToTest, func(i, j int) bool {
  402. if gvrsToTest[i].Group < gvrsToTest[j].Group {
  403. return true
  404. }
  405. if gvrsToTest[i].Group > gvrsToTest[j].Group {
  406. return false
  407. }
  408. if gvrsToTest[i].Version < gvrsToTest[j].Version {
  409. return true
  410. }
  411. if gvrsToTest[i].Version > gvrsToTest[j].Version {
  412. return false
  413. }
  414. if gvrsToTest[i].Resource < gvrsToTest[j].Resource {
  415. return true
  416. }
  417. if gvrsToTest[i].Resource > gvrsToTest[j].Resource {
  418. return false
  419. }
  420. return true
  421. })
  422. // map unqualified resource names to the fully qualified resource we will expect to be converted to
  423. // Note: this only works because there are no overlapping resource names in-process that are not co-located
  424. convertedResources := map[string]schema.GroupVersionResource{}
  425. // build the webhook rules enumerating the specific group/version/resources we want
  426. convertedRules := []admissionv1beta1.RuleWithOperations{}
  427. for _, gvr := range gvrsToTest {
  428. metaGVR := metav1.GroupVersionResource{Group: gvr.Group, Version: gvr.Version, Resource: gvr.Resource}
  429. convertedGVR, ok := convertedResources[gvr.Resource]
  430. if !ok {
  431. // this is the first time we've seen this resource
  432. // record the fully qualified resource we expect
  433. convertedGVR = gvr
  434. convertedResources[gvr.Resource] = gvr
  435. // add an admission rule indicating we can receive this version
  436. convertedRules = append(convertedRules, admissionv1beta1.RuleWithOperations{
  437. Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll},
  438. Rule: admissionv1beta1.Rule{APIGroups: []string{gvr.Group}, APIVersions: []string{gvr.Version}, Resources: []string{gvr.Resource}},
  439. })
  440. }
  441. // record the expected resource and kind
  442. holder.gvrToConvertedGVR[metaGVR] = metav1.GroupVersionResource{Group: convertedGVR.Group, Version: convertedGVR.Version, Resource: convertedGVR.Resource}
  443. holder.gvrToConvertedGVK[metaGVR] = schema.GroupVersionKind{Group: resourcesByGVR[convertedGVR].Group, Version: resourcesByGVR[convertedGVR].Version, Kind: resourcesByGVR[convertedGVR].Kind}
  444. }
  445. if err := createV1beta1MutationWebhook(client, webhookServer.URL+"/"+mutation, webhookServer.URL+"/convert/"+mutation, convertedRules); err != nil {
  446. t.Fatal(err)
  447. }
  448. if err := createV1beta1ValidationWebhook(client, webhookServer.URL+"/"+validation, webhookServer.URL+"/convert/"+validation, convertedRules); err != nil {
  449. t.Fatal(err)
  450. }
  451. // Allow the webhook to establish
  452. time.Sleep(time.Second)
  453. // Test admission on all resources, subresources, and verbs
  454. for _, gvr := range gvrsToTest {
  455. resource := resourcesByGVR[gvr]
  456. t.Run(gvr.Group+"."+gvr.Version+"."+strings.ReplaceAll(resource.Name, "/", "."), func(t *testing.T) {
  457. for _, verb := range []string{"create", "update", "patch", "connect", "delete", "deletecollection"} {
  458. if shouldTestResourceVerb(gvr, resource, verb) {
  459. t.Run(verb, func(t *testing.T) {
  460. holder.reset(t)
  461. testFunc := getTestFunc(gvr, verb)
  462. testFunc(&testContext{
  463. t: t,
  464. admissionHolder: holder,
  465. client: dynamicClient,
  466. clientset: client,
  467. verb: verb,
  468. gvr: gvr,
  469. resource: resource,
  470. resources: resourcesByGVR,
  471. })
  472. holder.verify(t)
  473. })
  474. }
  475. }
  476. })
  477. }
  478. }
  479. //
  480. // generic resource testing
  481. //
  482. func testResourceCreate(c *testContext) {
  483. stubObj, err := getStubObj(c.gvr, c.resource)
  484. if err != nil {
  485. c.t.Error(err)
  486. return
  487. }
  488. ns := ""
  489. if c.resource.Namespaced {
  490. ns = testNamespace
  491. }
  492. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkCreateOptions, v1beta1.Create, stubObj.GetName(), ns, true, false, true)
  493. _, err = c.client.Resource(c.gvr).Namespace(ns).Create(stubObj, metav1.CreateOptions{})
  494. if err != nil {
  495. c.t.Error(err)
  496. return
  497. }
  498. }
  499. func testResourceUpdate(c *testContext) {
  500. if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
  501. obj, err := createOrGetResource(c.client, c.gvr, c.resource)
  502. if err != nil {
  503. return err
  504. }
  505. obj.SetAnnotations(map[string]string{"update": "true"})
  506. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkUpdateOptions, v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true, true)
  507. _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Update(obj, metav1.UpdateOptions{})
  508. return err
  509. }); err != nil {
  510. c.t.Error(err)
  511. return
  512. }
  513. }
  514. func testResourcePatch(c *testContext) {
  515. obj, err := createOrGetResource(c.client, c.gvr, c.resource)
  516. if err != nil {
  517. c.t.Error(err)
  518. return
  519. }
  520. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkUpdateOptions, v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true, true)
  521. _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
  522. obj.GetName(),
  523. types.MergePatchType,
  524. []byte(`{"metadata":{"annotations":{"patch":"true"}}}`),
  525. metav1.PatchOptions{})
  526. if err != nil {
  527. c.t.Error(err)
  528. return
  529. }
  530. }
  531. func testResourceDelete(c *testContext) {
  532. // Verify that an immediate delete triggers the webhook and populates the admisssionRequest.oldObject.
  533. obj, err := createOrGetResource(c.client, c.gvr, c.resource)
  534. if err != nil {
  535. c.t.Error(err)
  536. return
  537. }
  538. background := metav1.DeletePropagationBackground
  539. zero := int64(0)
  540. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkDeleteOptions, v1beta1.Delete, obj.GetName(), obj.GetNamespace(), false, true, true)
  541. err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Delete(obj.GetName(), &metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background})
  542. if err != nil {
  543. c.t.Error(err)
  544. return
  545. }
  546. c.admissionHolder.verify(c.t)
  547. // wait for the item to be gone
  548. err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
  549. obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
  550. if errors.IsNotFound(err) {
  551. return true, nil
  552. }
  553. if err == nil {
  554. c.t.Logf("waiting for %#v to be deleted (name: %s, finalizers: %v)...\n", c.gvr, obj.GetName(), obj.GetFinalizers())
  555. return false, nil
  556. }
  557. return false, err
  558. })
  559. if err != nil {
  560. c.t.Error(err)
  561. return
  562. }
  563. // Verify that an update-on-delete triggers the webhook and populates the admisssionRequest.oldObject.
  564. obj, err = createOrGetResource(c.client, c.gvr, c.resource)
  565. if err != nil {
  566. c.t.Error(err)
  567. return
  568. }
  569. // Adding finalizer to the object, then deleting it.
  570. // We don't add finalizers by setting DeleteOptions.PropagationPolicy
  571. // because some resource (e.g., events) do not support garbage
  572. // collector finalizers.
  573. _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
  574. obj.GetName(),
  575. types.MergePatchType,
  576. []byte(`{"metadata":{"finalizers":["test/k8s.io"]}}`),
  577. metav1.PatchOptions{})
  578. if err != nil {
  579. c.t.Error(err)
  580. return
  581. }
  582. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkDeleteOptions, v1beta1.Delete, obj.GetName(), obj.GetNamespace(), false, true, true)
  583. err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Delete(obj.GetName(), &metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background})
  584. if err != nil {
  585. c.t.Error(err)
  586. return
  587. }
  588. c.admissionHolder.verify(c.t)
  589. // wait other finalizers (e.g., crd's customresourcecleanup finalizer) to be removed.
  590. err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
  591. obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
  592. if err != nil {
  593. return false, err
  594. }
  595. finalizers := obj.GetFinalizers()
  596. if len(finalizers) != 1 {
  597. c.t.Logf("waiting for other finalizers on %#v %s to be removed, existing finalizers are %v", c.gvr, obj.GetName(), obj.GetFinalizers())
  598. return false, nil
  599. }
  600. if finalizers[0] != "test/k8s.io" {
  601. return false, fmt.Errorf("expected the single finalizer on %#v %s to be test/k8s.io, got %v", c.gvr, obj.GetName(), obj.GetFinalizers())
  602. }
  603. return true, nil
  604. })
  605. // remove the finalizer
  606. _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
  607. obj.GetName(),
  608. types.MergePatchType,
  609. []byte(`{"metadata":{"finalizers":[]}}`),
  610. metav1.PatchOptions{})
  611. if err != nil {
  612. c.t.Error(err)
  613. return
  614. }
  615. // wait for the item to be gone
  616. err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
  617. obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
  618. if errors.IsNotFound(err) {
  619. return true, nil
  620. }
  621. if err == nil {
  622. c.t.Logf("waiting for %#v to be deleted (name: %s, finalizers: %v)...\n", c.gvr, obj.GetName(), obj.GetFinalizers())
  623. return false, nil
  624. }
  625. return false, err
  626. })
  627. if err != nil {
  628. c.t.Error(err)
  629. return
  630. }
  631. }
  632. func testResourceDeletecollection(c *testContext) {
  633. obj, err := createOrGetResource(c.client, c.gvr, c.resource)
  634. if err != nil {
  635. c.t.Error(err)
  636. return
  637. }
  638. background := metav1.DeletePropagationBackground
  639. zero := int64(0)
  640. // update the object with a label that matches our selector
  641. _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
  642. obj.GetName(),
  643. types.MergePatchType,
  644. []byte(`{"metadata":{"labels":{"webhooktest":"true"}}}`),
  645. metav1.PatchOptions{})
  646. if err != nil {
  647. c.t.Error(err)
  648. return
  649. }
  650. // set expectations
  651. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkDeleteOptions, v1beta1.Delete, "", obj.GetNamespace(), false, true, true)
  652. // delete
  653. err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).DeleteCollection(&metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background}, metav1.ListOptions{LabelSelector: "webhooktest=true"})
  654. if err != nil {
  655. c.t.Error(err)
  656. return
  657. }
  658. // wait for the item to be gone
  659. err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
  660. obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
  661. if errors.IsNotFound(err) {
  662. return true, nil
  663. }
  664. if err == nil {
  665. c.t.Logf("waiting for %#v to be deleted (name: %s, finalizers: %v)...\n", c.gvr, obj.GetName(), obj.GetFinalizers())
  666. return false, nil
  667. }
  668. return false, err
  669. })
  670. if err != nil {
  671. c.t.Error(err)
  672. return
  673. }
  674. }
  675. func getParentGVR(gvr schema.GroupVersionResource) schema.GroupVersionResource {
  676. parentGVR, found := parentResources[gvr]
  677. // if no special override is found, just drop the subresource
  678. if !found {
  679. parentGVR = gvr
  680. parentGVR.Resource = strings.Split(parentGVR.Resource, "/")[0]
  681. }
  682. return parentGVR
  683. }
  684. func testSubresourceUpdate(c *testContext) {
  685. if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
  686. parentGVR := getParentGVR(c.gvr)
  687. parentResource := c.resources[parentGVR]
  688. obj, err := createOrGetResource(c.client, parentGVR, parentResource)
  689. if err != nil {
  690. return err
  691. }
  692. // Save the parent object as what we submit
  693. submitObj := obj
  694. gvrWithoutSubresources := c.gvr
  695. gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
  696. subresources := strings.Split(c.gvr.Resource, "/")[1:]
  697. // If the subresource supports get, fetch that as the object to submit (namespaces/finalize, */scale, etc)
  698. if sets.NewString(c.resource.Verbs...).Has("get") {
  699. submitObj, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{}, subresources...)
  700. if err != nil {
  701. return err
  702. }
  703. }
  704. // Modify the object
  705. submitObj.SetAnnotations(map[string]string{"subresourceupdate": "true"})
  706. // set expectations
  707. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkUpdateOptions, v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true, true)
  708. _, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Update(
  709. submitObj,
  710. metav1.UpdateOptions{},
  711. subresources...,
  712. )
  713. return err
  714. }); err != nil {
  715. c.t.Error(err)
  716. }
  717. }
  718. func testSubresourcePatch(c *testContext) {
  719. parentGVR := getParentGVR(c.gvr)
  720. parentResource := c.resources[parentGVR]
  721. obj, err := createOrGetResource(c.client, parentGVR, parentResource)
  722. if err != nil {
  723. c.t.Error(err)
  724. return
  725. }
  726. gvrWithoutSubresources := c.gvr
  727. gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
  728. subresources := strings.Split(c.gvr.Resource, "/")[1:]
  729. // set expectations
  730. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkUpdateOptions, v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true, true)
  731. _, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Patch(
  732. obj.GetName(),
  733. types.MergePatchType,
  734. []byte(`{"metadata":{"annotations":{"subresourcepatch":"true"}}}`),
  735. metav1.PatchOptions{},
  736. subresources...,
  737. )
  738. if err != nil {
  739. c.t.Error(err)
  740. return
  741. }
  742. }
  743. func unimplemented(c *testContext) {
  744. c.t.Errorf("Test function for %+v has not been implemented...", c.gvr)
  745. }
  746. //
  747. // custom methods
  748. //
  749. // testNamespaceDelete verifies namespace-specific delete behavior:
  750. // - ensures admission is called on first delete (which only sets deletionTimestamp and terminating state)
  751. // - removes finalizer from namespace
  752. // - ensures admission is called on final delete once finalizers are removed
  753. func testNamespaceDelete(c *testContext) {
  754. obj, err := createOrGetResource(c.client, c.gvr, c.resource)
  755. if err != nil {
  756. c.t.Error(err)
  757. return
  758. }
  759. background := metav1.DeletePropagationBackground
  760. zero := int64(0)
  761. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkDeleteOptions, v1beta1.Delete, obj.GetName(), obj.GetNamespace(), false, true, true)
  762. err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Delete(obj.GetName(), &metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background})
  763. if err != nil {
  764. c.t.Error(err)
  765. return
  766. }
  767. c.admissionHolder.verify(c.t)
  768. // do the finalization so the namespace can be deleted
  769. obj, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
  770. if err != nil {
  771. c.t.Error(err)
  772. return
  773. }
  774. err = unstructured.SetNestedField(obj.Object, nil, "spec", "finalizers")
  775. if err != nil {
  776. c.t.Error(err)
  777. return
  778. }
  779. _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Update(obj, metav1.UpdateOptions{}, "finalize")
  780. if err != nil {
  781. c.t.Error(err)
  782. return
  783. }
  784. // verify namespace is gone
  785. obj, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
  786. if err == nil || !errors.IsNotFound(err) {
  787. c.t.Errorf("expected namespace to be gone, got %#v, %v", obj, err)
  788. }
  789. }
  790. // testDeploymentRollback verifies rollback-specific behavior:
  791. // - creates a parent deployment
  792. // - creates a rollback object and posts it
  793. func testDeploymentRollback(c *testContext) {
  794. deploymentGVR := gvr("apps", "v1", "deployments")
  795. obj, err := createOrGetResource(c.client, deploymentGVR, c.resources[deploymentGVR])
  796. if err != nil {
  797. c.t.Error(err)
  798. return
  799. }
  800. gvrWithoutSubresources := c.gvr
  801. gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
  802. subresources := strings.Split(c.gvr.Resource, "/")[1:]
  803. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkCreateOptions, v1beta1.Create, obj.GetName(), obj.GetNamespace(), true, false, true)
  804. var rollbackObj runtime.Object
  805. switch c.gvr {
  806. case gvr("apps", "v1beta1", "deployments/rollback"):
  807. rollbackObj = &appsv1beta1.DeploymentRollback{
  808. TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1beta1", Kind: "DeploymentRollback"},
  809. Name: obj.GetName(),
  810. RollbackTo: appsv1beta1.RollbackConfig{Revision: 0},
  811. }
  812. case gvr("extensions", "v1beta1", "deployments/rollback"):
  813. rollbackObj = &extensionsv1beta1.DeploymentRollback{
  814. TypeMeta: metav1.TypeMeta{APIVersion: "extensions/v1beta1", Kind: "DeploymentRollback"},
  815. Name: obj.GetName(),
  816. RollbackTo: extensionsv1beta1.RollbackConfig{Revision: 0},
  817. }
  818. default:
  819. c.t.Errorf("unknown rollback resource %#v", c.gvr)
  820. return
  821. }
  822. rollbackUnstructuredBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(rollbackObj)
  823. if err != nil {
  824. c.t.Errorf("ToUnstructured failed: %v", err)
  825. return
  826. }
  827. rollbackUnstructuredObj := &unstructured.Unstructured{Object: rollbackUnstructuredBody}
  828. rollbackUnstructuredObj.SetName(obj.GetName())
  829. _, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Create(rollbackUnstructuredObj, metav1.CreateOptions{}, subresources...)
  830. if err != nil {
  831. c.t.Error(err)
  832. return
  833. }
  834. }
  835. // testPodConnectSubresource verifies connect subresources
  836. func testPodConnectSubresource(c *testContext) {
  837. podGVR := gvr("", "v1", "pods")
  838. pod, err := createOrGetResource(c.client, podGVR, c.resources[podGVR])
  839. if err != nil {
  840. c.t.Error(err)
  841. return
  842. }
  843. // check all upgradeable verbs
  844. for _, httpMethod := range []string{"GET", "POST"} {
  845. c.t.Logf("verifying %v", httpMethod)
  846. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), schema.GroupVersionKind{}, v1beta1.Connect, pod.GetName(), pod.GetNamespace(), true, false, false)
  847. var err error
  848. switch c.gvr {
  849. case gvr("", "v1", "pods/exec"):
  850. err = c.clientset.CoreV1().RESTClient().Verb(httpMethod).Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("exec").Do().Error()
  851. case gvr("", "v1", "pods/attach"):
  852. err = c.clientset.CoreV1().RESTClient().Verb(httpMethod).Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("attach").Do().Error()
  853. case gvr("", "v1", "pods/portforward"):
  854. err = c.clientset.CoreV1().RESTClient().Verb(httpMethod).Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("portforward").Do().Error()
  855. default:
  856. c.t.Errorf("unknown subresource %#v", c.gvr)
  857. return
  858. }
  859. if err != nil {
  860. c.t.Logf("debug: result of subresource connect: %v", err)
  861. }
  862. c.admissionHolder.verify(c.t)
  863. }
  864. }
  865. // testPodBindingEviction verifies pod binding and eviction admission
  866. func testPodBindingEviction(c *testContext) {
  867. podGVR := gvr("", "v1", "pods")
  868. pod, err := createOrGetResource(c.client, podGVR, c.resources[podGVR])
  869. if err != nil {
  870. c.t.Error(err)
  871. return
  872. }
  873. background := metav1.DeletePropagationBackground
  874. zero := int64(0)
  875. forceDelete := &metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background}
  876. defer func() {
  877. err := c.clientset.CoreV1().Pods(pod.GetNamespace()).Delete(pod.GetName(), forceDelete)
  878. if err != nil && !errors.IsNotFound(err) {
  879. c.t.Error(err)
  880. return
  881. }
  882. }()
  883. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkCreateOptions, v1beta1.Create, pod.GetName(), pod.GetNamespace(), true, false, true)
  884. switch c.gvr {
  885. case gvr("", "v1", "bindings"):
  886. err = c.clientset.CoreV1().RESTClient().Post().Namespace(pod.GetNamespace()).Resource("bindings").Body(&corev1.Binding{
  887. ObjectMeta: metav1.ObjectMeta{Name: pod.GetName()},
  888. Target: corev1.ObjectReference{Name: "foo", Kind: "Node", APIVersion: "v1"},
  889. }).Do().Error()
  890. case gvr("", "v1", "pods/binding"):
  891. err = c.clientset.CoreV1().RESTClient().Post().Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("binding").Body(&corev1.Binding{
  892. ObjectMeta: metav1.ObjectMeta{Name: pod.GetName()},
  893. Target: corev1.ObjectReference{Name: "foo", Kind: "Node", APIVersion: "v1"},
  894. }).Do().Error()
  895. case gvr("", "v1", "pods/eviction"):
  896. err = c.clientset.CoreV1().RESTClient().Post().Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("eviction").Body(&policyv1beta1.Eviction{
  897. ObjectMeta: metav1.ObjectMeta{Name: pod.GetName()},
  898. DeleteOptions: forceDelete,
  899. }).Do().Error()
  900. default:
  901. c.t.Errorf("unhandled resource %#v", c.gvr)
  902. return
  903. }
  904. if err != nil {
  905. c.t.Error(err)
  906. return
  907. }
  908. }
  909. // testSubresourceProxy verifies proxy subresources
  910. func testSubresourceProxy(c *testContext) {
  911. parentGVR := getParentGVR(c.gvr)
  912. parentResource := c.resources[parentGVR]
  913. obj, err := createOrGetResource(c.client, parentGVR, parentResource)
  914. if err != nil {
  915. c.t.Error(err)
  916. return
  917. }
  918. gvrWithoutSubresources := c.gvr
  919. gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
  920. subresources := strings.Split(c.gvr.Resource, "/")[1:]
  921. verbToHTTPMethods := map[string][]string{
  922. "create": {"POST", "GET", "HEAD", "OPTIONS"}, // also test read-only verbs map to Connect admission
  923. "update": {"PUT"},
  924. "patch": {"PATCH"},
  925. "delete": {"DELETE"},
  926. }
  927. httpMethodsToTest, ok := verbToHTTPMethods[c.verb]
  928. if !ok {
  929. c.t.Errorf("unknown verb %v", c.verb)
  930. return
  931. }
  932. for _, httpMethod := range httpMethodsToTest {
  933. c.t.Logf("testing %v", httpMethod)
  934. request := c.clientset.CoreV1().RESTClient().Verb(httpMethod)
  935. // add the namespace if required
  936. if len(obj.GetNamespace()) > 0 {
  937. request = request.Namespace(obj.GetNamespace())
  938. }
  939. // set expectations
  940. c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), schema.GroupVersionKind{}, v1beta1.Connect, obj.GetName(), obj.GetNamespace(), true, false, false)
  941. // run the request. we don't actually care if the request is successful, just that admission gets called as expected
  942. err = request.Resource(gvrWithoutSubresources.Resource).Name(obj.GetName()).SubResource(subresources...).Do().Error()
  943. if err != nil {
  944. c.t.Logf("debug: result of subresource proxy (error expected): %v", err)
  945. }
  946. // verify the result
  947. c.admissionHolder.verify(c.t)
  948. }
  949. }
  950. func testPruningRandomNumbers(c *testContext) {
  951. testResourceCreate(c)
  952. cr2pant, err := c.client.Resource(c.gvr).Get("fortytwo", metav1.GetOptions{})
  953. if err != nil {
  954. c.t.Error(err)
  955. return
  956. }
  957. foo, found, err := unstructured.NestedString(cr2pant.Object, "foo")
  958. if err != nil {
  959. c.t.Error(err)
  960. return
  961. }
  962. if found {
  963. c.t.Errorf("expected .foo to be pruned, but got: %s", foo)
  964. }
  965. }
  966. func testNoPruningCustomFancy(c *testContext) {
  967. testResourceCreate(c)
  968. cr2pant, err := c.client.Resource(c.gvr).Get("cr2pant", metav1.GetOptions{})
  969. if err != nil {
  970. c.t.Error(err)
  971. return
  972. }
  973. foo, _, err := unstructured.NestedString(cr2pant.Object, "foo")
  974. if err != nil {
  975. c.t.Error(err)
  976. return
  977. }
  978. // check that no pruning took place
  979. if expected, got := "test", foo; expected != got {
  980. c.t.Errorf("expected /foo to be %q, got: %q", expected, got)
  981. }
  982. }
  983. //
  984. // utility methods
  985. //
  986. func newWebhookHandler(t *testing.T, holder *holder, phase string, converted bool) http.Handler {
  987. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  988. defer r.Body.Close()
  989. data, err := ioutil.ReadAll(r.Body)
  990. if err != nil {
  991. t.Error(err)
  992. return
  993. }
  994. if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
  995. t.Errorf("contentType=%s, expect application/json", contentType)
  996. return
  997. }
  998. review := v1beta1.AdmissionReview{}
  999. if err := json.Unmarshal(data, &review); err != nil {
  1000. t.Errorf("Fail to deserialize object: %s with error: %v", string(data), err)
  1001. http.Error(w, err.Error(), 400)
  1002. return
  1003. }
  1004. if review.GetObjectKind().GroupVersionKind() != gvk("admission.k8s.io", "v1beta1", "AdmissionReview") {
  1005. t.Errorf("Invalid admission review kind: %#v", review.GetObjectKind().GroupVersionKind())
  1006. http.Error(w, err.Error(), 400)
  1007. return
  1008. }
  1009. if len(review.Request.Object.Raw) > 0 {
  1010. u := &unstructured.Unstructured{Object: map[string]interface{}{}}
  1011. if err := json.Unmarshal(review.Request.Object.Raw, u); err != nil {
  1012. t.Errorf("Fail to deserialize object: %s with error: %v", string(review.Request.Object.Raw), err)
  1013. http.Error(w, err.Error(), 400)
  1014. return
  1015. }
  1016. review.Request.Object.Object = u
  1017. }
  1018. if len(review.Request.OldObject.Raw) > 0 {
  1019. u := &unstructured.Unstructured{Object: map[string]interface{}{}}
  1020. if err := json.Unmarshal(review.Request.OldObject.Raw, u); err != nil {
  1021. t.Errorf("Fail to deserialize object: %s with error: %v", string(review.Request.OldObject.Raw), err)
  1022. http.Error(w, err.Error(), 400)
  1023. return
  1024. }
  1025. review.Request.OldObject.Object = u
  1026. }
  1027. if len(review.Request.Options.Raw) > 0 {
  1028. u := &unstructured.Unstructured{Object: map[string]interface{}{}}
  1029. if err := json.Unmarshal(review.Request.Options.Raw, u); err != nil {
  1030. t.Errorf("Fail to deserialize options object: %s for admission request %#+v with error: %v", string(review.Request.Options.Raw), review.Request, err)
  1031. http.Error(w, err.Error(), 400)
  1032. return
  1033. }
  1034. review.Request.Options.Object = u
  1035. }
  1036. if review.Request.UserInfo.Username == testClientUsername {
  1037. // only record requests originating from this integration test's client
  1038. holder.record(phase, converted, review.Request)
  1039. }
  1040. review.Response = &v1beta1.AdmissionResponse{
  1041. Allowed: true,
  1042. UID: review.Request.UID,
  1043. Result: &metav1.Status{Message: "admitted"},
  1044. }
  1045. // If we're mutating, and have an object, return a patch to exercise conversion
  1046. if phase == mutation && len(review.Request.Object.Raw) > 0 {
  1047. review.Response.Patch = []byte(`[{"op":"add","path":"/foo","value":"test"}]`)
  1048. jsonPatch := v1beta1.PatchTypeJSONPatch
  1049. review.Response.PatchType = &jsonPatch
  1050. }
  1051. w.Header().Set("Content-Type", "application/json")
  1052. if err := json.NewEncoder(w).Encode(review); err != nil {
  1053. t.Errorf("Marshal of response failed with error: %v", err)
  1054. }
  1055. })
  1056. }
  1057. func getTestFunc(gvr schema.GroupVersionResource, verb string) testFunc {
  1058. if f, found := customTestFuncs[gvr][verb]; found {
  1059. return f
  1060. }
  1061. if f, found := customTestFuncs[gvr]["*"]; found {
  1062. return f
  1063. }
  1064. if strings.Contains(gvr.Resource, "/") {
  1065. if f, found := defaultSubresourceFuncs[verb]; found {
  1066. return f
  1067. }
  1068. return unimplemented
  1069. }
  1070. if f, found := defaultResourceFuncs[verb]; found {
  1071. return f
  1072. }
  1073. return unimplemented
  1074. }
  1075. func getStubObj(gvr schema.GroupVersionResource, resource metav1.APIResource) (*unstructured.Unstructured, error) {
  1076. stub := ""
  1077. if data, ok := etcd.GetEtcdStorageDataForNamespace(testNamespace)[gvr]; ok {
  1078. stub = data.Stub
  1079. }
  1080. if data, ok := stubDataOverrides[gvr]; ok {
  1081. stub = data
  1082. }
  1083. if len(stub) == 0 {
  1084. return nil, fmt.Errorf("no stub data for %#v", gvr)
  1085. }
  1086. stubObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
  1087. if err := json.Unmarshal([]byte(stub), &stubObj.Object); err != nil {
  1088. return nil, fmt.Errorf("error unmarshaling stub for %#v: %v", gvr, err)
  1089. }
  1090. return stubObj, nil
  1091. }
  1092. func createOrGetResource(client dynamic.Interface, gvr schema.GroupVersionResource, resource metav1.APIResource) (*unstructured.Unstructured, error) {
  1093. stubObj, err := getStubObj(gvr, resource)
  1094. if err != nil {
  1095. return nil, err
  1096. }
  1097. ns := ""
  1098. if resource.Namespaced {
  1099. ns = testNamespace
  1100. }
  1101. obj, err := client.Resource(gvr).Namespace(ns).Get(stubObj.GetName(), metav1.GetOptions{})
  1102. if err == nil {
  1103. return obj, nil
  1104. }
  1105. if !errors.IsNotFound(err) {
  1106. return nil, err
  1107. }
  1108. return client.Resource(gvr).Namespace(ns).Create(stubObj, metav1.CreateOptions{})
  1109. }
  1110. func gvr(group, version, resource string) schema.GroupVersionResource {
  1111. return schema.GroupVersionResource{Group: group, Version: version, Resource: resource}
  1112. }
  1113. func gvk(group, version, kind string) schema.GroupVersionKind {
  1114. return schema.GroupVersionKind{Group: group, Version: version, Kind: kind}
  1115. }
  1116. var (
  1117. gvkCreateOptions = metav1.SchemeGroupVersion.WithKind("CreateOptions")
  1118. gvkUpdateOptions = metav1.SchemeGroupVersion.WithKind("UpdateOptions")
  1119. gvkDeleteOptions = metav1.SchemeGroupVersion.WithKind("DeleteOptions")
  1120. )
  1121. func shouldTestResource(gvr schema.GroupVersionResource, resource metav1.APIResource) bool {
  1122. if !sets.NewString(resource.Verbs...).HasAny("create", "update", "patch", "connect", "delete", "deletecollection") {
  1123. return false
  1124. }
  1125. return true
  1126. }
  1127. func shouldTestResourceVerb(gvr schema.GroupVersionResource, resource metav1.APIResource, verb string) bool {
  1128. if !sets.NewString(resource.Verbs...).Has(verb) {
  1129. return false
  1130. }
  1131. return true
  1132. }
  1133. //
  1134. // webhook registration helpers
  1135. //
  1136. func createV1beta1ValidationWebhook(client clientset.Interface, endpoint, convertedEndpoint string, convertedRules []admissionv1beta1.RuleWithOperations) error {
  1137. fail := admissionv1beta1.Fail
  1138. equivalent := admissionv1beta1.Equivalent
  1139. // Attaching Admission webhook to API server
  1140. _, err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&admissionv1beta1.ValidatingWebhookConfiguration{
  1141. ObjectMeta: metav1.ObjectMeta{Name: "admission.integration.test"},
  1142. Webhooks: []admissionv1beta1.ValidatingWebhook{
  1143. {
  1144. Name: "admission.integration.test",
  1145. ClientConfig: admissionv1beta1.WebhookClientConfig{
  1146. URL: &endpoint,
  1147. CABundle: localhostCert,
  1148. },
  1149. Rules: []admissionv1beta1.RuleWithOperations{{
  1150. Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll},
  1151. Rule: admissionv1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
  1152. }},
  1153. FailurePolicy: &fail,
  1154. AdmissionReviewVersions: []string{"v1beta1"},
  1155. },
  1156. {
  1157. Name: "admission.integration.testconversion",
  1158. ClientConfig: admissionv1beta1.WebhookClientConfig{
  1159. URL: &convertedEndpoint,
  1160. CABundle: localhostCert,
  1161. },
  1162. Rules: convertedRules,
  1163. FailurePolicy: &fail,
  1164. MatchPolicy: &equivalent,
  1165. AdmissionReviewVersions: []string{"v1beta1"},
  1166. },
  1167. },
  1168. })
  1169. return err
  1170. }
  1171. func createV1beta1MutationWebhook(client clientset.Interface, endpoint, convertedEndpoint string, convertedRules []admissionv1beta1.RuleWithOperations) error {
  1172. fail := admissionv1beta1.Fail
  1173. equivalent := admissionv1beta1.Equivalent
  1174. // Attaching Mutation webhook to API server
  1175. _, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{
  1176. ObjectMeta: metav1.ObjectMeta{Name: "mutation.integration.test"},
  1177. Webhooks: []admissionv1beta1.MutatingWebhook{
  1178. {
  1179. Name: "mutation.integration.test",
  1180. ClientConfig: admissionv1beta1.WebhookClientConfig{
  1181. URL: &endpoint,
  1182. CABundle: localhostCert,
  1183. },
  1184. Rules: []admissionv1beta1.RuleWithOperations{{
  1185. Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll},
  1186. Rule: admissionv1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
  1187. }},
  1188. FailurePolicy: &fail,
  1189. AdmissionReviewVersions: []string{"v1beta1"},
  1190. },
  1191. {
  1192. Name: "mutation.integration.testconversion",
  1193. ClientConfig: admissionv1beta1.WebhookClientConfig{
  1194. URL: &convertedEndpoint,
  1195. CABundle: localhostCert,
  1196. },
  1197. Rules: convertedRules,
  1198. FailurePolicy: &fail,
  1199. MatchPolicy: &equivalent,
  1200. AdmissionReviewVersions: []string{"v1beta1"},
  1201. },
  1202. },
  1203. })
  1204. return err
  1205. }
  1206. // localhostCert was generated from crypto/tls/generate_cert.go with the following command:
  1207. // go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
  1208. var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
  1209. MIIBjzCCATmgAwIBAgIRAKpi2WmTcFrVjxrl5n5YDUEwDQYJKoZIhvcNAQELBQAw
  1210. EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
  1211. MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
  1212. QQC9fEbRszP3t14Gr4oahV7zFObBI4TfA5i7YnlMXeLinb7MnvT4bkfOJzE6zktn
  1213. 59zP7UiHs3l4YOuqrjiwM413AgMBAAGjaDBmMA4GA1UdDwEB/wQEAwICpDATBgNV
  1214. HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MC4GA1UdEQQnMCWCC2V4
  1215. YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUA
  1216. A0EAUsVE6KMnza/ZbodLlyeMzdo7EM/5nb5ywyOxgIOCf0OOLHsPS9ueGLQX9HEG
  1217. //yjTXuhNcUugExIjM/AIwAZPQ==
  1218. -----END CERTIFICATE-----`)
  1219. // localhostKey is the private key for localhostCert.
  1220. var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
  1221. MIIBOwIBAAJBAL18RtGzM/e3XgavihqFXvMU5sEjhN8DmLtieUxd4uKdvsye9Phu
  1222. R84nMTrOS2fn3M/tSIezeXhg66quOLAzjXcCAwEAAQJBAKcRxH9wuglYLBdI/0OT
  1223. BLzfWPZCEw1vZmMR2FF1Fm8nkNOVDPleeVGTWoOEcYYlQbpTmkGSxJ6ya+hqRi6x
  1224. goECIQDx3+X49fwpL6B5qpJIJMyZBSCuMhH4B7JevhGGFENi3wIhAMiNJN5Q3UkL
  1225. IuSvv03kaPR5XVQ99/UeEetUgGvBcABpAiBJSBzVITIVCGkGc7d+RCf49KTCIklv
  1226. bGWObufAR8Ni4QIgWpILjW8dkGg8GOUZ0zaNA6Nvt6TIv2UWGJ4v5PoV98kCIQDx
  1227. rIiZs5QbKdycsv9gQJzwQAogC8o04X3Zz3dsoX+h4A==
  1228. -----END RSA PRIVATE KEY-----`)