Why IaC for auth
Auth config drifts in ways application code doesn’t. An agent spun up by hand in a dashboard has no peer review, no version history, and no automated rollback. When the production incident happens and you need to know why an agent had write access to the deploy tool, “someone added it last Tuesday” is not an audit trail.
Treating agents and permissions as Terraform resources fixes this. Every grant goes through a pull request. Every change is versioned in git. Destroying a staging environment tears down the access alongside the infrastructure, no orphaned tokens floating around.
Installation
The provider requires Go 1.21 to build.
Clone the repository and build the binary:git clone https://github.com/kavachos/terraform-provider-kavachos
cd terraform-provider-kavachos/sdks/terraform
go build -o terraform-provider-kavachos .
Install into the local plugin cache:OS=$(go env GOOS)
ARCH=$(go env GOARCH)
PLUGIN_DIR=~/.terraform.d/plugins/registry.terraform.io/kavachos/kavachos/0.1.0/${OS}_${ARCH}
mkdir -p "$PLUGIN_DIR"
mv terraform-provider-kavachos "$PLUGIN_DIR/"
Declare the provider in your Terraform configuration:terraform {
required_version = ">= 1.5"
required_providers {
kavachos = {
source = "kavachos/kavachos"
version = "~> 0.1"
}
}
}
Provider setup
provider "kavachos" {
base_url = "https://your-app.com/api/kavach"
token = var.kavachos_token
}
Both arguments can be set via environment variables, which is preferred in CI:
export KAVACHOS_BASE_URL=https://your-app.com/api/kavach
export KAVACHOS_TOKEN=kv_live_...
base_url
string
default:"KAVACHOS_BASE_URL env var"
Base URL of your KavachOS deployment.
token
string
default:"KAVACHOS_TOKEN env var"
API token for authenticating with KavachOS.
Resources
kavachos_agent
Manages an agent identity, the primary entity in KavachOS.
resource "kavachos_agent" "github_reader" {
owner_id = "user-123"
name = "github-reader"
type = "autonomous"
permission {
resource = "mcp:github:*"
actions = ["read"]
}
permission {
resource = "mcp:deploy:production"
actions = ["execute"]
constraints {
require_approval = true
max_calls_per_hour = 10
ip_allowlist = ["10.0.0.0/8"]
}
}
expires_at = "2026-12-31T23:59:59Z"
}
The token attribute is computed on creation and marked sensitive. Read it with:
terraform output -raw github_reader_token
The raw token is only returned once, at creation time. KavachOS does not store it in recoverable form. If you lose it, rotate the agent’s token via the API or dashboard.
ID of the user who owns this agent.
type
"autonomous" | "delegated" | "service"
required
Agent type: autonomous, delegated, or service.
One or more permission grant blocks.
RFC 3339 expiry timestamp.
Permission block arguments
Resource pattern, e.g. mcp:github:* or mcp:deploy:production.
Allowed actions, e.g. [“read”] or [“execute”].
Optional block limiting usage.
Constraints block arguments
Require human approval before the action executes.
Rate limit: maximum calls allowed per hour.
Glob patterns restricting allowed argument values.
CIDR blocks from which this permission may be used.
kavachos_permission
Grants a single permission to an existing agent from a separate module. If you control both the agent and all its permissions in one place, use inline permission blocks on kavachos_agent instead.
resource "kavachos_permission" "staging_deploy" {
agent_id = kavachos_agent.deploy_bot.id
resource = "mcp:deploy:staging"
actions = ["execute"]
max_calls_per_hour = 20
}
Glob patterns for argument values.
CIDR blocks allowed to exercise the permission.
kavachos_api_key
Manages an API key for server-to-server requests.
resource "kavachos_api_key" "ci_pipeline" {
name = "ci-pipeline"
scopes = ["agents:read", "agents:write"]
}
output "ci_key" {
value = kavachos_api_key.ci_pipeline.key
sensitive = true
}
Valid scopes: agents:read, agents:write, audit:read, delegation:read, delegation:write, organizations:read, organizations:write, admin.
The key attribute is only populated immediately after creation. Read it with terraform output -raw ci_key and store it in your secret manager before the session ends.
kavachos_organization
Manages an organization for multi-tenant isolation.
resource "kavachos_organization" "engineering" {
name = "Engineering"
slug = "engineering"
plan = "pro"
}
slug is immutable after creation. Change it by deleting and recreating the organization.
Data sources
kavachos_agent
Reads an agent that was not created by this Terraform configuration.
data "kavachos_agent" "legacy" {
id = "agent-id-from-dashboard"
}
output "status" {
value = data.kavachos_agent.legacy.status
}
kavachos_agents
Lists agents, optionally filtered.
data "kavachos_agents" "active_bots" {
owner_id = "user-123"
status = "active"
type = "autonomous"
}
output "bot_count" {
value = length(data.kavachos_agents.active_bots.agents)
}
Filter arguments: owner_id, status (active | revoked | expired), type (autonomous | delegated | service).
Import existing state
Resources provisioned outside Terraform can be brought under management:
# Import an agent
terraform import kavachos_agent.my_bot agent-abc123
# Import an API key
terraform import kavachos_api_key.ci kv_key_abc123
# Import an organization
terraform import kavachos_organization.eng org-xyz789
After importing, run terraform plan. Terraform will show a diff for any attributes that differ from your configuration. Update the HCL to match, or let Terraform converge.
Importing an API key restores the ID and metadata but not the raw key value, that was only available at creation time.
GitOps workflow
Manage KavachOS configuration alongside your application infrastructure:
infra/
├── main.tf # Provider and state backend
├── organizations.tf # kavachos_organization resources
├── agents.tf # Agent definitions
├── api_keys.tf # API keys for CI and services
└── variables.tf
A minimal GitHub Actions workflow:
name: Terraform
on:
push:
branches: [main]
paths: ['infra/**']
pull_request:
paths: ['infra/**']
jobs:
terraform:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.8"
- run: terraform -chdir=infra init
- name: Plan
id: plan
env:
KAVACHOS_BASE_URL: ${{ secrets.KAVACHOS_BASE_URL }}
KAVACHOS_TOKEN: ${{ secrets.KAVACHOS_TOKEN }}
run: terraform -chdir=infra plan -out=tfplan -no-color
- name: Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
env:
KAVACHOS_BASE_URL: ${{ secrets.KAVACHOS_BASE_URL }}
KAVACHOS_TOKEN: ${{ secrets.KAVACHOS_TOKEN }}
run: terraform -chdir=infra apply tfplan
Every permission change ships as a pull request diff. The plan output shows exactly what will change before it’s applied. The apply only runs on merge to main.
This means the same controls you use for code changes, required reviewers, branch protection, signed commits, apply to auth config changes too.