A bank can manage its Mirror posture as code:
- the corridors it proposes and ratifies;
- the witnesses it vouches for;
- the https endpoints it wants pushed to.
There are two ways, both shipped as source: a module (terraform/, with examples/bank-a) that needs nothing but Terraform and Node, and a compiled provider (terraform/provider, Go) that adds drift detection and import.
The module#
module "mirror" {
source = "./terraform"
bander_command = ["/opt/mirror/mirror-bander"]
signer = { custody_file = "/etc/mirror/member-signer.json" }
propose_corridors = { cu_v1 = { product = "cu", version = 1, edges = [7800, 8200, 8600, 9000], tolerance = 1 } }
webhooks = { gateway = { url = "https://mirror-hooks.bank.example/deliveries" } }
}
Each resource runs a helper (terraform/mirror-tf.js) that does only the HTTP. Before sending an event, the helper reads the live registry and skips anything this member already proposed or ratified, so re-applying changes nothing. The module does not notice a subscription removed outside Terraform; the provider does.
The provider#
terraform {
required_providers {
mirror = { source = "terraform.local/mirror/mirror" }
}
}
provider "mirror" {
bander_command = ["/opt/mirror/mirror-bander"] # signs; has no network
custody_file = "/etc/mirror/member-signer.json" # or key_file, for a trial
}
resource "mirror_webhook_subscription" "gateway" {
url = "https://mirror-hooks.bank.example/deliveries"
}
resource "mirror_governance_event" "copper" {
type = "corridor.propose"
payload = jsonencode({ product = "cu", version = 1, edges = [7800, 8200, 8600, 9000], closed = true, max_ratio = 1.2 })
}
data "mirror_registry" "live" {} # members, ratified corridors, active witnesses
mirror_webhook_subscriptionreads the operator's live list on every plan. A subscription removed outside Terraform shows up as drift and is recreated. An existing one can be adopted withterraform import mirror_webhook_subscription.gateway wh_.... Ids derive from (member, url, events), so a recreated subscription gets the same id.mirror_governance_eventsends a signed corridor, witness or member proposal or ratification. It reportsapplied = falsewhen its effect is already on the registry for this member.- Building it:
go build -o terraform-provider-mirror .interraform/provider, usingterraform-plugin-framework1.19. Then point Terraform at it with a CLI configurationdev_overridesblock forterraform.local/mirror/mirror.
How both keep their promises#
- The key never enters Terraform. The bander signs every event and every login (
sign-event,sign-login), from a key file or the HSM, and the bander has no network. Neither the configuration nor the state holds key material; both test suites check the state. - Signed answers are verified against the operator's JWKS. The helper refuses to run without the verifier, and the provider rejects a subscription receipt that does not verify.
- Governance is append-only.
terraform destroyremoves webhook subscriptions. It never un-ratifies anything, and the provider says so in a warning. Retiring a corridor or suspending a witness is its own signed event, decided by quorum.
What is proven#
Both suites were run with Terraform 1.16.4, against a running operator in production posture.
The module: server/terraform-e2e.test.js, 12 checks, drives a real terraform binary:
initandvalidateon the shipped example and on a bank module;apply, and a second plan that wants no changes;- no key material in the state;
destroy, which leaves governance on the record.
The provider: server/terraform-provider.test.js, 19 checks. It builds the provider with Go 1.27, then checks:
validateandapply;- drift: a subscription removed behind Terraform's back is recreated;
importby id, followed by a plan with no changes;- no private key in the state;
- the destroy warning.
The helper alone is also tested by server/terraform-helper.test.js: propose, ratify and an idempotent re-apply; subscribe and unsubscribe; the bander refusing an event that names another member; loud configuration errors.
What is not done#
- Not on the Terraform Registry. Publishing needs a registry namespace and a signing key for releases; that is the maintainer's decision. Until then, build it from source.
- Only
dev_overrideswas tested. A filesystem mirror for air-gapped installs is standard Terraform, but it has not been exercised here.