teriac server Policy checks and safety

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 team

Built-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 prod

Tamper-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.