Decoys as Code (Terraform)
Manage your decoy estate the same way you manage the rest of your infrastructure. The Tripwire Terraform provider declares canary tokens and honeypots — and stands up a fully populated honeypot VM — in code, against the SaaS API or your self-hosted control server.
Configure the provider
Point the provider at your API and give it a scoped API key. Both fall back to
TRIPWIRE_ENDPOINT / TRIPWIRE_API_KEY.
terraform {
required_providers {
tripwire = { source = "levantar-ai/tripwire" }
}
}
provider "tripwire" {
endpoint = "https://api.gettripwires.com" # or your self-host control server
api_key = var.tripwire_api_key # a scoped key
}
The tripwire_tripwire resource
A single decoy — a bait credential, a canary document, a token — minted through the same API the console and CLI use. Tag it with the instance it defends, and pair it with a lease (TTL) so the decoy self-reaps when the infrastructure it protects is gone.
resource "tripwire_tripwire" "pg" {
name = "prod-db-primary-creds"
technology = "postgresql"
ttl = "720h"
tags = {
env = "prod"
"instance-id" = aws_instance.db.id
"managed-by" = "terraform"
}
}
output "bait_username" { value = tripwire_tripwire.pg.tcp_username }
Because it's just Terraform, use for_each to blanket a fleet, modules to stamp a standard set per environment, and plan review so a human sees every change. Remove the block and run apply — the decoy is reaped.
Turnkey honeypot VMs — tripwire_honeypot_vm
A decoy is only half a trap; it isn't a trap until it sits somewhere an attacker will look. The tripwire_honeypot_vm resource does both halves in one apply: it launches an EC2 instance from the maintained Tripwire honeypot image, creates the decoys the box should serve, and writes each decoy's live connection into a believable file on the instance — a .pgpass, a Rails database.yml, an ~/.aws/credentials profile, an SSH config entry. Destroying the resource terminates the instance and reaps its decoys.
resource "tripwire_honeypot_vm" "app_tier" {
region = "eu-west-2"
ami = "ami-0honeypot0maintained" # shared to your account by Tripwire
instance_type = "t3.small"
subnet_id = aws_subnet.private.id
security_group_ids = [aws_security_group.honeypot.id] # must open the decoy ports
tripwire {
technology = "postgresql"
name = "prod-db-primary"
place { path = "/srv/app/config/database.yml" format = "rails_db" }
place { path = "/home/deploy/.pgpass" format = "pgpass" }
}
tripwire {
technology = "ssh"
name = "bastion"
place { path = "/root/.ssh/config" format = "ssh_config" }
}
plant { type = "aws-credentials" name = "prod-deploy" } # decoy + ~/.aws/credentials
}
output "decoys" { value = tripwire_honeypot_vm.app_tier.tripwire_ids }
The image is private — shared straight to your account, never public — so the box doesn't announce itself as a honeypot the way a public AMI would. The provider needs AWS credentials in its environment (standard AWS_* / profile chain) to launch the instance.
Three ways to declare the decoys
- Explicit — inline
tripwire { place { … } }andplant { … }blocks (above). - Manifest —
manifest = file("fleet.yaml"), the same YAML shape the CLI consumes. - AI (reserved) —
ai { profile = "finance-db-server" }; not yet implemented.
place formats
Each place renders the decoy's connection into the file format that matches where it lives:
| Format | Renders |
|---|---|
| pgpass | host:port:db:user:password line for a .pgpass |
| connstring | scheme://user:pass@host:port/db URL |
| dotenv | KEY=value lines (DB URL, AWS keys, or token URL by technology) |
| rails_db | a production: stanza for config/database.yml |
| ssh_config | a Host entry for ~/.ssh/config |
| aws_credentials | a named profile block for ~/.aws/credentials |
| template | a Go template you supply, evaluated against the connection (.Host, .Port, .Username, .Password, .URL, …) |
plant types
A plant is shorthand for a decoy plus its conventional breadcrumb file:
aws-credentials→ an AWS key decoy in~/.aws/credentialsenv-file→ a token in/srv/app/.envssh-config→ a DNS-token host in~/.ssh/configbash-history→ a token referenced from~/.bash_history
One apply, one destroy
The resource owns the whole lifecycle: apply creates the decoys, renders their lures and launches the box; destroy terminates the instance and reaps every decoy it created — so a failed apply leaves nothing behind, and a destroy leaves no orphaned bait.