Required Structure
Every Rego policy in Forgecroft must use the forgecroft.policy package:
package forgecroft.policy
Deny Rules
Deny rules block a run when they fire. They return an array of violation messages:
deny[msg] {
input.workspace.attributes.environment == "production"
input.plan_changes.destroyed[_].resource_type == "aws_db_instance"
msg := "Destroying a database instance in production is not allowed"
}
Each matching rule instance adds one message to the deny array. If any messages exist and the policy set is mandatory, the run is rejected.
Require Approval Rules
Approval rules trigger a multi-stage approval requirement:
require_approval[r] {
input.workspace.attributes.environment == "production"
count(input.plan_changes.changed) > 5
r := {
"team": "platform-leads",
"min": 2,
"stage": 1,
"reason": "Production workspace with significant changes",
"timeout_hours": 24
}
}
The returned object:
| Field | Type | Description |
|---|---|---|
team | string | Team name or ID to approve |
min | int | Minimum number of approvals needed |
stage | int | Approval stage (1 = first) |
reason | string | Why approval is required |
timeout_hours | int | Hours before escalation (0 = no timeout) |
Input Schema
Policies receive the following input:
{
"workspace": {
"id": "uuid",
"name": "production",
"attributes": { "environment": "production", "team": "platform" },
"detected_attributes": {
"providers": ["aws"],
"resource_types": ["aws_instance", "aws_security_group"]
}
},
"run": {
"id": "uuid",
"run_type": "plan",
"trigger": "push",
"triggered_by": "user@example.com"
},
"org": {
"id": "uuid"
},
"user": {
"id": "uuid",
"email": "user@example.com"
},
"plan_changes": {
"added": [
{ "address": "aws_instance.web", "resource_type": "aws_instance", "provider_name": "aws" }
],
"changed": [
{ "address": "aws_security_group.main", "resource_type": "aws_security_group" }
],
"destroyed": []
}
}
Example Policies
Block Destroy in Production
package forgecroft.policy
deny[msg] {
input.workspace.attributes.environment == "production"
count(input.plan_changes.destroyed) > 0
msg := sprintf("Production destroy: %d resource(s) would be destroyed", [count(input.plan_changes.destroyed)])
}
Require Approval for IAM Changes
package forgecroft.policy
require_approval[r] {
input.plan_changes.added[_].resource_type == "aws_iam_role"
r := {
"team": "security",
"min": 1,
"stage": 1,
"reason": "IAM role creation requires security review",
"timeout_hours": 48
}
}
Limit Changes Per Run
package forgecroft.policy
deny[msg] {
total := count(input.plan_changes.added) + count(input.plan_changes.changed) + count(input.plan_changes.destroyed)
total > 50
msg := sprintf("Too many changes: %d (max 50)", [total])
}
Require Approval for PR-Triggered Production Changes
package forgecroft.policy
require_approval[r] {
input.run.trigger == "pr"
input.workspace.attributes.environment == "production"
r := {
"team": "platform-leads",
"min": 1,
"stage": 1,
"reason": "PR-triggered production change requires approval",
"timeout_hours": 12
}
}
Block Unapproved Providers
package forgecroft.policy
approved_providers := {"aws", "google", "cloudflare"}
deny[msg] {
provider := input.workspace.detected_attributes.providers[_]
not approved_providers[provider]
msg := sprintf("Unapproved provider: %s", [provider])
}
Using Workspace Attributes
Workspace attributes are the primary mechanism for targeting policies:
deny[msg] {
input.workspace.attributes.criticality == "high"
input.plan_changes.destroyed[_].resource_type == "aws_instance"
msg := "Cannot destroy instances in high-criticality workspaces"
}
Set attributes on workspaces via PATCH /workspaces/{id} with an attributes object.
Testing Policies
Use the Rego playground endpoints to test your policies:
POST /policy-sets/validate-rego— Check syntax and structurePOST /policy-sets/evaluate-rego— Evaluate against custom input
See the Rego Playground guide for details.
Related
- Policy Sets — Grouping and scoping policies
- Rego Playground — Testing policies
- Workspace Attributes — Setting attributes for policy targeting