Terraform
Terraform builds the machine; Gardience keeps it patched, hardened and audited from its first boot. The Gardience provider for Terraform (and OpenTofu) manages:
| Resource or data source | What it does |
|---|---|
gardience_enrollment | Opens an enrollment and gives first-boot scripts (linux_script, windows_script) for user_data. Destroying it closes the enrollment. |
gardience_bootstrap_script (data source) | A first-boot script for one machine: its name in Gardience and extra groups. |
gardience_machine | Waits until a machine has joined, and removes it from the fleet when it is destroyed. |
gardience_node_group | A custom node group. |
gardience_state | A saved state, checked by the configuration keeper before it is published. |
gardience_event_rule | An event rule: restart a stopped service, warn about a filling disk, or apply a state to each new machine. |
gardience_runbook | A runbook: one Salt function with a form. Saving never runs it. |
gardience_change_window | A change window: changes to a node group wait until it is open. |
gardience_schedule | A schedule that runs a runbook or workflow on set days. It starts turned off: an admin turns it on in the console. |
gardience_machine (data source) | Whether a machine has joined, its OS and whether it is compliant. |
Everything the provider changes is recorded in the audit log under the key's owner, with "via API key NAME".
Before you start
- An admin creates a configure API key: Settings > API keys, scope Configure (Terraform, admins). It manages configuration only and never runs anything on the fleet. See API keys and the REST API.
- New machines must reach the Gardience server on the Salt ports (4505 and 4506, or the ports set at install), and download the Salt agent from
github.comandpackages.broadcom.com. For offline sites, bake the agent into the image and keep the script for the rest. - Give Terraform the server's address and the key, for example as environment variables:
export GARDIENCE_URL=https://gardience.example.com
export GARDIENCE_API_KEY=gdn_... # from your secrets managerIf the server uses a certificate from a private certificate authority, set ca_cert_file (or GARDIENCE_CA_CERT_FILE) to that authority's PEM file.
Example: web servers that configure themselves
terraform {
required_providers {
gardience = { source = "gardience/gardience" }
}
}
provider "gardience" {}
# machines built with this enrollment join on their own and land in the "web" group
resource "gardience_enrollment" "web" {
label = "web tier (terraform)"
hours = 2160 # 90 days; Terraform opens a new one when it expires
groups = ["web"]
}
# how a web server should be
resource "gardience_state" "web_base" {
name = "web_base"
content = <<-YAML
nginx:
pkg.installed: []
service.running:
- enable: True
- require:
- pkg: nginx
YAML
}
# the moment a new machine in "web" joins, apply web_base to it
resource "gardience_event_rule" "web_join" {
id = "web-join"
kind = "join"
scope = "group"
group = "web"
state = gardience_state.web_base.name
depends_on = [gardience_enrollment.web]
}
# keep nginx running everywhere it is watched
resource "gardience_event_rule" "nginx" {
id = "keep-nginx"
kind = "service"
os = "linux"
service = "nginx"
}
data "gardience_bootstrap_script" "web" {
count = 3
enrollment_id = gardience_enrollment.web.id
os = "linux"
node = "web-${count.index + 1}"
}
resource "aws_instance" "web" {
count = 3
ami = var.ami
instance_type = "t3.small"
user_data = data.gardience_bootstrap_script.web[count.index].content
tags = { Name = "web-${count.index + 1}" }
}
# wait until each machine has joined; on destroy, remove it from the fleet
resource "gardience_machine" "web" {
count = 3
name = "web-${count.index + 1}"
depends_on = [aws_instance.web]
}On terraform apply, each instance boots, installs the pinned and hash-checked Salt agent, joins Gardience without anyone approving it, lands in the web group, and gets web_base within seconds of joining. From then on it is in every scan, patch run, baseline and report like any other machine. On terraform destroy, the machines are removed from the fleet.
For Windows, use os = "windows" and wrap the script for your platform (on AWS: user_data = "<powershell>${data.gardience_bootstrap_script.win.content}</powershell>").
Bring an existing setup under Terraform
Gardience writes its current configuration as Terraform, with an import block for every item, so you can adopt what admins already built in the console without recreating it:
curl -fsS -H "Authorization: Bearer $GARDIENCE_API_KEY" "$GARDIENCE_URL/gardience/api/v1/export" > gardience.tf
terraform plan # shows each item to import, and no changes
terraform apply # adopts themIt covers node groups, saved states, event rules, custom runbooks, change windows, and schedules. It never writes secrets, API keys, enrollments, users or roles, or states kept in Git (those stay in Git). Each export is in the audit log.
Good to know
- Scripts and tokens are secrets. The provider marks them sensitive. Keep Terraform state in an encrypted backend.
- Saved states are checked before they are published. While command execution is off, states that run commands are refused, and so are templates.
terraform applythen fails with the reason (also in the audit log). See Saved states. - What a configure key can do. It cannot run anything on machines already in the fleet. On new machines it can: enrollments let them join without approval, and join rules apply saved states to them. Keep the key in your secrets manager and treat it like an admin credential.
- Removing machines is cushioned. On
terraform destroy,gardience_machineasks Gardience to remove the machine. The config keeper checks the request, and the Salt master holds the machine's key for 30 days instead of deleting it (root can restore it withgardience machine restore NAME), and at most 50 machines can be removed in any hour by default (root changes it withgardience machine limit N). For machines you never want Terraform to remove, addlifecycle { prevent_destroy = true }to theirgardience_machine, or setremove_on_destroy = false. - Join rules fire once per machine, the first time it connects. Machines that were already in the fleet when 1.5.0 was installed never count as new. Locked machines are skipped.
- Group names. A join rule can name a group that an open enrollment gives its machines, even before the first machine has joined.
- Closing an enrollment in the console makes Terraform create a new one on the next apply. Machines that already joined stay.
- Schedules wait for an admin. A schedule runs things on the fleet by itself, so one made with an API key starts turned off and says why under Automation > Schedules. Changing what or when it runs, or changing the runbook it runs, turns it off again until an admin looks. A stolen key alone cannot make the fleet run something.
- Roles are not managed by Terraform on purpose: a key that could make someone an admin would be a way around every other control. Manage people and roles in the console or your identity provider.
- Importing. Node groups (
name), saved states (name), event rules (id), runbooks, change windows, and schedules (id), and machines (name) can be imported. Enrollments cannot, because their token is only shown when they are created.
Without Terraform
The same API works from any tool. See API keys and the REST API for the endpoints, and Enrollment tokens for first-boot scripts from the console.