Skip to content

Commit 6de48cc

Browse files
authored
Merge pull request #28 from j-fuentes/how-to-create-packages
Add manual to write packages
2 parents fc93276 + 3b4bd68 commit 6de48cc

File tree

2 files changed

+263
-1
lines changed

2 files changed

+263
-1
lines changed

docs/how_to_write_packages.md

Lines changed: 263 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,265 @@
11
# How to write Preflight Packages
22

3-
We are writing a comprehensive manual on how to create your own packages. In the meanwhile, we have [this basic tutorial on how to encode a policy into Preflight Packages](https://blog.jetstack.io/blog/introducing-preflight/#encoding-your-policy-into-preflight-packages).
3+
## What is a Preflight package?
4+
5+
A Preflight package contains the definition of a policy. A policy is a set of rules that Preflight will check in your cluster.
6+
7+
Preflight packages are made of two well distinguished parts, the _policy manifest_ and the _Rego_ definition of the rules.
8+
9+
<img align="center" width="460" height="300" src="./images/preflight_package.png">
10+
11+
## Writing the _policy manifest_
12+
13+
The _policy manifest_ is a _YAML_ file that contains information about your policy. You can see [here](https://godoc.org/github.com/jetstack/preflight/pkg/packaging#PolicyManifest) the schema of this file.
14+
15+
There is some metadata for the package, such as the name and the description.
16+
17+
The rules of your policy must also be declared here. They are organized in sections. Every section can have a name, a description, and a list of rules.
18+
19+
Every rule has its own name, description, useful links, and remediation instructions. This guide has a dedicated section about how to add rules.
20+
21+
### Choose your _data-gatherers_
22+
23+
Preflight evaluates the policies against data that it fetches from different sources. These sources are the _data-gatherers_. You can see [here](./datagatherers) a list of the available _data-gatherers_ and their documentation. In the future, Preflight will support sourcing data from external _data-gatherers_ (#24).
24+
25+
The _data-gatherers_ your package requires should be declared in the _policy manifest_.
26+
27+
```yaml
28+
schema-version: "1.0.0"
29+
id: "mypackage"
30+
namespace: "examples.jetstack.io"
31+
package-version: "1.0.0"
32+
data-gatherers:
33+
- k8s/pods
34+
- gke
35+
...
36+
```
37+
38+
### Versioning
39+
40+
Preflight packages are intended to evolve. Policies can be changed, new rules can be added or deleted, and all the metadata can mutate.
41+
42+
In order to ease keeping track of those changes, Preflight packages have a version tag. That version is specified with the `package-version` property in the _policy manifest_.
43+
44+
### The minimal _policy manifest_
45+
46+
Let's just write the minimal _policy manifest_ possible.
47+
48+
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`.
49+
50+
Then create the `policy-manifest.yaml` file. The following fields are mandatory:
51+
52+
- `schema-version`: indicates which schema is being used for the _policy manifest_. For the moment, there is only version `1.0.0`.
53+
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+
58+
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.
59+
60+
For simplicity's sake, this example contains just one section with one rule.
61+
62+
```
63+
# preflight-packages/examples.jetstack.io/podsbestpractices/policy-manifest.yaml
64+
65+
schema-version: "1.0.0"
66+
id: "podsbestpractices"
67+
namespace: "examples.jetstack.io"
68+
package-version: "1.0.0"
69+
root-query: "data.pods" # the concept of `root-query` is explained later in this doc
70+
data-gatherers:
71+
- k8s/pods
72+
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/"
85+
```
86+
87+
## Writing the policy definition in Rego
88+
89+
In the previous section, we created the _policy manifest_, which contains a human readable description of the rules in our policy. Now it's time to define the same rules in a language that is machine readable.
90+
91+
### The Rego package
92+
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+
95+
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.
96+
97+
For instance, this snippet shows an arbitrary Rego rule in a package named `podsbestpractices`:
98+
99+
```
100+
package pods
101+
102+
import input["k8s/pods"] as pods
103+
104+
preflight_tag_not_latest {
105+
true
106+
}
107+
```
108+
109+
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"`.
110+
111+
### Writing Rego rules
112+
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`.
116+
117+
118+
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).
119+
120+
```
121+
# preflight-packages/examples.jetstack.io/podsbestpractices/policy.rego
122+
123+
package pods
124+
125+
import input["k8s/pods"] as pods
126+
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+
}
151+
152+
containers_using_latest[container] {
153+
container := all_containers[_]
154+
not re_match(".*:.+", container.image)
155+
}
156+
```
157+
158+
### Testing Rego
159+
160+
As mentioned before, it is very useful to [write tests for the Rego rules](https://www.openpolicyagent.org/docs/latest/policy-testing/).
161+
162+
This snippet contains a testsuite for the previous Rego code.
163+
164+
```
165+
# preflight-packages/examples.jetstack.io/podsbestpractices/policy_test.rego
166+
167+
package pods
168+
169+
pods(x) = y { y := {"k8s/pods": {"items": x }} }
170+
171+
test_tag_not_latest_no_pods {
172+
preflight_tag_not_latest with input as pods([])
173+
}
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+
])
193+
}
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+
])
203+
}
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+
])
220+
}
221+
```
222+
223+
Soon, Preflight will be able to run Rego tests inside Preflight packages (#26), but unfortunatelly this is not possible yet.
224+
225+
However it is possible to run these tests directly with the [OPA command line](https://www.openpolicyagent.org/docs/latest/#running-opa):
226+
227+
```
228+
opa test ./preflight-packages/examples.jetstack.io/podsbestpractices
229+
```
230+
231+
## Lint your packages
232+
233+
The Preflight command line has a built-in linter. This helps to make sure that the package follows the best practices.
234+
235+
You can lint your package by running:
236+
237+
```
238+
preflight package lint ./preflight-packages/examples.jetstack.io/podsbestpractices
239+
```
240+
241+
## Configure Preflight to use your package
242+
243+
The last step would be to tell Preflight to actually use these new package. That configuration goes into the `preflight.yaml` file. For this example, it would look like this:
244+
245+
```yaml
246+
# preflight.yaml
247+
cluster-name: my-cluster
248+
249+
data-gatherers:
250+
k8s/pods:
251+
kubeconfig: ~/.kube/config
252+
253+
package-sources:
254+
- type: local
255+
dir: preflight-packages/
256+
257+
enabled-packages:
258+
- "examples.jetstack.io/podsbestpractice"
259+
260+
outputs:
261+
- type: local
262+
path: ./output
263+
format: json
264+
- type: cli
265+
```

docs/images/preflight_package.png

29.6 KB
Loading

0 commit comments

Comments
 (0)