admission_test.go 61 KB

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