All Policies
Ensure Production Matches staging
It is common to have two separate Namespaces such as staging and production in order to test and promote app deployments in a controlled manner. In order to ensure that level of control, certain guardrails must be present so as to minimize regressions or unintended behavior. This policy has a set of three rules to try and provide some sane defaults for app promotion across these two environments (Namespaces) called staging and production. First, it makes sure that every Deployment in production has a corresponding Deployment in staging. Second, that a production Deployment uses same image name as its staging counterpart. Third, that a production Deployment uses an older or equal image version as its staging counterpart.
Policy Definition
/other/ensure-production-matches-staging/ensure-production-matches-staging.yaml
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4 name: ensure-production-matches-staging
5 annotations:
6 policies.kyverno.io/title: Ensure Production Matches staging
7 policies.kyverno.io/category: Other
8 policies.kyverno.io/minversion: 1.6.0
9 kyverno.io/kyverno-version: 1.7.0
10 kyverno.io/kubernetes-version: "1.23"
11 policies.kyverno.io/subject: Deployment
12 policies.kyverno.io/description: >-
13 It is common to have two separate Namespaces such as staging and production
14 in order to test and promote app deployments in a controlled manner. In order to ensure
15 that level of control, certain guardrails must be present so as to minimize regressions or
16 unintended behavior. This policy has a set of three rules to try and provide some sane defaults
17 for app promotion across these two environments (Namespaces) called staging and production. First,
18 it makes sure that every Deployment in production has a corresponding Deployment in staging. Second,
19 that a production Deployment uses same image name as its staging counterpart. Third, that
20 a production Deployment uses an older or equal image version as its staging counterpart.
21spec:
22 validationFailureAction: Enforce
23 background: true
24 rules:
25#######################
26# Ensures that each Deployment being created in production
27# has an existing Deployment already in staging of the same name.
28 - name: require-staging-deployment
29 match:
30 any:
31 - resources:
32 namespaces:
33 - production
34 kinds:
35 - Deployment
36 preconditions:
37 any:
38 - key: "{{request.operation || 'BACKGROUND'}}"
39 operator: AnyIn
40 value:
41 - CREATE
42 - UPDATE
43 context:
44 - name: deployment_count
45 apiCall:
46 urlPath: "/apis/apps/v1/namespaces/staging/deployments"
47 jmesPath: "items[?metadata.name=='{{ request.object.metadata.name }}'] || `[]` | length(@)"
48 validate:
49 message: "Every Deployment in production requires a corresponding Deployment in staging."
50 deny:
51 conditions:
52 any:
53 - key: "{{deployment_count}}"
54 operator: Equals
55 value: 0
56#######################
57# Ensures that each corresponding Deployment in staging and production
58# Namespaces uses the same image name (not tag).
59 - name: require-same-image
60 match:
61 any:
62 - resources:
63 namespaces:
64 - production
65 kinds:
66 - Deployment
67 preconditions:
68 all:
69 - key: "{{request.operation || 'BACKGROUND'}}"
70 operator: AnyIn
71 value:
72 - CREATE
73 - UPDATE
74 - key: "{{ deployment_count }}"
75 operator: GreaterThan
76 value: 0
77 context:
78 - name: deployment_count
79 apiCall:
80 urlPath: "/apis/apps/v1/namespaces/staging/deployments"
81 jmesPath: "items[?metadata.name=='{{ request.object.metadata.name}}'] || `[]` | length(@)"
82 - name: deployment_images
83 apiCall:
84 urlPath: "/apis/apps/v1/namespaces/staging/deployments/{{ request.object.metadata.name }}"
85 jmesPath: "spec.template.spec.containers[].image.split(@, ':')[0]"
86 validate:
87 message: "Every Deployment in production is required to use the same image(s) as in staging."
88 deny:
89 conditions:
90 any:
91 - key: "{{ request.object.spec.template.spec.containers[].image.split(@, ':')[0] }}"
92 operator: AnyNotIn
93 value: "{{ deployment_images }}"
94#######################
95# Ensures that each image version in production is less than or
96# equal to its corresponding image version in staging. Uses the container
97# name as the basis for comparison. Recommended to pair this policy
98# with another policy or rule which enforces only semver image tags, or otherwise extend this rule
99# with a precondition which filters out everything else.
100 - name: require-same-or-older-imageversion
101 match:
102 any:
103 - resources:
104 namespaces:
105 - production
106 kinds:
107 - Deployment
108 preconditions:
109 all:
110 - key: "{{request.operation || 'BACKGROUND'}}"
111 operator: AnyIn
112 value:
113 - CREATE
114 - UPDATE
115 - key: "{{ deployment_count }}"
116 operator: GreaterThan
117 value: 0
118 context:
119 - name: deployment_count
120 apiCall:
121 urlPath: "/apis/apps/v1/namespaces/staging/deployments"
122 jmesPath: "items[? metadata.name == '{{ request.object.metadata.name }}' ] | length(@)"
123 - name: deployment_containers
124 apiCall:
125 urlPath: "/apis/apps/v1/namespaces/staging/deployments/{{ request.object.metadata.name }}"
126 jmesPath: "spec.template.spec.containers[]"
127 validate:
128 message: "Every Deployment in production is required to use an image version less than or equal to the one in staging."
129 foreach:
130 - list: "request.object.spec.template.spec.containers[]"
131 deny:
132 conditions:
133 any:
134 # Uses the container name as the basis for comparing the image tag. Only semver has been tested.
135 - key: "{{ element.image.split(@,':')[1] }}"
136 operator: GreaterThan
137 value: "{{ deployment_containers[?name == '{{element.name}}'].image.split(@,':')[1] | [0] }}"