Policy checks and safety
Policy rules on every plan, scanner commands like Checkov, audited overrides, the two-person rule, and tamper-proof PR output.
Policy checks
Rules run on every plan. In audit mode they report; in enforce mode they also block apply. Only
people in override can apply anyway, with teriac apply -p <project> --override. That is audited.
--override skips only the checks whose override list includes you; see
overrides.
policies: # in repos.yaml (server side, so a PR can't change them)
mode: enforce # off | audit | enforce (a repo can only make it stricter)
override: [team:security]
rules:
- name: no-public-s3
resource: aws_s3_bucket_acl
deny_if: { attribute: acl, in: [public-read, public-read-write] }
- name: rds-encrypted
resource: aws_db_instance
require: { attribute: storage_encrypted, equals: true }
- name: no-prod-destroy
projects: ["prod/**"]
actions: [delete, replace]
message: Destroying in prod needs a security override
builtin: [security] # ready-made AWS security rules (below)
disable: [aws-kms-rotation-off] # turn single built-in rules off
commands: # any scanner; non-zero exit = finding
- name: checkov
run: [checkov, -f, "{plan_json}", --framework, terraform_plan, --compact, --quiet]Modes
| Mode | Findings shown | Apply blocked |
|---|---|---|
off |
No | No |
audit |
Yes | No |
enforce |
Yes | Yes, unless someone in override uses --override |
policies at the top of repos.yaml apply to every repo. A repo entry can add its own
policies with more rules or a stricter mode, never a looser one.
Rule fields
| Field | Meaning |
|---|---|
name |
Shown with each finding |
resource |
A glob on the resource type, such as aws_s3_* |
actions |
create, update, replace or delete |
projects |
Globs on the project |
deny_if |
A finding when the condition is true |
require |
A finding when the condition is false |
| neither | The change itself is a finding |
message |
Your own text for the finding |
severity |
low, medium, high (default) or critical. Feeds the risk score. |
Conditions check an attribute path with one of:
| Operator | Example |
|---|---|
equals |
{ attribute: storage_encrypted, equals: true } |
in |
{ attribute: acl, in: [public-read, public-read-write] } |
matches (a regex) |
{ attribute: instance_type, matches: "^t3\\." } |
exists |
{ attribute: tags.owner, exists: true } |
Values only known after apply are skipped.
Scanner commands
Commands run any scanner against the plan; {plan_json} is replaced with the plan file in JSON,
and a non-zero exit is a finding. The scanner must be installed in the server's image.
commands:
- name: checkov
run: [checkov, -f, "{plan_json}", --framework, terraform_plan, --compact, --quiet]
- name: conftest
run: [conftest, test, "{plan_json}", -p, /policies]More rule examples
rules:
- name: tags-required
resource: "aws_*"
actions: [create]
require: { attribute: tags.owner, exists: true }
message: Every new AWS resource needs an owner tag
- name: small-instances-in-dev
projects: ["dev/**"]
resource: aws_instance
deny_if: { attribute: instance_type, matches: "^(m|c|r)[0-9]+\\.(4|8|12|16|24)xlarge$" }
- name: no-iam-changes
resource: "aws_iam_*"
message: IAM changes need the security teamBuilt-in AWS security rules
New in 0.2.0.
builtin: [security] turns on twelve ready-made rules. They report like any other rule, so mode,
--override and the findings tab all apply. Turn single rules off with disable.
| Rule | Severity | Flags |
|---|---|---|
aws-s3-public-acl |
high | bucket ACLs public-read, public-read-write, authenticated-read |
aws-s3-public-access-block-off |
high | a public access block with any setting turned off |
aws-s3-public-policy |
critical | a bucket policy allowing "Principal": "*" without a condition |
aws-security-group-open-ingress |
high | ingress from 0.0.0.0/0 or ::/0 on anything but ports 80 and 443 |
aws-storage-unencrypted |
high | RDS, Aurora, DocumentDB, Neptune, EBS, EFS, ElastiCache or Redshift without encryption |
aws-iam-wildcard |
critical | an IAM policy allowing "Action": "*" on "Resource": "*" |
aws-database-public |
high | a publicly accessible database |
aws-deletion-protection-off |
high | deletion protection turned off on a database, table or load balancer |
aws-kms-key-deleted |
critical | a KMS key destroyed or replaced |
aws-kms-rotation-off |
medium | a symmetric KMS key without rotation |
aws-audit-or-backup-removed |
critical | CloudTrail, AWS Config, GuardDuty or a backup vault or plan deleted, or CloudTrail logging off |
aws-eks-public-endpoint |
medium | an EKS API endpoint open to the whole internet |
Where findings show up
- the PR comment;
- the project's check run;
- the dashboard's Audit → Policy findings tab, searchable by rule.
Two-person rule
two_person_rule: true on a repo, or on a project rule in repos.yaml, means the PR's author can't
apply it; someone else with apply rights must.
repos:
- id: github.com/acme/infra
two_person_rule: true # every project in the repo
- id: github.com/acme/platform
projects:
- match: prod/**
two_person_rule: true # only prodTamper-proof output
GitHub lets anyone with write access edit any comment, including teriac's. teriac never reads its comments back: what gets applied comes from the plan saved on the server, and check runs (which users can't edit) and the dashboard are the trusted record.
If someone edits or deletes a teriac comment, teriac posts a warning naming who did it, with a link to the comment and a "View the original" section, and logs it in the audit log.
Other safety rails
- Saved plans only. Apply runs the reviewed plan file, never a fresh plan.
- Stale plans refused. A newer commit, or a dependency applied since, means "re-plan first".
- No passthrough. PR commands are parsed and checked; extra Terraform arguments are off unless a
repo sets
allow_extra_args: true. - Destroy needs more. Plans that delete or replace need
apply_destroy(maintain by default). - Secrets stay out of logs. Known secret values are replaced with
***in every log teriac keeps or posts.
More guardrails (risk scores, change freezes, protected resources, a leaked-secret scan) are in Risk and guardrails. More in the security model.