Policy as Code in ThothCTL¶
Overview¶
ThothCTL integrates policy-as-code at multiple layers of the IaC lifecycle. Policies define what is allowed, enforced, or blocked — from project structure to security posture to drift response to AI decision-making.
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3b82f6',
'primaryTextColor':'#ffffff',
'primaryBorderColor':'#2563eb',
'lineColor':'#94a3b8',
'secondaryColor':'#10b981',
'tertiaryColor':'#8b5cf6',
'background':'transparent',
'mainBkg':'#3b82f6',
'secondBkg':'#10b981',
'tertiaryBkg':'#8b5cf6',
'clusterBkg':'rgba(241, 245, 249, 0.05)',
'clusterBorder':'#475569',
'titleColor':'currentColor',
'edgeLabelBackground':'transparent',
'nodeTextColor':'#ffffff',
'textColor':'currentColor',
'nodeBorder':'#1e293b',
'fontSize':'14px'
}}}%%
graph TB
subgraph Structure["📐 Structure Policy"]
S1[".thothcf.toml<br/>[project_structure]"]
S2["check project iac"]
end
subgraph Security["🔒 Security Policy"]
P1["policy/*.rego<br/>(OPA / Conftest)"]
P2["scan iac --tools opa"]
end
subgraph Operations["⚙️ Operations Policy"]
O1[".driftpolicy<br/>ai_decision_config.yaml"]
O2["check iac --type drift<br/>ai-review decide"]
end
S1 --> S2
P1 --> P2
O1 --> O2
S2 --> Gate{"Policy Gate"}
P2 --> Gate
O2 --> Gate
Gate -->|All pass| Deploy["✅ Deploy"]
Gate -->|Violation| Block["❌ Block"]
classDef structStyle fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
classDef secStyle fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff
classDef opsStyle fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
classDef gateStyle fill:#f59e0b,stroke:#fbbf24,stroke-width:2px,color:#fff
classDef resultStyle fill:#15803d,stroke:#4ade80,stroke-width:2px,color:#ffffff
classDef blockStyle fill:#dc2626,stroke:#f87171,stroke-width:2px,color:#ffffff
class S1,S2 structStyle
class P1,P2 secStyle
class O1,O2 opsStyle
class Gate gateStyle
class Deploy resultStyle
class Block blockStyle
Policy Types¶
1. Project Structure Policy¶
File: .thothcf.toml → [project_structure]
Evaluated by: thothctl check project iac
Purpose: Enforce that projects follow organizational folder/file conventions.
[project_structure]
root_files = [".gitignore", "README.md", ".thothcf.toml"]
ignore_folders = [".git", ".terraform", ".terragrunt-cache"]
[[project_structure.folders]]
name = "modules"
mandatory = true
type = "root"
content = ["main.tf", "variables.tf", "outputs.tf"]
[[project_structure.folders]]
name = "environments"
mandatory = true
type = "root"
Enforcement: Hard failure if mandatory folders/files are missing.
thothctl check project iac
# ✅ modules/ exists with required files
# ❌ FAIL: environments/ is missing (mandatory)
2. Security Policy (OPA/Rego)¶
File: policy/*.rego
Evaluated by: thothctl scan iac --tools opa
Purpose: Evaluate IaC code against custom security, naming, and compliance rules using the OPA policy language.
Modes:
- conftest (default): Static analysis of .tf / .yaml files
- opa: Plan-based evaluation against tfplan.json
# policy/s3.rego
package main
deny[msg] {
resource := input.resource.aws_s3_bucket[name]
not resource.server_side_encryption_configuration
msg := sprintf("S3 bucket '%s' must have encryption enabled", [name])
}
deny[msg] {
resource := input.resource.aws_s3_bucket[name]
resource.acl == "public-read"
msg := sprintf("S3 bucket '%s' must not be public", [name])
}
Enforcement:
# Static HCL analysis (conftest mode)
thothctl scan iac --tools opa
# Plan-based evaluation
thothctl scan iac --tools opa --opa-mode opa --policy-dir ./policy
Options:
| Option | Description |
|---|---|
--opa-mode conftest |
Static file analysis (default) |
--opa-mode opa |
Plan-based evaluation via opa exec |
--policy-dir PATH |
Directory containing .rego files (default: policy/) |
--opa-namespace |
Rego namespace (conftest mode) |
--opa-data-dir |
Additional data directory for policies |
3. Drift Response Policy¶
File: .driftpolicy (YAML)
Evaluated by: thothctl check iac --type drift
Purpose: Define per-resource tolerance for infrastructure drift — block, alert, accept, or ignore.
# .driftpolicy
coverage_threshold: 90.0
rules:
- resource: "aws_security_group.*"
severity_override: critical
action: block_deploy
- resource: "aws_instance.*"
attribute: "tags.*"
action: auto_accept
- resource: "aws_db_instance.*"
action: alert
- resource: "aws_cloudwatch_log_group.*"
action: ignore
Actions:
| Action | Behavior |
|---|---|
block_deploy |
Fail CI, prevent deployment until drift is resolved |
alert |
Warn but allow deployment |
auto_accept |
Silently accept the drift (e.g., tag-only changes) |
ignore |
Remove from report entirely |
Enforcement:
thothctl check iac --type drift --recursive
# Drift in aws_security_group.api → ACTION: block_deploy → ❌ CI fails
# Drift in aws_instance.web tags → ACTION: auto_accept → ✅ Ignored
4. AI Decision Policy¶
File: .thothctl/ai_decision_config.yaml
Evaluated by: thothctl ai-review decide
Purpose: Define thresholds for automated PR approve/reject/request-changes decisions.
# .thothctl/ai_decision_config.yaml
approve_thresholds:
risk_score_max: 20
confidence_min: 0.90
critical_issues_max: 0
high_issues_max: 0
compliance_violations_max: 0
reject_thresholds:
risk_score_min: 85
confidence_min: 0.85
critical_issues_min: 1
safety:
max_auto_approvals_per_day: 50
max_auto_rejections_per_day: 20
cooldown_between_actions: 300 # seconds
emergency_labels: ["emergency", "hotfix", "security-patch"]
trusted_bots: ["dependabot", "renovate"]
blocking_patterns:
- hardcoded_secrets
- public_s3_buckets
- unencrypted_databases
- overly_permissive_iam
Enforcement:
thothctl ai-review decide --pr-number 42 --dry-run
# Risk score: 15 → below approve_thresholds.risk_score_max (20)
# Confidence: 0.95 → above confidence_min (0.90)
# Decision: APPROVE ✅
Policy Hierarchy¶
Policies are resolved in this order (most specific wins):
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3b82f6',
'primaryTextColor':'#ffffff',
'primaryBorderColor':'#2563eb',
'lineColor':'#94a3b8',
'secondaryColor':'#10b981',
'tertiaryColor':'#8b5cf6',
'background':'transparent',
'mainBkg':'#3b82f6',
'secondBkg':'#10b981',
'tertiaryBkg':'#8b5cf6',
'clusterBkg':'rgba(241, 245, 249, 0.05)',
'clusterBorder':'#475569',
'titleColor':'currentColor',
'edgeLabelBackground':'transparent',
'nodeTextColor':'#ffffff',
'textColor':'currentColor',
'nodeBorder':'#1e293b',
'fontSize':'14px'
}}}%%
graph TB
Org["🏢 Organization<br/>Git Policy Repository"] --> Space["🗂️ Space<br/>.thothcf_project.toml"]
Space --> Project["📁 Project<br/>.thothcf.toml"]
Org -->|"Inherited by all"| Space
Space -->|"Override per team"| Project
classDef orgStyle fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff
classDef spaceStyle fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
classDef projStyle fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
class Org orgStyle
class Space spaceStyle
class Project projStyle
| Level | Source | Scope |
|---|---|---|
| Organization | Git repository (shared across all teams) | Global governance baseline |
| Space | .thothcf_project.toml in space root |
Team/domain-specific overrides |
| Project | .thothcf.toml in project root |
Project-specific exceptions |
Resolution rule: Project-level policies override Space-level, which override Organization-level. Structure policies are replaced entirely (no merge). Security policies (Rego) are additive (all levels evaluated).
Organization Policy Repository¶
Organization-level policies live in a dedicated Git repository that acts as the single source of truth for governance across all teams, domains, and workloads.
Repository Structure¶
org-iac-policies/
├── README.md
├── .thothcf.toml # Org-level defaults
│
├── domains/ # Policies per business domain
│ ├── fintech/
│ │ ├── policy/ # Rego policies for fintech
│ │ │ ├── encryption.rego # PCI-DSS encryption requirements
│ │ │ ├── network.rego # No public subnets
│ │ │ └── data.rego # Data residency rules
│ │ ├── .thothcf.toml # Structure rules for fintech projects
│ │ └── .driftpolicy # Strict drift tolerance
│ │
│ ├── platform/
│ │ ├── policy/
│ │ │ ├── naming.rego # Platform naming conventions
│ │ │ └── modules.rego # Approved modules only
│ │ └── .thothcf.toml
│ │
│ └── data-engineering/
│ ├── policy/
│ │ ├── storage.rego # S3/Glue/Redshift rules
│ │ └── compute.rego # EMR/Spark guardrails
│ └── .thothcf.toml
│
├── workloads/ # Policies per workload type
│ ├── containers/
│ │ ├── policy/
│ │ │ ├── ecs.rego # ECS task hardening
│ │ │ └── eks.rego # EKS cluster policies
│ │ └── .thothcf.toml
│ │
│ ├── serverless/
│ │ ├── policy/
│ │ │ ├── lambda.rego # Lambda function constraints
│ │ │ └── api_gateway.rego # API Gateway policies
│ │ └── .thothcf.toml
│ │
│ └── databases/
│ ├── policy/
│ │ ├── rds.rego # Multi-AZ, encryption, backup
│ │ └── dynamodb.rego # Capacity and encryption
│ └── .thothcf.toml
│
├── layers/ # Policies per infrastructure layer
│ ├── networking/
│ │ ├── policy/
│ │ │ ├── vpc.rego # VPC CIDR, flow logs
│ │ │ ├── security_groups.rego # No 0.0.0.0/0 ingress
│ │ │ └── dns.rego # Route53 conventions
│ │ └── .thothcf.toml
│ │
│ ├── security/
│ │ ├── policy/
│ │ │ ├── iam.rego # Least privilege, no wildcard
│ │ │ ├── kms.rego # Key rotation, deletion protection
│ │ │ └── secrets.rego # Secrets Manager policies
│ │ └── .thothcf.toml
│ │
│ └── observability/
│ ├── policy/
│ │ ├── cloudwatch.rego # Required alarms per service
│ │ └── logging.rego # Log retention minimums
│ └── .thothcf.toml
│
├── compliance/ # Framework-specific compliance mappings
│ ├── soc2/
│ │ ├── policy/
│ │ │ └── soc2_controls.rego # SOC2 control enforcement
│ │ └── mapping.yaml # Finding → SOC2 control mapping
│ ├── cis-aws/
│ │ └── policy/
│ │ └── cis_benchmark.rego
│ └── iso27001/
│ └── policy/
│ └── iso_controls.rego
│
└── shared/ # Shared policies (applied everywhere)
├── policy/
│ ├── tagging.rego # Required tags for all resources
│ ├── regions.rego # Allowed regions
│ └── cost_controls.rego # Instance size limits
├── .driftpolicy # Default drift tolerance
└── ai_decision_config.yaml # Default AI decision thresholds
How Projects Consume Organization Policies¶
ThothCTL resolves organization policies from a configured Git repository or local path. The policy repository can be set via:
Option 1: Space initialization
This stores governance.policy_repo in ~/.thothcf/spaces.toml.
Option 2: Environment variable
export THOTH_POLICY_REPO=/path/to/local/clone
# or
export THOTH_POLICY_REPO=https://github.com/my-org/org-iac-policies.git
Option 3: Direct path on scan command
# Point to a specific subfolder within your org repo
thothctl scan iac --tools opa --policy-dir layers/networking/policy
# Or absolute path
thothctl scan iac --tools opa --policy-dir /path/to/org-iac-policies/domains/fintech/policy
Policy Resolution Order¶
When thothctl scan iac --tools opa runs, the OPA scanner resolves policies in this order:
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3b82f6',
'primaryTextColor':'#ffffff',
'primaryBorderColor':'#2563eb',
'lineColor':'#94a3b8',
'secondaryColor':'#10b981',
'tertiaryColor':'#8b5cf6',
'background':'transparent',
'mainBkg':'#3b82f6',
'secondBkg':'#10b981',
'tertiaryBkg':'#8b5cf6',
'clusterBkg':'rgba(241, 245, 249, 0.05)',
'clusterBorder':'#475569',
'titleColor':'currentColor',
'edgeLabelBackground':'transparent',
'nodeTextColor':'#ffffff',
'textColor':'currentColor',
'nodeBorder':'#1e293b',
'fontSize':'14px'
}}}%%
graph TD
A["thothctl scan iac --tools opa<br/>--policy-dir <path>"] --> B{"Project has<br/>local policy/?"}
B -->|Yes| C["✅ Use project/policy/"]
B -->|No| D{"Is absolute<br/>path?"}
D -->|Yes| E["✅ Use absolute path"]
D -->|No| F{"THOTH_POLICY_REPO<br/>set?"}
F -->|No| G["❌ No policy found<br/>(skip scan)"]
F -->|Yes| H{"path exists in<br/>org repo?"}
H -->|Yes| I["✅ Use org_repo/<path>"]
H -->|No| J{"org_repo/shared/<br/>policy exists?"}
J -->|Yes| K["✅ Use org_repo/shared/policy"]
J -->|No| G
classDef found fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
classDef notfound fill:#991b1b,stroke:#f87171,stroke-width:2px,color:#fff
classDef decision fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
class C,E,I,K found
class G notfound
class B,D,F,H,J decision
Resolution examples:
--policy-dir value |
THOTH_POLICY_REPO |
Resolved path |
|---|---|---|
policy (default) |
not set | <project>/policy/ |
policy (default) |
/path/to/org-repo |
<org-repo>/shared/policy (fallback) |
layers/networking/policy |
/path/to/org-repo |
<org-repo>/layers/networking/policy |
domains/fintech/policy |
/path/to/org-repo |
<org-repo>/domains/fintech/policy |
workloads/databases/policy |
/path/to/org-repo |
<org-repo>/workloads/databases/policy |
/absolute/path/to/policies |
(ignored) | /absolute/path/to/policies |
Configuration in .thothcf.toml¶
Projects declare which domain, workload, and layer they belong to. This determines which org policies apply when scanning:
[thothcf]
project_id = "payment-service"
project_type = "terraform-terragrunt"
# Policy selectors — determines which org policy subfolder to use
# with: thothctl scan iac --tools opa --policy-dir domains/fintech/policy
[thothcf.governance]
domain = "fintech"
workload = "containers"
layer = "networking"
compliance = ["soc2", "cis-aws"]
Note: Automatic policy selection based on
[thothcf.governance]selectors is on the roadmap. Currently, you specify the subfolder explicitly via--policy-diror use theshared/policyfallback.
Space Configuration¶
When a space is initialized with --policy-repo, the governance config is stored in ~/.thothcf/spaces.toml:
Example: Organization Policy Repository¶
A reference implementation is available as a GitHub template:
🔗 thothforge/org-iac-policies — Example organization policy repository with pre-built policies for AWS, naming conventions, tagging, and compliance frameworks.
# Use as a template for your organization
gh repo create my-org/iac-policies --template thothforge/org-iac-policies
Integration Points¶
Where policies are evaluated in the workflow¶
Developer writes IaC
│
├──► thothctl check project iac → Structure policy
│
├──► thothctl scan iac --tools opa → Security policy (Rego)
│
├──► thothctl scan iac --tools checkov → Built-in CIS/AWS rules
│
├──► thothctl check iac --type drift → Drift policy
│
└──► thothctl ai-review decide → Decision policy
(uses scan results as input)
CI/CD Integration Example¶
# .github/workflows/iac-policy.yml
name: IaC Policy Gate
on: [pull_request]
jobs:
policy-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ThothCTL
run: pip install thothctl
# Layer 1: Structure
- name: Check project structure
run: thothctl check project iac
# Layer 2: Security (OPA + Checkov)
- name: Security scan with policies
run: |
thothctl scan iac --tools checkov opa --recursive
# Layer 3: Drift (if state available)
- name: Drift detection
run: thothctl check iac --type drift --recursive
# Layer 4: AI decision
- name: AI review decision
run: |
thothctl ai-review decide \
--pr-number ${{ github.event.pull_request.number }} \
--repository ${{ github.repository }}
Writing Custom Policies¶
OPA/Rego Quick Start¶
- Create a
policy/directory in your project root:
- Write a Rego policy:
# policy/naming.rego
package main
# Enforce resource naming convention
deny[msg] {
resource := input.resource[type][name]
not regex.match(`^(dev|stg|prd)-[a-z]+-[a-z0-9-]+$`, name)
msg := sprintf(
"Resource '%s.%s' violates naming convention: must be '{env}-{service}-{name}'",
[type, name]
)
}
- Run:
Policy Examples¶
| Policy | File | What It Enforces |
|---|---|---|
| Encryption required | policy/encryption.rego |
All S3/RDS/EBS must have encryption |
| No public access | policy/network.rego |
Security groups can't allow 0.0.0.0/0 ingress |
| Tag compliance | policy/tags.rego |
Required tags: Environment, Owner, CostCenter |
| Module versioning | policy/modules.rego |
All modules must pin exact versions |
| Region restriction | policy/regions.rego |
Only approved regions allowed |
Relationship to FdI (Framework-defined Infrastructure)¶
In the FdI model, policies become the framework rules that govern code generation:
Today: Developer writes IaC → policies validate after the fact
Future: Policies constrain generation → code is compliant by construction
The intent-to-IaC generation engine (roadmap) will read these same policy files to produce code that already passes all checks:
| Policy Layer | FdI Role |
|---|---|
Structure ([project_structure]) |
Generated code follows the required structure |
Security (policy/*.rego) |
Generated code uses only approved patterns |
Drift (.driftpolicy) |
Generated code accounts for drift tolerance |
AI decisions (ai_decision_config) |
Auto-approve low-risk generated changes |