validation_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. /*
  2. Copyright 2018 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 validation
  14. import (
  15. "strings"
  16. "testing"
  17. "github.com/stretchr/testify/require"
  18. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  19. "k8s.io/apimachinery/pkg/util/validation/field"
  20. "k8s.io/kubernetes/pkg/apis/auditregistration"
  21. utilpointer "k8s.io/utils/pointer"
  22. )
  23. func TestValidateAuditSink(t *testing.T) {
  24. testQPS := int64(10)
  25. testURL := "http://localhost"
  26. testCases := []struct {
  27. name string
  28. conf auditregistration.AuditSink
  29. numErr int
  30. }{
  31. {
  32. name: "should pass full config",
  33. conf: auditregistration.AuditSink{
  34. ObjectMeta: metav1.ObjectMeta{
  35. Name: "myconf",
  36. },
  37. Spec: auditregistration.AuditSinkSpec{
  38. Policy: auditregistration.Policy{
  39. Level: auditregistration.LevelRequest,
  40. Stages: []auditregistration.Stage{
  41. auditregistration.StageRequestReceived,
  42. },
  43. },
  44. Webhook: auditregistration.Webhook{
  45. Throttle: &auditregistration.WebhookThrottleConfig{
  46. QPS: &testQPS,
  47. },
  48. ClientConfig: auditregistration.WebhookClientConfig{
  49. URL: &testURL,
  50. },
  51. },
  52. },
  53. },
  54. numErr: 0,
  55. },
  56. {
  57. name: "should fail no policy",
  58. conf: auditregistration.AuditSink{
  59. ObjectMeta: metav1.ObjectMeta{
  60. Name: "myconf",
  61. },
  62. Spec: auditregistration.AuditSinkSpec{
  63. Webhook: auditregistration.Webhook{
  64. ClientConfig: auditregistration.WebhookClientConfig{
  65. URL: &testURL,
  66. },
  67. },
  68. },
  69. },
  70. numErr: 1,
  71. },
  72. {
  73. name: "should fail no webhook",
  74. conf: auditregistration.AuditSink{
  75. ObjectMeta: metav1.ObjectMeta{
  76. Name: "myconf",
  77. },
  78. Spec: auditregistration.AuditSinkSpec{
  79. Policy: auditregistration.Policy{
  80. Level: auditregistration.LevelMetadata,
  81. Stages: []auditregistration.Stage{
  82. auditregistration.StageRequestReceived,
  83. },
  84. },
  85. },
  86. },
  87. numErr: 1,
  88. },
  89. }
  90. for _, test := range testCases {
  91. t.Run(test.name, func(t *testing.T) {
  92. errs := ValidateAuditSink(&test.conf)
  93. require.Len(t, errs, test.numErr)
  94. })
  95. }
  96. }
  97. func TestValidatePolicy(t *testing.T) {
  98. successCases := []auditregistration.Policy{}
  99. successCases = append(successCases, auditregistration.Policy{ // Policy with omitStages and level
  100. Level: auditregistration.LevelRequest,
  101. Stages: []auditregistration.Stage{
  102. auditregistration.Stage("RequestReceived"),
  103. auditregistration.Stage("ResponseStarted"),
  104. },
  105. })
  106. successCases = append(successCases, auditregistration.Policy{Level: auditregistration.LevelNone}) // Policy with none level only
  107. for i, policy := range successCases {
  108. if errs := ValidatePolicy(policy, field.NewPath("policy")); len(errs) != 0 {
  109. t.Errorf("[%d] Expected policy %#v to be valid: %v", i, policy, errs)
  110. }
  111. }
  112. errorCases := []auditregistration.Policy{}
  113. errorCases = append(errorCases, auditregistration.Policy{}) // Empty policy // Policy with missing level
  114. errorCases = append(errorCases, auditregistration.Policy{Stages: []auditregistration.Stage{ // Policy with invalid stages
  115. auditregistration.Stage("Bad")}})
  116. errorCases = append(errorCases, auditregistration.Policy{Level: auditregistration.Level("invalid")}) // Policy with bad level
  117. errorCases = append(errorCases, auditregistration.Policy{Level: auditregistration.LevelMetadata}) // Policy without stages
  118. for i, policy := range errorCases {
  119. if errs := ValidatePolicy(policy, field.NewPath("policy")); len(errs) == 0 {
  120. t.Errorf("[%d] Expected policy %#v to be invalid!", i, policy)
  121. }
  122. }
  123. }
  124. func TestValidateWebhookConfiguration(t *testing.T) {
  125. tests := []struct {
  126. name string
  127. config auditregistration.Webhook
  128. expectedError string
  129. }{
  130. {
  131. name: "both service and URL missing",
  132. config: auditregistration.Webhook{
  133. ClientConfig: auditregistration.WebhookClientConfig{},
  134. },
  135. expectedError: `exactly one of`,
  136. },
  137. {
  138. name: "both service and URL provided",
  139. config: auditregistration.Webhook{
  140. ClientConfig: auditregistration.WebhookClientConfig{
  141. Service: &auditregistration.ServiceReference{
  142. Namespace: "ns",
  143. Name: "n",
  144. Port: 443,
  145. },
  146. URL: utilpointer.StringPtr("example.com/k8s/webhook"),
  147. },
  148. },
  149. expectedError: `webhook.clientConfig: Required value: exactly one of url or service is required`,
  150. },
  151. {
  152. name: "blank URL",
  153. config: auditregistration.Webhook{
  154. ClientConfig: auditregistration.WebhookClientConfig{
  155. URL: utilpointer.StringPtr(""),
  156. },
  157. },
  158. expectedError: `webhook.clientConfig.url: Invalid value: "": host must be provided`,
  159. },
  160. {
  161. name: "missing host",
  162. config: auditregistration.Webhook{
  163. ClientConfig: auditregistration.WebhookClientConfig{
  164. URL: utilpointer.StringPtr("https:///fancy/webhook"),
  165. },
  166. },
  167. expectedError: `host must be provided`,
  168. },
  169. {
  170. name: "fragment",
  171. config: auditregistration.Webhook{
  172. ClientConfig: auditregistration.WebhookClientConfig{
  173. URL: utilpointer.StringPtr("https://example.com/#bookmark"),
  174. },
  175. },
  176. expectedError: `"bookmark": fragments are not permitted`,
  177. },
  178. {
  179. name: "query",
  180. config: auditregistration.Webhook{
  181. ClientConfig: auditregistration.WebhookClientConfig{
  182. URL: utilpointer.StringPtr("https://example.com?arg=value"),
  183. },
  184. },
  185. expectedError: `"arg=value": query parameters are not permitted`,
  186. },
  187. {
  188. name: "user",
  189. config: auditregistration.Webhook{
  190. ClientConfig: auditregistration.WebhookClientConfig{
  191. URL: utilpointer.StringPtr("https://harry.potter@example.com/"),
  192. },
  193. },
  194. expectedError: `"harry.potter": user information is not permitted`,
  195. },
  196. {
  197. name: "just totally wrong",
  198. config: auditregistration.Webhook{
  199. ClientConfig: auditregistration.WebhookClientConfig{
  200. URL: utilpointer.StringPtr("arg#backwards=thisis?html.index/port:host//:https"),
  201. },
  202. },
  203. expectedError: `host must be provided`,
  204. },
  205. {
  206. name: "path must start with slash",
  207. config: auditregistration.Webhook{
  208. ClientConfig: auditregistration.WebhookClientConfig{
  209. Service: &auditregistration.ServiceReference{
  210. Namespace: "ns",
  211. Name: "n",
  212. Path: utilpointer.StringPtr("foo/"),
  213. Port: 443,
  214. },
  215. },
  216. },
  217. expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`,
  218. },
  219. {
  220. name: "invalid port >65535",
  221. config: auditregistration.Webhook{
  222. ClientConfig: auditregistration.WebhookClientConfig{
  223. Service: &auditregistration.ServiceReference{
  224. Namespace: "ns",
  225. Name: "n",
  226. Path: utilpointer.StringPtr("foo/"),
  227. Port: 65536,
  228. },
  229. },
  230. },
  231. expectedError: `Invalid value: 65536: port is not valid: must be between 1 and 65535, inclusive`,
  232. },
  233. {
  234. name: "invalid port 0",
  235. config: auditregistration.Webhook{
  236. ClientConfig: auditregistration.WebhookClientConfig{
  237. Service: &auditregistration.ServiceReference{
  238. Namespace: "ns",
  239. Name: "n",
  240. Path: utilpointer.StringPtr("foo/"),
  241. Port: 0,
  242. },
  243. },
  244. },
  245. expectedError: `Invalid value: 0: port is not valid: must be between 1 and 65535, inclusive`,
  246. },
  247. {
  248. name: "path accepts slash",
  249. config: auditregistration.Webhook{
  250. ClientConfig: auditregistration.WebhookClientConfig{
  251. Service: &auditregistration.ServiceReference{
  252. Namespace: "ns",
  253. Name: "n",
  254. Path: utilpointer.StringPtr("/"),
  255. Port: 443,
  256. },
  257. },
  258. },
  259. expectedError: ``,
  260. },
  261. {
  262. name: "path accepts no trailing slash",
  263. config: auditregistration.Webhook{
  264. ClientConfig: auditregistration.WebhookClientConfig{
  265. Service: &auditregistration.ServiceReference{
  266. Namespace: "ns",
  267. Name: "n",
  268. Path: utilpointer.StringPtr("/foo"),
  269. Port: 443,
  270. },
  271. },
  272. },
  273. expectedError: ``,
  274. },
  275. {
  276. name: "path fails //",
  277. config: auditregistration.Webhook{
  278. ClientConfig: auditregistration.WebhookClientConfig{
  279. Service: &auditregistration.ServiceReference{
  280. Namespace: "ns",
  281. Name: "n",
  282. Path: utilpointer.StringPtr("//"),
  283. Port: 443,
  284. },
  285. },
  286. },
  287. expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`,
  288. },
  289. {
  290. name: "path no empty step",
  291. config: auditregistration.Webhook{
  292. ClientConfig: auditregistration.WebhookClientConfig{
  293. Service: &auditregistration.ServiceReference{
  294. Namespace: "ns",
  295. Name: "n",
  296. Path: utilpointer.StringPtr("/foo//bar/"),
  297. Port: 443,
  298. },
  299. },
  300. },
  301. expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`,
  302. }, {
  303. name: "path no empty step 2",
  304. config: auditregistration.Webhook{
  305. ClientConfig: auditregistration.WebhookClientConfig{
  306. Service: &auditregistration.ServiceReference{
  307. Namespace: "ns",
  308. Name: "n",
  309. Path: utilpointer.StringPtr("/foo/bar//"),
  310. Port: 443,
  311. },
  312. },
  313. },
  314. expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`,
  315. },
  316. {
  317. name: "path no non-subdomain",
  318. config: auditregistration.Webhook{
  319. ClientConfig: auditregistration.WebhookClientConfig{
  320. Service: &auditregistration.ServiceReference{
  321. Namespace: "ns",
  322. Name: "n",
  323. Path: utilpointer.StringPtr("/apis/foo.bar/v1alpha1/--bad"),
  324. Port: 443,
  325. },
  326. },
  327. },
  328. expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a DNS-1123 subdomain`,
  329. },
  330. }
  331. for _, test := range tests {
  332. t.Run(test.name, func(t *testing.T) {
  333. errs := ValidateWebhook(test.config, field.NewPath("webhook"))
  334. err := errs.ToAggregate()
  335. if err != nil {
  336. if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
  337. t.Errorf("expected to contain \nerr: %s \ngot: %s", e, a)
  338. }
  339. } else {
  340. if test.expectedError != "" {
  341. t.Errorf("unexpected no error, expected to contain %s", test.expectedError)
  342. }
  343. }
  344. })
  345. }
  346. }