Slide 1

Slide 1 text

Using Open Policy Agent Gareth Rushgrove | Director, Product Management | Snyk snyk.io Unit testing your Kubernetes configuration May, 2019

Slide 2

Slide 2 text

snyk.io

Slide 3

Slide 3 text

snyk.io - A quick introduction to Open Policy Agent - Shift-left testing - Introducing conftest - Rego as a programming language - Portability between different Kubernetes solutions - Not just Kubernetes Agenda

Slide 4

Slide 4 text

Open Policy Agent snyk.io A policy enforcement engine for configuration

Slide 5

Slide 5 text

What is Open Policy Agent?

Slide 6

Slide 6 text

Policy Data OPA Service How Open Policy Agent works

Slide 7

Slide 7 text

A growing ecosystem

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Shift left snyk.io Faster feedback

Slide 10

Slide 10 text

Shift-left testing is an approach to software testing and system testing in which testing is performed earlier in the lifecycle (i.e., moved left on the project timeline). Wikipedia

Slide 11

Slide 11 text

Development cycle Fast Slow Slower Cluster Local development Continuous integration

Slide 12

Slide 12 text

Open Policy Agent is normally used here Development cycle Cluster Local development Continuous integration

Slide 13

Slide 13 text

Cluster What if we could use Open Policy Agent here as well? Development cycle Local development Continuous integration

Slide 14

Slide 14 text

“Everything as code” Said several times at the CDF event

Slide 15

Slide 15 text

“Would you write code without tests?”

Slide 16

Slide 16 text

Introducing conftest snyk.io Test your configuration locally, and in CI

Slide 17

Slide 17 text

Introducing conftest

Slide 18

Slide 18 text

apiVersion: apps/v1 kind: Deployment metadata: name: hello-kubernetes spec: replicas: 3 selector: matchLabels: app: hello-kubernetes template: metadata: labels: app: hello-kubernetes spec: containers: - name: hello-kubernetes image: paulbouwer/hello-kubernetes:1.5 ports: Given a deployment snyk.io

Slide 19

Slide 19 text

package main deny[msg] { input.kind = "Deployment" not input.spec.template.spec.securityContext.runAsNonRoot = true msg = "Containers must not run as root" } deny[msg] { input.kind = "Deployment" not input.spec.selector.matchLabels.app msg = "Containers must provide app label for pod selectors" } Write your policies snyk.io

Slide 20

Slide 20 text

deny[msg] { input.kind = "Deployment" not input.spec.template.spec.securityContext.runAsNonRoot = true msg = "Containers must not run as root" } Explaining what we just wrote snyk.io We should deny any input for which Deployment is the value for kind and When runAsNonRoot is set to false

Slide 21

Slide 21 text

$ conftest test deployment.yaml deployment.yaml Containers must not run as root $ echo $status 1 Run tests locally with conftest snyk.io

Slide 22

Slide 22 text

snyk.io Demo

Slide 23

Slide 23 text

Rego as a language snyk.io The usual pros and cons of a DSL

Slide 24

Slide 24 text

Good documentation

Slide 25

Slide 25 text

The Rego playground

Slide 26

Slide 26 text

package main test_deployment_without_security_context { deny["Containers must not run as root"] with input as {"kind": "Deployment"} } test_deployment_with_security_context { no_violations with input as {"kind": "Deployment", "spec": { "selector": { "matchLabels": { "app": "something", "release": "something" }}, "template": { "spec": { "securityContext": { "runAsNonRoot": true }}}}} } test_services_not_denied { no_violations with input as {"kind": "Service"} } test_services_issue_warning { warn["Services are not allowed"] with input as {"kind": "Service"} Built-in testing tools

Slide 27

Slide 27 text

$ opa test --verbose . data.main.test_deployment_without_security_context: PASS (1.029µs) data.main.test_deployment_with_security_context: PASS (1.058µs) data.main.test_services_not_denied: PASS (701ns) data.main.test_services_issue_warning: PASS (614ns) --------------------------------------------------------------------- PASS: 4/4 Open Policy Agent test runner snyk.io

Slide 28

Slide 28 text

In:path .rego extension:rego 546 results Not much public rego code yet

Slide 29

Slide 29 text

Open Policy Agent Bundles

Slide 30

Slide 30 text

package main # has_field returns whether an object has a field has_field(object, field) { object[field] } # False is a tricky special case, as false responses would create an undefined # document unless they are explicitly tested for has_field(object, field) { object[field] == false } has_field(object, field) = false { General helpers

Slide 31

Slide 31 text

package main empty(value) { count(value) == 0 } no_violations { empty(deny) } no_warnings { empty(warn) } Test helpers snyk.io

Slide 32

Slide 32 text

package kubernetes is_service { input.kind = "Service" } is_deployment { input.kind = "Deployment" } Domain specific helpers snyk.io

Slide 33

Slide 33 text

Reusing OCI registries

Slide 34

Slide 34 text

Proposed OCI media types of OPA

Slide 35

Slide 35 text

$ ls kubernetes.rego $ conftest push instrumenta.azurecr.io/kubernetes-helpers $ ls policy $ conftest pull instrumenta.azurecr.io/kubernetes-helpers:latest $ ls policy kubernetes.rego Using conftest to share policy snyk.io

Slide 36

Slide 36 text

Bundle Bar

Slide 37

Slide 37 text

Portability snyk.io Helping to move between different Kubernetes tools

Slide 38

Slide 38 text

snyk.io Demo

Slide 39

Slide 39 text

$ kustomize build | conftest test - Kustomize snyk.io

Slide 40

Slide 40 text

$ helm template | conftest test - Helm snyk.io

Slide 41

Slide 41 text

import {Pod} from 'kubernetes-types/core/v1' import {ObjectMeta} from 'kubernetes-types/meta/v1' import * as yaml from 'js-yaml' let metadata: ObjectMeta = {name: 'example', labels: {}} let pod: Pod = { apiVersion: 'v1', kind: 'Pod', metadata, spec: { containers: [ /* ... */ ], }, } console.log(yaml.safeDump(pod)) Typescript

Slide 42

Slide 42 text

$ npx ts-node pod.ts | conftest test - Typescript snyk.io

Slide 43

Slide 43 text

$ kubectl get all -o json \ | jq -cj '.items[] | tostring+"\u0000"' \ | xargs -n1 -0 -I@ bash -c "echo '@' | conftest test -" Kubectl (look away now) snyk.io

Slide 44

Slide 44 text

$ kubectl krew install conftest $ kubectl conftest deployment some-deployment Kubectl plugin snyk.io

Slide 45

Slide 45 text

package kubernetes deployment "hello-kubernetes": { apiVersion: "apps/v1" spec: { replicas: 3 template spec containers: [{ image: "paulbouwer/hello-kubernetes:1.5" ports: [{ containerPort: 8080 }] }] } } Cue snyk.io

Slide 46

Slide 46 text

package kubernetes import "encoding/yaml" command test: { task conftest: { kind: "exec" cmd: "conftest test -" stdin: yaml.MarshalStream(objects) } } Cue snyk.io

Slide 47

Slide 47 text

$ cue test Containers must not run as root --- . command "conftest test -" failed: exit status 1 terminating because of errors Cue snyk.io

Slide 48

Slide 48 text

Not just Kubernetes snyk.io Lots of other configurations to care about

Slide 49

Slide 49 text

apiVersion: kind: extension:yaml 1,054,453 results Lots of Kubernetes-like docs

Slide 50

Slide 50 text

in:path .yml language:YAML 69,822,306 results Lots more YAML

Slide 51

Slide 51 text

snyk.io Demo

Slide 52

Slide 52 text

$ terraform plan -out config.tfplan $ terraform show -json config.tfplan | configtest test - Terraform snyk.io

Slide 53

Slide 53 text

package main blacklist = [ "google_iam", "google_container" ] deny[msg] { check_resources(input.resource_changes, blacklist) banned := concat(", ", blacklist) msg = sprintf("Terraform plan will change prohibited resources in: %v", [banned]) } # Checks whether the plan will cause resources with certain prefixes to change check_resources(resources, disallowed_prefixes) { startswith(resources[_].type, disallowed_prefixes[_]) } Terraform snyk.io

Slide 54

Slide 54 text

service: aws-python-scheduled-cron frameworkVersion: ">=1.2.0 <2.0.0" provider: name: aws runtime: python2.7 tags: author: "this field is required" functions: cron: handler: handler.run runtime: python2.7 events: - schedule: cron(0/2 * ? * MON-FRI *) Serverless framework snyk.io

Slide 55

Slide 55 text

package main deny[msg] { input.provider.runtime = "python2.7" msg = "Python 2.7 cannot be the default provider runtime" } runtime[name] { input.functions[i].runtime = name } deny[msg] { runtime["python2.7"] msg = "Python 2.7 cannot be used as the runtime for functions" } deny[msg] { not has_field(input.provider.tags, "author") msg = "Should set provider tags for author" } Serverless framework snyk.io

Slide 56

Slide 56 text

version: "3.4" services: web: build: . ports: - "5000:5000" redis: image: "redis:latest" Docker Compose snyk.io

Slide 57

Slide 57 text

package main version { to_number(input.version) } deny[msg] { endswith(input.services[_].image, ":latest") msg = "No images tagged latest" } deny[msg] { version < 3.5 msg = "Must be using at least version 3.5 of the Compose file format" } Docker Compose snyk.io

Slide 58

Slide 58 text

Conclusions snyk.io If all you remember is...

Slide 59

Slide 59 text

@jdolitsky @stevelasker @sometorin Thanks open source community!

Slide 60

Slide 60 text

snyk.io - Open Policy Agent is incredibly flexible - Check out conftest at github.com/instrumenta/conftest - Expect lots more integrations in the future - Managing configuration as code needs better tools Come talk to Snyk at booth #S41 Summary

Slide 61

Slide 61 text

Questions? snyk.io And thanks for listening