Conftest: Expressing and Enforcing Policy for Configuration Systems
Anthony Critelli
You don't need a picture of me because I'm standing right in front of you, so here are my dogs: Kramer and Newman
hosts: all
tasks:
- name: Set file permissions
ansible.builtin.file:
path: /etc/passwd
mode: 0777
- name: Install very important package
ansible.builtin.package:
name: telnet
state: latest
Write config with policy violation
Perform code review. Human may or may not notice bad practice
Policy violation may or may not make it into production
# Scalar
admin_user := "bob"
# List
packages := [ "telnet", "netcat" ]
# Set
apps_set := { "telnet", "netcat", "ping", "tcpdump" }
# List of objects
users := [
{
"name": "alice",
"admin": true,
"account_enabled": false
},
{
"name": "bob",
"admin": true,
"account_enabled": true
},
{
"name": "trudy",
"admin": false,
"account_enabled": true
}
]
package main
import future.keywords.in
import future.keywords.if
# Constants are technically rules
apps := [ "telnet", "netcat", "ping", "tcpdump" ]
# The rules below are equivalent
# Uses newer keywords "if" and "in"
contains_netcat := true if {
some app in apps
app == "netcat"
}
# If the head doesn't define a value, then it defaults to true
contains_netcat {
apps[_] == "netcat"
}
package main
import future.keywords.in
import future.keywords.if
import future.keywords.contains
apps := [
{ "name": "apache", "latest": false },
{ "name": "java", "latest": true },
{ "name": "vim", "latest": false },
]
outdated_packages contains app.name if {
some app in apps
app.latest == false
}
outdated_packages[name] {
some app in apps
not app.latest
name := app.name
}
outdated_apache {
some app
outdated_packages[app]
app == "apache"
}
$ wget https://github.com/open-policy-agent/conftest/releases/download/v0.34.0/conftest_0.34.0_Linux_x86_64.tar.gz
$ tar -xf conftest_0.34.0_Linux_x86_64.tar.gz
$ sudo mv conftest /usr/local/bin/conftest
$ tree
.
├── playbook.yaml
├── policy
│ └── main.rego
└── README.md
1 directory, 3 files
2. Run tests against config
$ conftest test playbook.yaml
FAIL - playbook.yaml - main - Netcat is not a permitted package
1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions
package security.example.com
...
warn_sudo contains msg if {
some key,val in input.path
"sudo" in val.capabilities
msg := sprintf("Sudo capabilities granted on %v", [key])
}
$ conftest test docker-compose.yaml --data data.yaml
FAIL - docker-compose.yaml - main - Port 3306 is not allowed to be exposed.
1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions
import data.allowed_ports
deny_ports contains msg if {
some service in input.services
some published_port in service.ports
port := split(published_port, ":")[0]
not to_number(port) in allowed_ports
msg := sprintf("Port %v is not allowed to be exposed.", [port])
}
-o, --output string Output format for conftest results - valid options are: [stdout json tap table junit github] (default "stdout")
$ git commit -m "Add initial Dockerfile"
Running conftest pre-commit hook...
FAIL - Dockerfile - main - Base image 'ubuntu:12.04' is not in list of allowed base images
1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions
# Parsed config files can then be used in the Rego playground
$ conftest parse vault_policy.hcl
{
"path": {
"auth/token/*": {
"capabilities": [
"create",
"read",
"update",
"sudo"
]
},
"monolithic-database/*": {
"capabilities": [
"create",
"read",
"update"
]
}
}
}
Contact: critellia at gee mail dot com
Connect: https://www.linkedin.com/in/anthonycritelli/