Skip to content

Infrastructure as Code with Terraform ​

How to manage MeshGuard agents, policies, and alert channels as code using the Terraform provider, with complete HCL examples, state management, and team workflows.

Why IaC for Agent Governance ​

Click-ops governance doesn't scale. When you have 3 agents and 2 policies, the admin dashboard works fine. When you have 30 agents across 5 teams, each with environment-specific policies and alert channels, you need the same discipline you apply to your infrastructure: version control, code review, automated testing, and repeatable deployments.

The MeshGuard Terraform provider gives you this by managing three core resource types as code:

  1. Agents — Register and configure agents with trust tiers, tags, and metadata
  2. Policies — Define YAML policy documents that control what each agent can do
  3. Alert channels — Configure where governance events are routed (Slack, PagerDuty, etc.)

This means:

  • Every change is reviewed. A new policy or trust tier change goes through a pull request. Your team sees the diff, reviews the impact, and approves before terraform apply.
  • Every change is versioned. Git history tells you exactly who changed what and when. If a policy change causes unexpected denials, git log shows you what changed and terraform plan shows you how to revert.
  • Every environment is reproducible. Staging mirrors production because they're defined by the same Terraform modules with different variable files.
  • Drift is detectable. terraform plan shows you when the live state doesn't match your code — someone made a change through the API that wasn't committed.

Provider Setup ​

Requirements ​

  • Terraform >= 1.0
  • A running MeshGuard gateway with admin API access
  • An admin API token

Installation ​

The provider is available from the Terraform Registry:

hcl
terraform {
  required_providers {
    meshguard = {
      source  = "registry.terraform.io/meshguard/meshguard"
      version = "~> 0.1"
    }
  }
}

For local development, you can build the provider from source:

bash
git clone https://github.com/meshguard/meshguard-terraform.git
cd meshguard-terraform
go build -o terraform-provider-meshguard

# Install locally
mkdir -p ~/.terraform.d/plugins/registry.terraform.io/meshguard/meshguard/0.1.0/$(go env GOOS)_$(go env GOARCH)
cp terraform-provider-meshguard ~/.terraform.d/plugins/registry.terraform.io/meshguard/meshguard/0.1.0/$(go env GOOS)_$(go env GOARCH)/

Provider Configuration ​

hcl
provider "meshguard" {
  gateway_url = "https://gw.meshguard.app"
  admin_token = var.meshguard_admin_token
}

variable "meshguard_admin_token" {
  description = "MeshGuard admin API token"
  type        = string
  sensitive   = true
}

Both arguments can be set via environment variables instead:

ArgumentEnvironment VariableDescription
gateway_urlMESHGUARD_GATEWAY_URLMeshGuard gateway URL
admin_tokenMESHGUARD_ADMIN_TOKENAdmin API token

Using environment variables keeps secrets out of your Terraform files:

bash
export MESHGUARD_GATEWAY_URL="https://gw.meshguard.app"
export MESHGUARD_ADMIN_TOKEN="msat_your-production-token"
terraform plan

Managing Agents ​

The meshguard_agent resource registers an agent with the MeshGuard gateway, assigning it an identity, trust tier, and metadata.

Basic Agent ​

hcl
resource "meshguard_agent" "code_reviewer" {
  name       = "code-reviewer"
  trust_tier = "verified"
  tags       = ["ci", "code-review"]
  metadata = {
    team        = "platform"
    environment = "production"
  }
}

Trust Tiers ​

Every agent has a trust tier that determines its baseline permissions. The tiers, from least to most privileged:

TierUse Case
unverifiedNew or untrusted agents, most restrictive
verifiedIdentity confirmed, moderate access
trustedEstablished agents with track record
privilegedAdministrative agents with broad access

Accessing Agent Outputs ​

When Terraform creates an agent, the gateway returns an API key. Use outputs to capture it:

hcl
output "code_reviewer_id" {
  description = "ID of the code-reviewer agent"
  value       = meshguard_agent.code_reviewer.id
}

output "code_reviewer_api_key" {
  description = "API key for the code-reviewer agent"
  value       = meshguard_agent.code_reviewer.api_key
  sensitive   = true
}

Multiple Agents with for_each ​

When you have many agents with similar configuration, use for_each:

hcl
variable "agents" {
  description = "Map of agent configurations"
  type = map(object({
    trust_tier = string
    tags       = list(string)
    team       = string
  }))
  default = {
    code-reviewer = {
      trust_tier = "verified"
      tags       = ["ci", "code-review"]
      team       = "platform"
    }
    doc-summarizer = {
      trust_tier = "trusted"
      tags       = ["docs"]
      team       = "content"
    }
    deployment-bot = {
      trust_tier = "privileged"
      tags       = ["ci", "deploy"]
      team       = "platform"
    }
  }
}

resource "meshguard_agent" "agents" {
  for_each   = var.agents
  name       = each.key
  trust_tier = each.value.trust_tier
  tags       = each.value.tags
  metadata = {
    team        = each.value.team
    environment = var.environment
  }
}

Managing Policies ​

The meshguard_policy resource manages YAML policy documents. You can define policies inline or load them from files.

Inline Policy ​

hcl
resource "meshguard_policy" "code_review_policy" {
  name = "code-review-policy"
  content = yamlencode({
    name          = "code-review-policy"
    version       = "1.0"
    description   = "Policy for code review agents"
    appliesTo     = { tags = ["code-review"] }
    defaultEffect = "deny"
    rules = [
      {
        name   = "allow-read-repos"
        effect = "allow"
        conditions = {
          action = ["read"]
          path   = ["/api/repos/*"]
        }
      },
      {
        name   = "allow-post-comments"
        effect = "allow"
        conditions = {
          action = ["write"]
          path   = ["/api/repos/*/comments"]
        }
      },
    ]
  })
}

File-Based Policy ​

For complex policies, keep the YAML in a separate file and reference it:

hcl
resource "meshguard_policy" "production_guardrails" {
  name    = "production-guardrails"
  content = file("policies/production-guardrails.yaml")
}
yaml
# policies/production-guardrails.yaml
name: production-guardrails
version: "1.0"
description: Restrict dangerous actions for unverified agents

appliesTo:
  trustTiers:
    - unverified
    - verified

rules:
  - name: deny-destructive
    effect: deny
    actions:
      - "tool:exec"
      - "admin:*"
      - "write:delete"
  - name: allow-reads
    effect: allow
    actions:
      - "read:*"

defaultEffect: deny

Policy per Agent ​

Combine for_each with templatefile for agent-specific policies:

hcl
resource "meshguard_policy" "agent_policies" {
  for_each = var.agents
  name     = "${each.key}-policy"
  content  = templatefile("policies/agent-policy.yaml.tpl", {
    agent_name = each.key
    trust_tier = each.value.trust_tier
    tags       = each.value.tags
  })
}
yaml
# policies/agent-policy.yaml.tpl
name: ${agent_name}-policy
version: "1.0"
description: Auto-generated policy for ${agent_name}

appliesTo:
  tags:
%{ for tag in tags ~}
    - ${tag}
%{ endfor ~}

rules:
  - name: allow-reads
    effect: allow
    actions:
      - "read:*"
%{ if trust_tier == "privileged" ~}
  - name: allow-writes
    effect: allow
    actions:
      - "write:*"
%{ endif ~}

defaultEffect: deny

Managing Alert Channels ​

The meshguard_alert_channel resource configures where governance events are sent.

Slack ​

hcl
resource "meshguard_alert_channel" "slack_alerts" {
  type          = "slack"
  endpoint      = "https://hooks.slack.com/services/T00/B00/xxx"
  slack_channel = "#meshguard-alerts"
  severity      = "warning"
  triggers      = ["deny", "error"]
}

PagerDuty ​

hcl
resource "meshguard_alert_channel" "pagerduty_critical" {
  type                  = "pagerduty"
  endpoint              = "https://events.pagerduty.com/v2/enqueue"
  pagerduty_routing_key = var.pagerduty_key
  severity              = "critical"
  triggers              = ["deny"]
}

variable "pagerduty_key" {
  description = "PagerDuty routing key"
  type        = string
  sensitive   = true
}

Severity-Based Routing ​

Route different severity levels to different channels:

hcl
# Warnings go to Slack
resource "meshguard_alert_channel" "slack_warnings" {
  type          = "slack"
  endpoint      = var.slack_webhook_url
  slack_channel = "#meshguard-warnings"
  severity      = "warning"
  triggers      = ["deny"]
}

# Critical events go to PagerDuty
resource "meshguard_alert_channel" "pagerduty_critical" {
  type                  = "pagerduty"
  endpoint              = "https://events.pagerduty.com/v2/enqueue"
  pagerduty_routing_key = var.pagerduty_key
  severity              = "critical"
  triggers              = ["deny", "error"]
}

# Everything goes to the audit Slack channel for the record
resource "meshguard_alert_channel" "slack_audit" {
  type          = "slack"
  endpoint      = var.slack_webhook_url
  slack_channel = "#meshguard-audit"
  severity      = "info"
  triggers      = ["deny", "allow", "error"]
}

Data Sources ​

Use data sources to read existing resources from the gateway.

List All Agents ​

hcl
data "meshguard_agents" "all" {}

output "total_agents" {
  value = data.meshguard_agents.all.count
}

Filter by Trust Tier ​

hcl
data "meshguard_agents" "trusted_only" {
  trust_tier = "trusted"
}

List All Policies ​

hcl
data "meshguard_policies" "all" {}

output "policy_count" {
  value = length(data.meshguard_policies.all.policies)
}

Complete Example ​

Here's a full Terraform configuration for a team running three agents with environment-specific policies and alerting:

hcl
terraform {
  required_providers {
    meshguard = {
      source  = "registry.terraform.io/meshguard/meshguard"
      version = "~> 0.1"
    }
  }

  backend "s3" {
    bucket = "your-org-terraform-state"
    key    = "meshguard/production.tfstate"
    region = "us-east-1"
  }
}

# --- Provider ---

provider "meshguard" {
  gateway_url = var.gateway_url
  admin_token = var.admin_token
}

# --- Variables ---

variable "gateway_url" {
  description = "MeshGuard gateway URL"
  type        = string
}

variable "admin_token" {
  description = "MeshGuard admin token"
  type        = string
  sensitive   = true
}

variable "environment" {
  description = "Deployment environment"
  type        = string
  default     = "production"
}

variable "slack_webhook_url" {
  description = "Slack webhook URL for alerts"
  type        = string
  sensitive   = true
}

variable "pagerduty_key" {
  description = "PagerDuty routing key"
  type        = string
  sensitive   = true
  default     = ""
}

# --- Agents ---

resource "meshguard_agent" "code_reviewer" {
  name       = "code-reviewer"
  trust_tier = "verified"
  tags       = ["ci", "code-review"]
  metadata = {
    team        = "platform"
    environment = var.environment
  }
}

resource "meshguard_agent" "summarizer" {
  name       = "doc-summarizer"
  trust_tier = "trusted"
  tags       = ["docs"]
  metadata = {
    team        = "content"
    environment = var.environment
  }
}

resource "meshguard_agent" "deployment_bot" {
  name       = "deployment-bot"
  trust_tier = "privileged"
  tags       = ["ci", "deploy"]
  metadata = {
    team        = "platform"
    environment = var.environment
  }
}

# --- Policies ---

resource "meshguard_policy" "code_review" {
  name    = "code-review-policy"
  content = file("policies/code-review.yaml")
}

resource "meshguard_policy" "summarizer" {
  name    = "summarizer-policy"
  content = file("policies/summarizer.yaml")
}

resource "meshguard_policy" "deployment" {
  name    = "deployment-policy"
  content = file("policies/deployment.yaml")
}

resource "meshguard_policy" "global_guardrails" {
  name    = "global-guardrails"
  content = file("policies/global-guardrails.yaml")
}

# --- Alert Channels ---

resource "meshguard_alert_channel" "slack" {
  type          = "slack"
  endpoint      = var.slack_webhook_url
  slack_channel = "#meshguard-${var.environment}"
  severity      = "warning"
  triggers      = ["deny", "error"]
}

resource "meshguard_alert_channel" "pagerduty" {
  count                 = var.pagerduty_key != "" ? 1 : 0
  type                  = "pagerduty"
  endpoint              = "https://events.pagerduty.com/v2/enqueue"
  pagerduty_routing_key = var.pagerduty_key
  severity              = "critical"
  triggers              = ["deny"]
}

# --- Outputs ---

output "agent_ids" {
  description = "Map of agent names to IDs"
  value = {
    code_reviewer  = meshguard_agent.code_reviewer.id
    summarizer     = meshguard_agent.summarizer.id
    deployment_bot = meshguard_agent.deployment_bot.id
  }
}

output "agent_api_keys" {
  description = "Map of agent names to API keys"
  sensitive   = true
  value = {
    code_reviewer  = meshguard_agent.code_reviewer.api_key
    summarizer     = meshguard_agent.summarizer.api_key
    deployment_bot = meshguard_agent.deployment_bot.api_key
  }
}

State Management ​

Terraform state contains sensitive data — agent API keys, admin tokens referenced in resources, and the full configuration of your governance layer. Treat it accordingly.

Remote State ​

Always use a remote backend with encryption:

hcl
terraform {
  backend "s3" {
    bucket         = "your-org-terraform-state"
    key            = "meshguard/production.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

State Locking ​

Enable state locking to prevent concurrent modifications. With S3, use a DynamoDB table:

hcl
resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

Workspaces for Environments ​

Use Terraform workspaces to manage multiple environments with the same code:

bash
terraform workspace new staging
terraform workspace new production

# Deploy to staging
terraform workspace select staging
terraform apply -var-file="environments/staging.tfvars"

# Deploy to production
terraform workspace select production
terraform apply -var-file="environments/production.tfvars"
hcl
# environments/staging.tfvars
gateway_url       = "https://gw-staging.meshguard.app"
environment       = "staging"
slack_webhook_url = "https://hooks.slack.com/services/staging/..."
hcl
# environments/production.tfvars
gateway_url       = "https://gw.meshguard.app"
environment       = "production"
slack_webhook_url = "https://hooks.slack.com/services/production/..."
pagerduty_key     = "your-production-routing-key"

Team Workflows ​

Pull Request Flow ​

  1. Developer creates a branch and modifies a policy file or agent configuration
  2. CI runs terraform plan and posts the output as a PR comment (see CI/CD Policy Checks with GitHub Actions)
  3. The team reviews the plan diff — they can see exactly which agents, policies, or alerts will change
  4. After approval, merge triggers terraform apply

Policy Review Process ​

Policy changes are the most sensitive Terraform changes. Add a CODEOWNERS file to require security team review:

# .github/CODEOWNERS
policies/                @your-org/security-team
*.tf                     @your-org/platform-team
environments/production* @your-org/security-team @your-org/platform-team

Import Existing Resources ​

If you already have agents and policies created through the admin API, import them into Terraform state:

bash
# Import an existing agent
terraform import meshguard_agent.code_reviewer agent_abc123

# Import an existing policy
terraform import meshguard_policy.production_guardrails policy_def456

After import, run terraform plan to verify the code matches the live state. Adjust your HCL until the plan shows no changes.

Common Patterns ​

Environment Promotion ​

Define policies once, promote across environments:

infra/
  modules/
    meshguard/
      main.tf        # Agent + policy + alert resources
      variables.tf   # Parameterized by environment
      outputs.tf
  environments/
    staging/
      main.tf        # module "meshguard" with staging vars
    production/
      main.tf        # module "meshguard" with production vars
hcl
# infra/environments/production/main.tf
module "meshguard" {
  source = "../../modules/meshguard"

  environment       = "production"
  gateway_url       = "https://gw.meshguard.app"
  admin_token       = var.admin_token
  slack_webhook_url = var.slack_webhook_url
  pagerduty_key     = var.pagerduty_key
}

Drift Detection ​

Run terraform plan on a schedule to detect configuration drift:

yaml
# .github/workflows/drift-detection.yml
name: Terraform Drift Detection
on:
  schedule:
    - cron: "0 */6 * * *"    # Every 6 hours

jobs:
  drift-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3

      - name: Terraform Plan
        run: terraform plan -detailed-exitcode -no-color
        env:
          MESHGUARD_GATEWAY_URL: ${{ secrets.MESHGUARD_GATEWAY_URL }}
          MESHGUARD_ADMIN_TOKEN: ${{ secrets.MESHGUARD_ADMIN_TOKEN }}

      - name: Alert on drift
        if: failure()
        run: |
          curl -X POST "$SLACK_WEBHOOK" \
            -H "Content-Type: application/json" \
            -d '{"text":"Terraform drift detected in MeshGuard configuration"}'

Quick-Start Checklist ​

  • [ ] Install the provider — Add the meshguard provider to your required_providers block
  • [ ] Configure authentication — Set MESHGUARD_GATEWAY_URL and MESHGUARD_ADMIN_TOKEN as environment variables
  • [ ] Import existing resources — If you have agents or policies already, import them into state
  • [ ] Define agents as code — Create meshguard_agent resources for each agent in your mesh
  • [ ] Define policies as code — Store policy YAML in version-controlled files, reference them with file()
  • [ ] Configure alert channels — Route governance events to Slack, PagerDuty, or your preferred platform
  • [ ] Set up remote state — Use an encrypted remote backend with state locking
  • [ ] Add CI/CD — Run terraform plan on PRs and terraform apply on merge
  • [ ] Add CODEOWNERS — Require security team review for policy changes
  • [ ] Schedule drift detection — Run terraform plan periodically to catch out-of-band changes

Where This Connects ​

Built for the agentic era.