Skip to content

Commit e27513a

Browse files
committed
Update the writing packages guide
Signed-off-by: Charlie Egan <charlieegan3@users.noreply.github.com>
1 parent 0d2b91b commit e27513a

File tree

1 file changed

+65
-99
lines changed

1 file changed

+65
-99
lines changed

docs/how_to_write_packages.md

Lines changed: 65 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,18 @@ In order to ease keeping track of those changes, Preflight packages have a versi
4343

4444
### The minimal _policy manifest_
4545

46-
Let's just write the minimal _policy manifest_ possible.
46+
Let's write a minimal _policy manifest_ to get started.
4747

4848
First, create a directory for this new package. We are going to create this new package under the `examples.jetstack.io` namespace, and we are going to name it `podsbestpractices`.
4949

5050
Then create the `policy-manifest.yaml` file. The following fields are mandatory:
5151

52-
- `schema-version`: indicates which schema is being used for the _policy manifest_. For the moment, there is only version `1.0.0`.
52+
- `schema-version`: indicates which schema is being used for the _policy manifest_. For now, there is only version: `1.0.0`.
53+
- `namespace`, `id`, and `package-version`: these properties identify the package. `namespace` must be a FQDN and it is encouraged that `package-version` uses [semver](https://semver.org).
54+
- `root-query`: Name of the Rego package containing the rules backing the
55+
package (see below).
5356

54-
- `namespace`, `id`, and `package-version`: these properties identify the package. `namespace` must be a FQDN and it is encouraged that `package-version` uses semver.
55-
56-
Then, you should also declare the _data-gatherers_ that your rules are going to need. For this example, let's just use `k8s/pods`.
57+
Then, you should also declare the _data-gatherers_ that your rules are going to need. For this example, we only need one, `k8s/pods`.
5758

5859
Finally, it's time to declare the rules for the policy. Rules are organized into sections. Every section has an ID, a name, and a description. Also, every rule has its own ID, name, and description. Additionally, rules can have other metadata like a remediation advice or a set of related links.
5960

@@ -70,18 +71,18 @@ root-query: "data.pods" # the concept of `root-query` is explained later in this
7071
data-gatherers:
7172
- k8s/pods
7273
sections:
73-
- id: images
74-
name: Images
75-
description: "Restrictions over the images."
76-
rules:
77-
- id: tag_not_latest
78-
name: "Tag is not latest"
79-
description: >
80-
Avoid using "latest" as tag for the image since.
81-
remediation: >
82-
Change your manifest and edit the Pod template so the image is pinned to a certain tag.
83-
links:
84-
- "https://kubernetes.io/docs/concepts/containers/images/"
74+
- id: images
75+
name: Images
76+
description: "Restrictions over the images."
77+
rules:
78+
- id: tag_not_latest
79+
name: "Tag is not latest"
80+
description: >
81+
Avoid using "latest" as tag for the image since.
82+
remediation: >
83+
Change your manifest and edit the Pod template so the image is pinned to a certain tag.
84+
links:
85+
- "https://kubernetes.io/docs/concepts/containers/images/"
8586
```
8687

8788
## Writing the policy definition in Rego
@@ -90,7 +91,7 @@ In the previous section, we created the _policy manifest_, which contains a huma
9091

9192
### The Rego package
9293

93-
Preflight relies on Open Policy Agent as the policy engine. Rego is OPA's language to define policies. You can find a comprenhensive [documentation](https://www.openpolicyagent.org/docs/latest/policy-language/).
94+
Preflight relies on Open Policy Agent as the policy engine. Rego is OPA's language to define policies. You can find their comprehensive [documentation here](https://www.openpolicyagent.org/docs/latest/policy-language/).
9495

9596
You can have multiple Rego files inside the directory of a Preflight package. All the Rego rules corresponding to the _policy manifest_ rules must be in the same Rego package, and that package must be indicated in the _policy manifest_ using the `root-query` property.
9697

@@ -101,19 +102,19 @@ package pods
101102
102103
import input["k8s/pods"] as pods
103104
104-
preflight_tag_not_latest {
105+
preflight_tag_not_latest[message] {
105106
true
107+
message := "true was found to be true"
106108
}
107109
```
108110

109111
As you can identify, the Rego package for that policy is `pods`. In this case, OPA's `root-query` is `data.pods`, and that is why in the previous section, `policy-manifest.yaml` contains `root-query: "data.pods"`.
110112

111113
### Writing Rego rules
112114

113-
Rego can be challenging at the beginning because it does not behaves like a traditional programming language. It is strongly recommended to read ["The Basics"](https://www.openpolicyagent.org/docs/latest/policy-language/#the-basics). Also, it is useful to have the [language refence](https://www.openpolicyagent.org/docs/latest/policy-reference/) at hand.
114-
115-
You will get faster as you write more Rego rules. In order to speed up this process, it's best to write tests for your rules, even if you think they are not needed. It means you can iterate fast while writing rules and make sure the rules are doing what you intended. It is conventional to name the test files for `policy.rego` as `policy_test.rego`.
115+
Rego is a declarative language and has a bit of a learning curve. It is strongly recommended to read ["The Basics"](https://www.openpolicyagent.org/docs/latest/policy-language/#the-basics). Also, it is useful to have the [language reference](https://www.openpolicyagent.org/docs/latest/policy-reference/) to hand.
116116

117+
In order to speed up the process of writing Rego rules, it's best to write tests. It means you can iterate fast while writing rules and make sure the rules are doing what you intended without int. It is conventional to name the test files for `policy.rego` as `policy_test.rego`.
117118

118119
This example contains the definition for the `tag_no_latest` rule. As you can see, there is the convention within Preflight to add `preflight_` as prefix to the rule ID when that is written in Rego (related issue #27).
119120

@@ -124,42 +125,24 @@ package pods
124125
125126
import input["k8s/pods"] as pods
126127
127-
default preflight_tag_not_latest = false
128-
preflight_tag_not_latest {
129-
count(containers_using_latest) == 0
130-
}
131-
132-
format_container(pod, container) = name {
133-
name := {
134-
"namespace": pod.metadata.namespace,
135-
"pod": pod.metadata.name,
136-
"image": container.image,
137-
"name": container.name
138-
}
139-
}
140-
141-
all_containers[container_name] {
142-
pod := pods.items[_]
143-
container := pod.spec.containers[_]
144-
container_name = format_container(pod, container)
145-
}
146-
147-
containers_using_latest[container] {
148-
container := all_containers[_]
149-
re_match(".*:latest", container.image)
150-
}
128+
preflight_explicit_image_tag[message] {
129+
# find all containers in all pods
130+
pod := pods.items[_]
131+
container := pod.spec.containers[_]
132+
# validate that the image value contains an explicit tag
133+
{ re_match(`latest$`, container.image),
134+
re_match(`^[^:]+$`, container.image) } & { true } != set()
151135
152-
containers_using_latest[container] {
153-
container := all_containers[_]
154-
not re_match(".*:.+", container.image)
136+
# bind a message for reporting
137+
message := sprintf("container '%s' in pod '%s' in namespace '%s' is missing an explicit image tag", [container.name, pod.metadata.name, pod.metadata.namespace])
155138
}
156139
```
157140

158141
### Testing Rego
159142

160-
As mentioned before, it is very useful to [write tests for the Rego rules](https://www.openpolicyagent.org/docs/latest/policy-testing/).
143+
As mentioned before, it is very useful to [write tests for your Rego rules](https://www.openpolicyagent.org/docs/latest/policy-testing/).
161144

162-
This snippet contains a testsuite for the previous Rego code.
145+
This snippet contains a test case for the previous Rego code.
163146

164147
```
165148
# preflight-packages/examples.jetstack.io/podsbestpractices/policy_test.rego
@@ -168,61 +151,44 @@ package pods
168151
169152
pods(x) = y { y := {"k8s/pods": {"items": x }} }
170153
171-
test_tag_not_latest_no_pods {
172-
preflight_tag_not_latest with input as pods([])
154+
assert_allowed(output) = output {
155+
trace(sprintf("GOT: %s", [concat(",", output)]))
156+
trace("WANT: empty set")
157+
output == set()
173158
}
174-
test_tag_not_latest_v1 {
175-
preflight_tag_not_latest with input as pods([
176-
{
177-
"metadata": { "namespace": "default", "name": "p1" },
178-
"spec": { "containers":[
179-
{"name": "c1", "image": "golang:v1"},
180-
]}
181-
}
182-
])
183-
}
184-
test_tag_not_latest_latest {
185-
not preflight_tag_not_latest with input as pods([
186-
{
187-
"metadata": { "namespace": "default", "name": "p1" },
188-
"spec": { "containers":[
189-
{"name": "c1", "image": "golang:latest"}
190-
]}
191-
}
192-
])
159+
160+
assert_violates(output, messages) = output {
161+
trace(sprintf("GOT: %s", [concat(",", output)]))
162+
trace(sprintf("WANT: %s", [concat(",", messages)]))
163+
164+
output == messages
193165
}
194-
test_tag_not_latest_latest_implicit {
195-
not preflight_tag_not_latest with input as pods([
196-
{
197-
"metadata": { "namespace": "default", "name": "p1" },
198-
"spec": { "containers":[
199-
{"name": "c1", "image": "golang"}
200-
]}
201-
}
202-
])
166+
167+
test_explicit_image_tag_no_pods {
168+
output := preflight_explicit_image_tag with input as pods([])
169+
assert_allowed(output)
203170
}
204-
test_tag_not_latest_latest_multiple {
205-
not preflight_tag_not_latest with input as pods([
206-
{
207-
"metadata": { "namespace": "default", "name": "p1" },
208-
"spec": { "containers":[
209-
{"name": "c1", "image": "golang:v1"}
210-
]}
211-
},
212-
{
213-
"metadata": { "namespace": "default", "name": "p2"},
214-
"spec": { "containers":[
215-
{"name": "c1", "image": "golang:v2"},
216-
{"name": "c2", "image": "golang:latest"},
217-
]}
218-
}
219-
])
171+
172+
test_explicit_image_tag_latest_tag {
173+
output := preflight_explicit_image_tag with input as pods([
174+
{"metadata": {
175+
"name": "foo",
176+
"namespace": "default"
177+
},
178+
"spec":{"containers":[
179+
{"name": "container-one",
180+
"image": "gcr.io/my-project/my-image:latest"}
181+
]}}])
182+
183+
assert_violates(output, {
184+
"container 'container-one' in pod 'foo' in namespace 'default' is missing an explicit image tag"
185+
})
220186
}
221187
```
222188

223-
Soon, Preflight will be able to run Rego tests inside Preflight packages (#26), but unfortunatelly this is not possible yet.
189+
Soon, Preflight will be able to run Rego tests inside Preflight packages (#26), but unfortunately this is not possible yet.
224190

225-
However it is possible to run these tests directly with the [OPA command line](https://www.openpolicyagent.org/docs/latest/#running-opa):
191+
To run our tests, we must use the [OPA command line](https://www.openpolicyagent.org/docs/latest/#running-opa) like this:
226192

227193
```
228194
opa test -v --explain=notes ./preflight-packages/examples.jetstack.io/podsbestpractices

0 commit comments

Comments
 (0)