| /* |
| Copyright 2018 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package validation |
| |
| import ( |
| "strings" |
| "testing" |
| |
| "github.com/stretchr/testify/require" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/util/validation/field" |
| "k8s.io/kubernetes/pkg/apis/auditregistration" |
| ) |
| |
| func TestValidateAuditSink(t *testing.T) { |
| testQPS := int64(10) |
| testURL := "http://localhost" |
| testCases := []struct { |
| name string |
| conf auditregistration.AuditSink |
| numErr int |
| }{ |
| { |
| name: "should pass full config", |
| conf: auditregistration.AuditSink{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "myconf", |
| }, |
| Spec: auditregistration.AuditSinkSpec{ |
| Policy: auditregistration.Policy{ |
| Level: auditregistration.LevelRequest, |
| Stages: []auditregistration.Stage{ |
| auditregistration.StageRequestReceived, |
| }, |
| }, |
| Webhook: auditregistration.Webhook{ |
| Throttle: &auditregistration.WebhookThrottleConfig{ |
| QPS: &testQPS, |
| }, |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| URL: &testURL, |
| }, |
| }, |
| }, |
| }, |
| numErr: 0, |
| }, |
| { |
| name: "should fail no policy", |
| conf: auditregistration.AuditSink{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "myconf", |
| }, |
| Spec: auditregistration.AuditSinkSpec{ |
| Webhook: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| URL: &testURL, |
| }, |
| }, |
| }, |
| }, |
| numErr: 1, |
| }, |
| { |
| name: "should fail no webhook", |
| conf: auditregistration.AuditSink{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "myconf", |
| }, |
| Spec: auditregistration.AuditSinkSpec{ |
| Policy: auditregistration.Policy{ |
| Level: auditregistration.LevelMetadata, |
| Stages: []auditregistration.Stage{ |
| auditregistration.StageRequestReceived, |
| }, |
| }, |
| }, |
| }, |
| numErr: 1, |
| }, |
| } |
| |
| for _, test := range testCases { |
| t.Run(test.name, func(t *testing.T) { |
| errs := ValidateAuditSink(&test.conf) |
| require.Len(t, errs, test.numErr) |
| }) |
| } |
| } |
| |
| func TestValidatePolicy(t *testing.T) { |
| successCases := []auditregistration.Policy{} |
| successCases = append(successCases, auditregistration.Policy{ // Policy with omitStages and level |
| Level: auditregistration.LevelRequest, |
| Stages: []auditregistration.Stage{ |
| auditregistration.Stage("RequestReceived"), |
| auditregistration.Stage("ResponseStarted"), |
| }, |
| }) |
| successCases = append(successCases, auditregistration.Policy{Level: auditregistration.LevelNone}) // Policy with none level only |
| |
| for i, policy := range successCases { |
| if errs := ValidatePolicy(policy, field.NewPath("policy")); len(errs) != 0 { |
| t.Errorf("[%d] Expected policy %#v to be valid: %v", i, policy, errs) |
| } |
| } |
| |
| errorCases := []auditregistration.Policy{} |
| errorCases = append(errorCases, auditregistration.Policy{}) // Empty policy // Policy with missing level |
| errorCases = append(errorCases, auditregistration.Policy{Stages: []auditregistration.Stage{ // Policy with invalid stages |
| auditregistration.Stage("Bad")}}) |
| errorCases = append(errorCases, auditregistration.Policy{Level: auditregistration.Level("invalid")}) // Policy with bad level |
| errorCases = append(errorCases, auditregistration.Policy{Level: auditregistration.LevelMetadata}) // Policy without stages |
| |
| for i, policy := range errorCases { |
| if errs := ValidatePolicy(policy, field.NewPath("policy")); len(errs) == 0 { |
| t.Errorf("[%d] Expected policy %#v to be invalid!", i, policy) |
| } |
| } |
| } |
| |
| func strPtr(s string) *string { return &s } |
| |
| func TestValidateWebhookConfiguration(t *testing.T) { |
| tests := []struct { |
| name string |
| config auditregistration.Webhook |
| expectedError string |
| }{ |
| { |
| name: "both service and URL missing", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{}, |
| }, |
| expectedError: `exactly one of`, |
| }, |
| { |
| name: "both service and URL provided", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| Service: &auditregistration.ServiceReference{ |
| Namespace: "ns", |
| Name: "n", |
| }, |
| URL: strPtr("example.com/k8s/webhook"), |
| }, |
| }, |
| expectedError: `webhook.clientConfig: Required value: exactly one of url or service is required`, |
| }, |
| { |
| name: "blank URL", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| URL: strPtr(""), |
| }, |
| }, |
| expectedError: `webhook.clientConfig.url: Invalid value: "": host must be provided`, |
| }, |
| { |
| name: "missing host", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| URL: strPtr("https:///fancy/webhook"), |
| }, |
| }, |
| expectedError: `host must be provided`, |
| }, |
| { |
| name: "fragment", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| URL: strPtr("https://example.com/#bookmark"), |
| }, |
| }, |
| expectedError: `"bookmark": fragments are not permitted`, |
| }, |
| { |
| name: "query", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| URL: strPtr("https://example.com?arg=value"), |
| }, |
| }, |
| expectedError: `"arg=value": query parameters are not permitted`, |
| }, |
| { |
| name: "user", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| URL: strPtr("https://harry.potter@example.com/"), |
| }, |
| }, |
| expectedError: `"harry.potter": user information is not permitted`, |
| }, |
| { |
| name: "just totally wrong", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"), |
| }, |
| }, |
| expectedError: `host must be provided`, |
| }, |
| { |
| name: "path must start with slash", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| Service: &auditregistration.ServiceReference{ |
| Namespace: "ns", |
| Name: "n", |
| Path: strPtr("foo/"), |
| }, |
| }, |
| }, |
| expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`, |
| }, |
| { |
| name: "path accepts slash", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| Service: &auditregistration.ServiceReference{ |
| Namespace: "ns", |
| Name: "n", |
| Path: strPtr("/"), |
| }, |
| }, |
| }, |
| expectedError: ``, |
| }, |
| { |
| name: "path accepts no trailing slash", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| Service: &auditregistration.ServiceReference{ |
| Namespace: "ns", |
| Name: "n", |
| Path: strPtr("/foo"), |
| }, |
| }, |
| }, |
| expectedError: ``, |
| }, |
| { |
| name: "path fails //", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| Service: &auditregistration.ServiceReference{ |
| Namespace: "ns", |
| Name: "n", |
| Path: strPtr("//"), |
| }, |
| }, |
| }, |
| expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`, |
| }, |
| { |
| name: "path no empty step", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| Service: &auditregistration.ServiceReference{ |
| Namespace: "ns", |
| Name: "n", |
| Path: strPtr("/foo//bar/"), |
| }, |
| }, |
| }, |
| expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, |
| }, { |
| name: "path no empty step 2", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| Service: &auditregistration.ServiceReference{ |
| Namespace: "ns", |
| Name: "n", |
| Path: strPtr("/foo/bar//"), |
| }, |
| }, |
| }, |
| expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`, |
| }, |
| { |
| name: "path no non-subdomain", |
| config: auditregistration.Webhook{ |
| ClientConfig: auditregistration.WebhookClientConfig{ |
| Service: &auditregistration.ServiceReference{ |
| Namespace: "ns", |
| Name: "n", |
| Path: strPtr("/apis/foo.bar/v1alpha1/--bad"), |
| }, |
| }, |
| }, |
| expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a DNS-1123 subdomain`, |
| }, |
| } |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| errs := ValidateWebhook(test.config, field.NewPath("webhook")) |
| err := errs.ToAggregate() |
| if err != nil { |
| if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" { |
| t.Errorf("expected to contain \nerr: %s \ngot: %s", e, a) |
| } |
| } else { |
| if test.expectedError != "" { |
| t.Errorf("unexpected no error, expected to contain %s", test.expectedError) |
| } |
| } |
| }) |
| } |
| } |