Skip to main content

Conftest

Conftest es una herramienta muy útil para probar políticas usando el lenguaje Rego.

No necesitamos esperar llegar al proceso de deploy para confrontar esas políticas en el controlador de admisión. Junto al repositorio del proyecto, o en otro repositorio, podemos tener esas políticas configuradas y confrontar los manifiestos de los Dockerfiles para evitar un pipeline completo y en el último segundo descubrir que tenemos errores.

Por defecto, en el mismo directorio que estamos ejecutando el comando conftest serán buscadas las reglas en el directorio policy, pero podemos apuntar un directorio donde tenemos las políticas escritas en Rego que vamos a confrontar.

Vamos a instalar el cli, pero podríamos usar vía docker obviamente.

LATEST_VERSION=$(wget -O - "https://api.github.com/repos/open-policy-agent/conftest/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-)
ARCH=$(arch)
SYSTEM=$(uname)
wget "https://github.com/open-policy-agent/conftest/releases/download/v${LATEST_VERSION}/conftest_${LATEST_VERSION}_${SYSTEM}_${ARCH}.tar.gz"
tar xzf conftest_${LATEST_VERSION}_${SYSTEM}_${ARCH}.tar.gz
sudo mv conftest /usr/local/bin

root@cks-master:~# conftest --version
Conftest: 0.55.0
OPA: 0.67.0

Vamos a crear dos carpetas

# Creando la carpeta policy para no tener que estar pasando como parámetro

root@cks-master:~# mkdir -p policy/kubernetes
root@cks-master:~# mkdir -p policy/dockerfiles

# Reglas para deployment
root@cks-master:~# vim policy/kubernetes/deployment.rego
# OBS: en esta regla está esperando securityContext a nivel de pod y no de contenedores.
root@cks-master:~# cat policy/kubernetes/deployment.rego
# from https://www.conftest.dev
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"
}
root@cks-master:~# vim policy/dockerfiles/base.rego
root@cks-master:~# cat policy/dockerfiles/base.rego
# from https://www.conftest.dev
package main

denylist = [
"ubuntu"
]

deny[msg] {
input[i].Cmd == "from"
val := input[i].Value
contains(val[i], denylist[_])

msg = sprintf("unallowed image found %s", [val])
}
root@cks-master:~# vim policy/dockerfiles/commands.rego
root@cks-master:~# cat policy/dockerfiles/commands.rego
# from https://www.conftest.dev

package commands

denylist = [
"apk",
"apt",
"pip",
"curl",
"wget",
]

deny[msg] {
input[i].Cmd == "run"
val := input[i].Value
contains(val[_], denylist[_])

msg = sprintf("unallowed commands found %s", [val])
}

Ahora vamos a crear un deployment y verificar con conftest.

root@cks-master:~# k create deployment httpd --image=httpd -oyaml --dry-run=client > httpd.yaml

root@cks-master:~# cat httpd.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: httpd
name: httpd
spec:
replicas: 1
selector:
matchLabels:
app: httpd
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: httpd
spec:
containers:
- image: httpd
name: httpd
resources: {}
status: {}

# Perfecto, porque tenemos la etiqueta app, pero está faltando runAsNonRoot a nivel de pod.
root@cks-master:~# conftest test httpd.yaml --policy ./policy/kubernetes/
FAIL - httpd.yaml - main - Containers must not run as root

2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions


# Resolviendo
root@cks-master:~# vim httpd.yaml
root@cks-master:~# cat httpd.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: httpd
name: httpd
spec:
replicas: 1
selector:
matchLabels:
app: httpd
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: httpd
spec:
securityContext:
runAsNonRoot: true
containers:
- image: httpd
name: httpd
resources: {}
status: {}
root@cks-master:~# conftest test httpd.yaml --policy ./policy/kubernetes/

2 tests, 2 passed, 0 warnings, 0 failures, 0 exceptions

Creamos reglas para base (FROM) y commands (RUN) del Dockerfile. No podemos tener la imagen ubuntu y no podemos tener las palabras apk, apt, pip, curl y wget en los comandos RUN. Vamos a crear un Dockerfile y probar.

root@cks-master:~# vim Dockerfile

root@cks-master:~# cat Dockerfile
FROM ubuntu
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y golang-go
COPY app.go .
RUN go build app.go
CMD ["./app"]

# Es necesario pasar todos los namespaces porque cada package forma un namespace, si no se pasa será analizado apenas el main que es el namespace por defecto.

root@cks-master:~# conftest test Dockerfile --policy ./policy/dockerfiles/ --all-namespaces
FAIL - Dockerfile - main - unallowed image found ["ubuntu"]
FAIL - Dockerfile - commands - unallowed commands found ["apt-get update && apt-get install -y golang-go"]

2 tests, 0 passed, 0 warnings, 2 failures, 0 exceptions

# Cambiamos la imagen para alpine
root@cks-master:~# vim Dockerfile
# Estamos teniendo problemas, porque no permite la palabra apt, vamos a cambiar esa política y remover esa palabra.
root@cks-master:~# conftest test Dockerfile --policy ./policy/dockerfiles/ --all-namespaces
FAIL - Dockerfile - commands - unallowed commands found ["apt-get update && apt-get install -y golang-go"]

2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions
root@cks-master:~# vim policy/dockerfiles/commands.rego
# apt removido...
root@cks-master:~# cat policy/dockerfiles/commands.rego
# from https://www.conftest.dev

package commands

denylist = [
"apk",
"pip",
"curl",
"wget",
]

deny[msg] {
input[i].Cmd == "run"
val := input[i].Value
contains(val[_], denylist[_])

msg = sprintf("unallowed commands found %s", [val])
}

# Todo correcto
root@cks-master:~# conftest test Dockerfile --policy ./policy/dockerfiles/ --all-namespaces

2 tests, 2 passed, 0 warnings, 0 failures, 0 exceptions
root@cks-master:~#

Bueno, tenemos ahí una herramienta más que ayudará en las pruebas locales y de pipeline.

Si quiere ejecutar como docker el comando será...

# mapeamos el volumen donde tenemos la carpeta policy
root@cks-master:~# docker run --rm -v $(pwd):/project openpolicyagent/conftest test Dockerfile --all-namespaces --policy policy/dockerfiles/

2 tests, 2 passed, 0 warnings, 0 failures, 0 exceptions
root@cks-master:~#