diff --git a/k8s/evals.go b/k8s/evals.go index 94f23fc..3f24da6 100644 --- a/k8s/evals.go +++ b/k8s/evals.go @@ -188,7 +188,9 @@ func calculateLazyEvalCost(lazyEvals lazyEvalMap) uint64 { func calculateEvalResponsesCost(evals evalResponses) uint64 { var cost uint64 for _, eval := range evals { - cost += *eval.details.ActualCost() + if eval.details != nil { + cost += *eval.details.ActualCost() + } } return cost } diff --git a/k8s/testdata/authorizer1 authorizer.yaml b/k8s/testdata/vap/authorizer1 authorizer.yaml similarity index 100% rename from k8s/testdata/authorizer1 authorizer.yaml rename to k8s/testdata/vap/authorizer1 authorizer.yaml diff --git a/k8s/testdata/authorizer1 namespace.yaml b/k8s/testdata/vap/authorizer1 namespace.yaml similarity index 100% rename from k8s/testdata/authorizer1 namespace.yaml rename to k8s/testdata/vap/authorizer1 namespace.yaml diff --git a/k8s/testdata/authorizer1 policy.yaml b/k8s/testdata/vap/authorizer1 policy.yaml similarity index 100% rename from k8s/testdata/authorizer1 policy.yaml rename to k8s/testdata/vap/authorizer1 policy.yaml diff --git a/k8s/testdata/authorizer1 updated.yaml b/k8s/testdata/vap/authorizer1 updated.yaml similarity index 100% rename from k8s/testdata/authorizer1 updated.yaml rename to k8s/testdata/vap/authorizer1 updated.yaml diff --git a/k8s/testdata/authorizer2 authorizer.yaml b/k8s/testdata/vap/authorizer2 authorizer.yaml similarity index 100% rename from k8s/testdata/authorizer2 authorizer.yaml rename to k8s/testdata/vap/authorizer2 authorizer.yaml diff --git a/k8s/testdata/authorizer2 namespace.yaml b/k8s/testdata/vap/authorizer2 namespace.yaml similarity index 100% rename from k8s/testdata/authorizer2 namespace.yaml rename to k8s/testdata/vap/authorizer2 namespace.yaml diff --git a/k8s/testdata/authorizer2 policy.yaml b/k8s/testdata/vap/authorizer2 policy.yaml similarity index 100% rename from k8s/testdata/authorizer2 policy.yaml rename to k8s/testdata/vap/authorizer2 policy.yaml diff --git a/k8s/testdata/authorizer2 updated.yaml b/k8s/testdata/vap/authorizer2 updated.yaml similarity index 100% rename from k8s/testdata/authorizer2 updated.yaml rename to k8s/testdata/vap/authorizer2 updated.yaml diff --git a/k8s/testdata/match1 policy.yaml b/k8s/testdata/vap/match1 policy.yaml similarity index 100% rename from k8s/testdata/match1 policy.yaml rename to k8s/testdata/vap/match1 policy.yaml diff --git a/k8s/testdata/match1 request.yaml b/k8s/testdata/vap/match1 request.yaml similarity index 100% rename from k8s/testdata/match1 request.yaml rename to k8s/testdata/vap/match1 request.yaml diff --git a/k8s/testdata/match1 updated.yaml b/k8s/testdata/vap/match1 updated.yaml similarity index 100% rename from k8s/testdata/match1 updated.yaml rename to k8s/testdata/vap/match1 updated.yaml diff --git a/k8s/testdata/match2 policy.yaml b/k8s/testdata/vap/match2 policy.yaml similarity index 100% rename from k8s/testdata/match2 policy.yaml rename to k8s/testdata/vap/match2 policy.yaml diff --git a/k8s/testdata/match2 request.yaml b/k8s/testdata/vap/match2 request.yaml similarity index 100% rename from k8s/testdata/match2 request.yaml rename to k8s/testdata/vap/match2 request.yaml diff --git a/k8s/testdata/match2 updated.yaml b/k8s/testdata/vap/match2 updated.yaml similarity index 100% rename from k8s/testdata/match2 updated.yaml rename to k8s/testdata/vap/match2 updated.yaml diff --git a/k8s/testdata/namespace1 namespace.yaml b/k8s/testdata/vap/namespace1 namespace.yaml similarity index 100% rename from k8s/testdata/namespace1 namespace.yaml rename to k8s/testdata/vap/namespace1 namespace.yaml diff --git a/k8s/testdata/namespace1 policy.yaml b/k8s/testdata/vap/namespace1 policy.yaml similarity index 100% rename from k8s/testdata/namespace1 policy.yaml rename to k8s/testdata/vap/namespace1 policy.yaml diff --git a/k8s/testdata/namespace1 updated.yaml b/k8s/testdata/vap/namespace1 updated.yaml similarity index 100% rename from k8s/testdata/namespace1 updated.yaml rename to k8s/testdata/vap/namespace1 updated.yaml diff --git a/k8s/testdata/policy1.yaml b/k8s/testdata/vap/policy1.yaml similarity index 100% rename from k8s/testdata/policy1.yaml rename to k8s/testdata/vap/policy1.yaml diff --git a/k8s/testdata/policy2.yaml b/k8s/testdata/vap/policy2.yaml similarity index 100% rename from k8s/testdata/policy2.yaml rename to k8s/testdata/vap/policy2.yaml diff --git a/k8s/testdata/request1 policy.yaml b/k8s/testdata/vap/request1 policy.yaml similarity index 100% rename from k8s/testdata/request1 policy.yaml rename to k8s/testdata/vap/request1 policy.yaml diff --git a/k8s/testdata/request1 request.yaml b/k8s/testdata/vap/request1 request.yaml similarity index 100% rename from k8s/testdata/request1 request.yaml rename to k8s/testdata/vap/request1 request.yaml diff --git a/k8s/testdata/request1 updated.yaml b/k8s/testdata/vap/request1 updated.yaml similarity index 100% rename from k8s/testdata/request1 updated.yaml rename to k8s/testdata/vap/request1 updated.yaml diff --git a/k8s/testdata/updated1.yaml b/k8s/testdata/vap/updated1.yaml similarity index 100% rename from k8s/testdata/updated1.yaml rename to k8s/testdata/vap/updated1.yaml diff --git a/k8s/testdata/updated2.yaml b/k8s/testdata/vap/updated2.yaml similarity index 100% rename from k8s/testdata/updated2.yaml rename to k8s/testdata/vap/updated2.yaml diff --git a/k8s/testdata/variable1 policy.yaml b/k8s/testdata/vap/variable1 policy.yaml similarity index 100% rename from k8s/testdata/variable1 policy.yaml rename to k8s/testdata/vap/variable1 policy.yaml diff --git a/k8s/testdata/variable1 updated.yaml b/k8s/testdata/vap/variable1 updated.yaml similarity index 100% rename from k8s/testdata/variable1 updated.yaml rename to k8s/testdata/vap/variable1 updated.yaml diff --git a/k8s/testdata/variable2 policy.yaml b/k8s/testdata/vap/variable2 policy.yaml similarity index 100% rename from k8s/testdata/variable2 policy.yaml rename to k8s/testdata/vap/variable2 policy.yaml diff --git a/k8s/testdata/variable2 updated.yaml b/k8s/testdata/vap/variable2 updated.yaml similarity index 100% rename from k8s/testdata/variable2 updated.yaml rename to k8s/testdata/vap/variable2 updated.yaml diff --git a/k8s/testdata/variable3 policy.yaml b/k8s/testdata/vap/variable3 policy.yaml similarity index 100% rename from k8s/testdata/variable3 policy.yaml rename to k8s/testdata/vap/variable3 policy.yaml diff --git a/k8s/testdata/variable3 updated.yaml b/k8s/testdata/vap/variable3 updated.yaml similarity index 100% rename from k8s/testdata/variable3 updated.yaml rename to k8s/testdata/vap/variable3 updated.yaml diff --git a/k8s/testdata/variable4 policy.yaml b/k8s/testdata/vap/variable4 policy.yaml similarity index 100% rename from k8s/testdata/variable4 policy.yaml rename to k8s/testdata/vap/variable4 policy.yaml diff --git a/k8s/testdata/variable4 updated.yaml b/k8s/testdata/vap/variable4 updated.yaml similarity index 100% rename from k8s/testdata/variable4 updated.yaml rename to k8s/testdata/vap/variable4 updated.yaml diff --git a/k8s/testdata/webhook/authorizer4.yaml b/k8s/testdata/webhook/authorizer4.yaml new file mode 100644 index 0000000..e06a30a --- /dev/null +++ b/k8s/testdata/webhook/authorizer4.yaml @@ -0,0 +1,12 @@ +paths: +groups: + admissionregistration.k8s.io: + resources: +validatingwebhookconfigurations: + checks: + "": + rbac.my-webhook.example.com: + breakglass: + decision: allow +serviceAccounts: + diff --git a/k8s/testdata/webhook/multi authorizer1.yaml b/k8s/testdata/webhook/multi authorizer1.yaml new file mode 100644 index 0000000..0734449 --- /dev/null +++ b/k8s/testdata/webhook/multi authorizer1.yaml @@ -0,0 +1,4 @@ +paths: +groups: +serviceAccounts: + diff --git a/k8s/testdata/webhook/multi authorizer2.yaml b/k8s/testdata/webhook/multi authorizer2.yaml new file mode 100644 index 0000000..a43118b --- /dev/null +++ b/k8s/testdata/webhook/multi authorizer2.yaml @@ -0,0 +1,12 @@ +paths: +groups: + admissionregistration.k8s.io: + resources: + validatingwebhookconfigurations: + checks: + "": + rbac.my-webhook.example.com: + breakglass: + decision: allow +serviceAccounts: + diff --git a/k8s/testdata/webhook/multi authorizer3.yaml b/k8s/testdata/webhook/multi authorizer3.yaml new file mode 100644 index 0000000..a43118b --- /dev/null +++ b/k8s/testdata/webhook/multi authorizer3.yaml @@ -0,0 +1,12 @@ +paths: +groups: + admissionregistration.k8s.io: + resources: + validatingwebhookconfigurations: + checks: + "": + rbac.my-webhook.example.com: + breakglass: + decision: allow +serviceAccounts: + diff --git a/k8s/testdata/webhook/multi request1.yaml b/k8s/testdata/webhook/multi request1.yaml new file mode 100644 index 0000000..83e409d --- /dev/null +++ b/k8s/testdata/webhook/multi request1.yaml @@ -0,0 +1,30 @@ +uid: 705ab4f5-6393-11e8-b7cc-42010a800002 +kind: + group: apps + version: v1 + resource: deployments +resource: + group: apps + version: v1 + resource: deployments +requestKind: + group: apps + version: v1 + resource: deployments +requestResource: + group: apps + version: v1 + resource: deployments +name: kubernetes-bootcamp +namespace: default +operation: CREATE +userInfo: + username: admin + uid: 014fbff9a07c + groups: + - system:authenticated + - my-admin-group + extra: + some-key: + - some-value1 + - some-value2 diff --git a/k8s/testdata/webhook/multi request2.yaml b/k8s/testdata/webhook/multi request2.yaml new file mode 100644 index 0000000..83e409d --- /dev/null +++ b/k8s/testdata/webhook/multi request2.yaml @@ -0,0 +1,30 @@ +uid: 705ab4f5-6393-11e8-b7cc-42010a800002 +kind: + group: apps + version: v1 + resource: deployments +resource: + group: apps + version: v1 + resource: deployments +requestKind: + group: apps + version: v1 + resource: deployments +requestResource: + group: apps + version: v1 + resource: deployments +name: kubernetes-bootcamp +namespace: default +operation: CREATE +userInfo: + username: admin + uid: 014fbff9a07c + groups: + - system:authenticated + - my-admin-group + extra: + some-key: + - some-value1 + - some-value2 diff --git a/k8s/testdata/webhook/multi request3.yaml b/k8s/testdata/webhook/multi request3.yaml new file mode 100644 index 0000000..83e409d --- /dev/null +++ b/k8s/testdata/webhook/multi request3.yaml @@ -0,0 +1,30 @@ +uid: 705ab4f5-6393-11e8-b7cc-42010a800002 +kind: + group: apps + version: v1 + resource: deployments +resource: + group: apps + version: v1 + resource: deployments +requestKind: + group: apps + version: v1 + resource: deployments +requestResource: + group: apps + version: v1 + resource: deployments +name: kubernetes-bootcamp +namespace: default +operation: CREATE +userInfo: + username: admin + uid: 014fbff9a07c + groups: + - system:authenticated + - my-admin-group + extra: + some-key: + - some-value1 + - some-value2 diff --git a/k8s/testdata/webhook/multi updated1.yaml b/k8s/testdata/webhook/multi updated1.yaml new file mode 100644 index 0000000..112cb3e --- /dev/null +++ b/k8s/testdata/webhook/multi updated1.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: kubernetes-bootcamp + name: kubernetes-bootcamp + namespace: default +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: kubernetes-bootcamp + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: kubernetes-bootcamp + spec: + containers: + - image: gcr.io/google-samples/kubernetes-bootcamp:v1 + imagePullPolicy: IfNotPresent + name: kubernetes-bootcamp + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/k8s/testdata/webhook/multi updated2.yaml b/k8s/testdata/webhook/multi updated2.yaml new file mode 100644 index 0000000..112cb3e --- /dev/null +++ b/k8s/testdata/webhook/multi updated2.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: kubernetes-bootcamp + name: kubernetes-bootcamp + namespace: default +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: kubernetes-bootcamp + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: kubernetes-bootcamp + spec: + containers: + - image: gcr.io/google-samples/kubernetes-bootcamp:v1 + imagePullPolicy: IfNotPresent + name: kubernetes-bootcamp + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/k8s/testdata/webhook/multi updated3.yaml b/k8s/testdata/webhook/multi updated3.yaml new file mode 100644 index 0000000..112cb3e --- /dev/null +++ b/k8s/testdata/webhook/multi updated3.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: kubernetes-bootcamp + name: kubernetes-bootcamp + namespace: default +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: kubernetes-bootcamp + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: kubernetes-bootcamp + spec: + containers: + - image: gcr.io/google-samples/kubernetes-bootcamp:v1 + imagePullPolicy: IfNotPresent + name: kubernetes-bootcamp + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/k8s/testdata/webhook/multi webhook1.yaml b/k8s/testdata/webhook/multi webhook1.yaml new file mode 100644 index 0000000..086b411 --- /dev/null +++ b/k8s/testdata/webhook/multi webhook1.yaml @@ -0,0 +1,43 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +webhooks: + - name: rbac.my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['apps'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Fail' # Fail-closed (the default) + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + # You can have up to 64 matchConditions per webhook + matchConditions: + - name: 'breakglass' + # Skip requests made by users authorized to 'breakglass' on this webhook. + # The 'breakglass' API verb does not need to exist outside this check. + expression: '!authorizer.group("admissionregistration.k8s.io").resource("validatingwebhookconfigurations").name("rbac.my-webhook.example.com").check("breakglass").allowed()' + - name: my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['*'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Ignore' # Fail-open (optional) + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + # You can have up to 64 matchConditions per webhook + matchConditions: + - name: 'exclude-leases' # Each match condition must have a unique name + expression: '!(request.resource.group == "coordination.k8s.io" && request.resource.resource == "leases")' # Match non-lease resources. + - name: 'exclude-kubelet-requests' + expression: '!("system:nodes" in request.userInfo.groups)' # Match requests made by non-node users. diff --git a/k8s/testdata/webhook/multi webhook2.yaml b/k8s/testdata/webhook/multi webhook2.yaml new file mode 100644 index 0000000..8643973 --- /dev/null +++ b/k8s/testdata/webhook/multi webhook2.yaml @@ -0,0 +1,41 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +webhooks: + - name: rbac.my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['apps'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Fail' # Fail-closed (the default) + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + # You can have up to 64 matchConditions per webhook + matchConditions: + - name: 'breakglass' + # Skip requests made by users authorized to 'breakglass' on this webhook. + # The 'breakglass' API verb does not need to exist outside this check. + expression: '!authorizer.group("admissionregistration.k8s.io").resource("validatingwebhookconfigurations").name("rbac.my-webhook.example.com").check("breakglass").allowed()' + - name: my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['*'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Ignore' # Fail-open (optional) + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + # You can have up to 64 matchConditions per webhook + matchConditions: + - name: 'exclude-bootcamp' + expression: '!(object.metadata.labels.app == "kubernetes-bootcamp")' diff --git a/k8s/testdata/webhook/multi webhook3.yaml b/k8s/testdata/webhook/multi webhook3.yaml new file mode 100644 index 0000000..086b411 --- /dev/null +++ b/k8s/testdata/webhook/multi webhook3.yaml @@ -0,0 +1,43 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +webhooks: + - name: rbac.my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['apps'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Fail' # Fail-closed (the default) + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + # You can have up to 64 matchConditions per webhook + matchConditions: + - name: 'breakglass' + # Skip requests made by users authorized to 'breakglass' on this webhook. + # The 'breakglass' API verb does not need to exist outside this check. + expression: '!authorizer.group("admissionregistration.k8s.io").resource("validatingwebhookconfigurations").name("rbac.my-webhook.example.com").check("breakglass").allowed()' + - name: my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['*'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Ignore' # Fail-open (optional) + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + # You can have up to 64 matchConditions per webhook + matchConditions: + - name: 'exclude-leases' # Each match condition must have a unique name + expression: '!(request.resource.group == "coordination.k8s.io" && request.resource.resource == "leases")' # Match non-lease resources. + - name: 'exclude-kubelet-requests' + expression: '!("system:nodes" in request.userInfo.groups)' # Match requests made by non-node users. diff --git a/k8s/testdata/webhook/request3.yaml b/k8s/testdata/webhook/request3.yaml new file mode 100644 index 0000000..83e409d --- /dev/null +++ b/k8s/testdata/webhook/request3.yaml @@ -0,0 +1,30 @@ +uid: 705ab4f5-6393-11e8-b7cc-42010a800002 +kind: + group: apps + version: v1 + resource: deployments +resource: + group: apps + version: v1 + resource: deployments +requestKind: + group: apps + version: v1 + resource: deployments +requestResource: + group: apps + version: v1 + resource: deployments +name: kubernetes-bootcamp +namespace: default +operation: CREATE +userInfo: + username: admin + uid: 014fbff9a07c + groups: + - system:authenticated + - my-admin-group + extra: + some-key: + - some-value1 + - some-value2 diff --git a/k8s/testdata/webhook/request4.yaml b/k8s/testdata/webhook/request4.yaml new file mode 100644 index 0000000..83e409d --- /dev/null +++ b/k8s/testdata/webhook/request4.yaml @@ -0,0 +1,30 @@ +uid: 705ab4f5-6393-11e8-b7cc-42010a800002 +kind: + group: apps + version: v1 + resource: deployments +resource: + group: apps + version: v1 + resource: deployments +requestKind: + group: apps + version: v1 + resource: deployments +requestResource: + group: apps + version: v1 + resource: deployments +name: kubernetes-bootcamp +namespace: default +operation: CREATE +userInfo: + username: admin + uid: 014fbff9a07c + groups: + - system:authenticated + - my-admin-group + extra: + some-key: + - some-value1 + - some-value2 diff --git a/k8s/testdata/webhook/updated1.yaml b/k8s/testdata/webhook/updated1.yaml new file mode 100644 index 0000000..112cb3e --- /dev/null +++ b/k8s/testdata/webhook/updated1.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: kubernetes-bootcamp + name: kubernetes-bootcamp + namespace: default +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: kubernetes-bootcamp + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: kubernetes-bootcamp + spec: + containers: + - image: gcr.io/google-samples/kubernetes-bootcamp:v1 + imagePullPolicy: IfNotPresent + name: kubernetes-bootcamp + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/k8s/testdata/webhook/updated2.yaml b/k8s/testdata/webhook/updated2.yaml new file mode 100644 index 0000000..112cb3e --- /dev/null +++ b/k8s/testdata/webhook/updated2.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: kubernetes-bootcamp + name: kubernetes-bootcamp + namespace: default +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: kubernetes-bootcamp + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: kubernetes-bootcamp + spec: + containers: + - image: gcr.io/google-samples/kubernetes-bootcamp:v1 + imagePullPolicy: IfNotPresent + name: kubernetes-bootcamp + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/k8s/testdata/webhook/updated3.yaml b/k8s/testdata/webhook/updated3.yaml new file mode 100644 index 0000000..112cb3e --- /dev/null +++ b/k8s/testdata/webhook/updated3.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: kubernetes-bootcamp + name: kubernetes-bootcamp + namespace: default +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: kubernetes-bootcamp + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: kubernetes-bootcamp + spec: + containers: + - image: gcr.io/google-samples/kubernetes-bootcamp:v1 + imagePullPolicy: IfNotPresent + name: kubernetes-bootcamp + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/k8s/testdata/webhook/updated4.yaml b/k8s/testdata/webhook/updated4.yaml new file mode 100644 index 0000000..112cb3e --- /dev/null +++ b/k8s/testdata/webhook/updated4.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: kubernetes-bootcamp + name: kubernetes-bootcamp + namespace: default +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: kubernetes-bootcamp + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: kubernetes-bootcamp + spec: + containers: + - image: gcr.io/google-samples/kubernetes-bootcamp:v1 + imagePullPolicy: IfNotPresent + name: kubernetes-bootcamp + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 diff --git a/k8s/testdata/webhook/webhook1.yaml b/k8s/testdata/webhook/webhook1.yaml new file mode 100644 index 0000000..1041633 --- /dev/null +++ b/k8s/testdata/webhook/webhook1.yaml @@ -0,0 +1,20 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +webhooks: + - name: my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['*'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Ignore' + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + matchConditions: + - name: 'include-bootcamp' # Each match condition must have a unique name + expression: 'object.metadata.labels.app == "kubernetes-bootcamp"' diff --git a/k8s/testdata/webhook/webhook2.yaml b/k8s/testdata/webhook/webhook2.yaml new file mode 100644 index 0000000..86a82ab --- /dev/null +++ b/k8s/testdata/webhook/webhook2.yaml @@ -0,0 +1,20 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +webhooks: + - name: my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['*'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Ignore' + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + matchConditions: + - name: 'exclude-bootcamp' # Each match condition must have a unique name + expression: '!(object.metadata.labels.app == "kubernetes-bootcamp")' diff --git a/k8s/testdata/webhook/webhook3.yaml b/k8s/testdata/webhook/webhook3.yaml new file mode 100644 index 0000000..86913b2 --- /dev/null +++ b/k8s/testdata/webhook/webhook3.yaml @@ -0,0 +1,23 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +webhooks: + - name: my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['*'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Ignore' # Fail-open (optional) + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + # You can have up to 64 matchConditions per webhook + matchConditions: + - name: 'exclude-leases' # Each match condition must have a unique name + expression: '!(request.resource.group == "coordination.k8s.io" && request.resource.resource == "leases")' # Match non-lease resources. + - name: 'exclude-kubelet-requests' + expression: '!("system:nodes" in request.userInfo.groups)' # Match requests made by non-node users. diff --git a/k8s/testdata/webhook/webhook4.yaml b/k8s/testdata/webhook/webhook4.yaml new file mode 100644 index 0000000..7556b12 --- /dev/null +++ b/k8s/testdata/webhook/webhook4.yaml @@ -0,0 +1,23 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +webhooks: + - name: rbac.my-webhook.example.com + matchPolicy: Equivalent + rules: + - operations: ['CREATE','UPDATE'] + apiGroups: ['apps'] + apiVersions: ['*'] + resources: ['*'] + failurePolicy: 'Fail' # Fail-closed (the default) + sideEffects: None + clientConfig: + service: + namespace: my-namespace + name: my-webhook + caBundle: 'PGNhYnVuZGxlPgo=' + # You can have up to 64 matchConditions per webhook + matchConditions: + - name: 'breakglass' + # Skip requests made by users authorized to 'breakglass' on this webhook. + # The 'breakglass' API verb does not need to exist outside this check. + expression: '!authorizer.group("admissionregistration.k8s.io").resource("validatingwebhookconfigurations").name("rbac.my-webhook.example.com").check("breakglass").allowed()' diff --git a/k8s/testdata_test.go b/k8s/testdata_test.go new file mode 100644 index 0000000..842a935 --- /dev/null +++ b/k8s/testdata_test.go @@ -0,0 +1,34 @@ +// Copyright 2023 Undistro 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 k8s_test + +import ( + "embed" +) + +//go:embed testdata +var testdata embed.FS + +func testfile(name string) string { + return "testdata/" + name +} + +func uint64ptr(val uint64) *uint64 { + return &val +} + +func strptr(str string) *string { + return &str +} diff --git a/k8s/validatingadmissionpolicy_test.go b/k8s/validatingadmissionpolicy_test.go index 0374ef8..a620eca 100644 --- a/k8s/validatingadmissionpolicy_test.go +++ b/k8s/validatingadmissionpolicy_test.go @@ -15,7 +15,6 @@ package k8s_test import ( - "embed" "encoding/json" "reflect" "testing" @@ -23,29 +22,26 @@ import ( "github.com/undistro/cel-playground/k8s" ) -//go:embed testdata -var testdata embed.FS - -func testfile(name string) string { - return "testdata/" + name +func vapTestfile(file string) string { + return testfile("vap/" + file) } -func readTestData(policy, original, updated, namespace, request, authorizer string) (policyData, originalData, updatedData, namespaceData, requestData, authorizerData []byte, err error) { - policyData, err = testdata.ReadFile(testfile(policy)) +func readValidationTestData(policy, original, updated, namespace, request, authorizer string) (policyData, originalData, updatedData, namespaceData, requestData, authorizerData []byte, err error) { + policyData, err = testdata.ReadFile(vapTestfile(policy)) if err == nil && original != "" { - originalData, err = testdata.ReadFile(testfile(original)) + originalData, err = testdata.ReadFile(vapTestfile(original)) } if err == nil && updated != "" { - updatedData, err = testdata.ReadFile(testfile(updated)) + updatedData, err = testdata.ReadFile(vapTestfile(updated)) } if err == nil && namespace != "" { - namespaceData, err = testdata.ReadFile(testfile(namespace)) + namespaceData, err = testdata.ReadFile(vapTestfile(namespace)) } if err == nil && request != "" { - requestData, err = testdata.ReadFile(testfile(request)) + requestData, err = testdata.ReadFile(vapTestfile(request)) } if err == nil && authorizer != "" { - authorizerData, err = testdata.ReadFile(testfile(authorizer)) + authorizerData, err = testdata.ReadFile(vapTestfile(authorizer)) } return } @@ -307,7 +303,7 @@ func TestValidationEval(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - policy, orig, updated, namespace, request, authorizer, err := readTestData(tt.policy, tt.orig, tt.updated, tt.namespace, tt.request, tt.authorizer) + policy, orig, updated, namespace, request, authorizer, err := readValidationTestData(tt.policy, tt.orig, tt.updated, tt.namespace, tt.request, tt.authorizer) var results string if err == nil { results, err = k8s.EvalValidatingAdmissionPolicy(policy, orig, updated, namespace, request, authorizer) @@ -334,11 +330,3 @@ func TestValidationEval(t *testing.T) { }) } } - -func uint64ptr(val uint64) *uint64 { - return &val -} - -func strptr(str string) *string { - return &str -} diff --git a/k8s/webhook_test.go b/k8s/webhook_test.go new file mode 100644 index 0000000..0358b3c --- /dev/null +++ b/k8s/webhook_test.go @@ -0,0 +1,164 @@ +// Copyright 2023 Undistro 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 k8s_test + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/undistro/cel-playground/k8s" +) + +func webhookTestfile(file string) string { + return testfile("webhook/" + file) +} + +func readWebhookTestData(webhook, original, updated, request, authorizer string) (webhookData, originalData, updatedData, requestData, authorizerData []byte, err error) { + webhookData, err = testdata.ReadFile(webhookTestfile(webhook)) + if err == nil && original != "" { + originalData, err = testdata.ReadFile(webhookTestfile(original)) + } + if err == nil && updated != "" { + updatedData, err = testdata.ReadFile(webhookTestfile(updated)) + } + if err == nil && request != "" { + requestData, err = testdata.ReadFile(webhookTestfile(request)) + } + if err == nil && authorizer != "" { + authorizerData, err = testdata.ReadFile(webhookTestfile(authorizer)) + } + return +} + +func TestWebhookEval(t *testing.T) { + tests := []struct { + name string + webhook string + orig string + updated string + request string + authorizer string + expected k8s.EvalResponse + wantErr bool + }{{ + name: "test a single webhook, match conditions will be successful", + webhook: "webhook1.yaml", + updated: "updated1.yaml", + expected: k8s.EvalResponse{ + WebhookMatchConditions: [][]*k8s.EvalResult{{{Name: strptr("include-bootcamp"), Result: true, Cost: uint64ptr(6)}}}, + Cost: uint64ptr(6), + }, + }, { + name: "test single webhook, match conditions will not be successful", + webhook: "webhook2.yaml", + updated: "updated2.yaml", + expected: k8s.EvalResponse{ + WebhookMatchConditions: [][]*k8s.EvalResult{{{Name: strptr("exclude-bootcamp"), Result: false, Cost: uint64ptr(7)}}}, + Cost: uint64ptr(7), + }, + }, { + name: "test a single webhook, match conditions will rely on request information", + webhook: "webhook3.yaml", + updated: "updated3.yaml", + request: "request3.yaml", + expected: k8s.EvalResponse{ + WebhookMatchConditions: [][]*k8s.EvalResult{{ + {Name: strptr("exclude-leases"), Result: true, Cost: uint64ptr(5)}, + {Name: strptr("exclude-kubelet-requests"), Result: true, Cost: uint64ptr(5)}, + }}, + Cost: uint64ptr(10), + }, + }, { + name: "test a single webhook, match conditions will rely on authorizer information", + webhook: "webhook4.yaml", + updated: "updated4.yaml", + request: "request4.yaml", + authorizer: "authorizer4.yaml", + expected: k8s.EvalResponse{ + WebhookMatchConditions: [][]*k8s.EvalResult{{ + {Name: strptr("breakglass"), Result: true, Cost: uint64ptr(7)}, + }}, + Cost: uint64ptr(7), + }, + }, { + name: "test multiple webhooks, match conditions will rely on request and authorizer information and will be successful", + webhook: "multi webhook1.yaml", + updated: "multi updated1.yaml", + request: "multi request1.yaml", + authorizer: "multi authorizer1.yaml", + expected: k8s.EvalResponse{ + WebhookMatchConditions: [][]*k8s.EvalResult{ + {{Name: strptr("breakglass"), Result: true, Cost: uint64ptr(7)}}, + {{Name: strptr("exclude-leases"), Result: true, Cost: uint64ptr(5)}, {Name: strptr("exclude-kubelet-requests"), Result: true, Cost: uint64ptr(5)}}, + }, + Cost: uint64ptr(17), + }, + }, { + name: "test multiple webhooks, match conditions will rely on request and authorizer information and will not be successful", + webhook: "multi webhook2.yaml", + updated: "multi updated2.yaml", + request: "multi request2.yaml", + authorizer: "multi authorizer2.yaml", + expected: k8s.EvalResponse{ + WebhookMatchConditions: [][]*k8s.EvalResult{ + {{Name: strptr("breakglass"), Result: false, Cost: uint64ptr(7)}}, + {{Name: strptr("exclude-bootcamp"), Result: false, Cost: uint64ptr(7)}}, + }, + Cost: uint64ptr(14), + }, + }, { + name: "test multiple webhooks, match conditions will rely on request and authorizer information with mixed responses", + webhook: "multi webhook3.yaml", + updated: "multi updated3.yaml", + request: "multi request3.yaml", + authorizer: "multi authorizer3.yaml", + expected: k8s.EvalResponse{ + WebhookMatchConditions: [][]*k8s.EvalResult{ + {{Name: strptr("breakglass"), Result: false, Cost: uint64ptr(7)}}, + {{Name: strptr("exclude-leases"), Result: true, Cost: uint64ptr(5)}, {Name: strptr("exclude-kubelet-requests"), Result: true, Cost: uint64ptr(5)}}, + }, + Cost: uint64ptr(17), + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + webhook, orig, updated, request, authorizer, err := readWebhookTestData(tt.webhook, tt.orig, tt.updated, tt.request, tt.authorizer) + var results string + if err == nil { + results, err = k8s.EvalWebhook(webhook, orig, updated, request, authorizer) + } + if err != nil { + if !tt.wantErr { + t.Errorf("Eval() error = %v, wantErr %v", err, tt.wantErr) + } + } else { + evalResponse := k8s.EvalResponse{} + if err := json.Unmarshal([]byte(results), &evalResponse); err != nil { + t.Errorf("Eval() error = %v", err) + } + if !reflect.DeepEqual(tt.expected, evalResponse) { + expected, expErr := json.Marshal(tt.expected) + response, respErr := json.Marshal(evalResponse) + if expErr != nil || respErr != nil { + t.Errorf("Error marshalling expected results or evaluated responses: %v, %v", expErr, respErr) + } else { + t.Errorf("Expected %s\n, received %s", expected, response) + } + } + } + }) + } +}