tofu: read provider credentials from 1Password
Items are referenced by UUID because the provider fails title lookups
with SDK return code -4.
Assisted-by: Claude Opus 5.5 via Claude Code
diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl
index 0d218cc..eeb8a80 100644
--- a/.terraform.lock.hcl
+++ b/.terraform.lock.hcl
@@ -1,6 +1,28 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
+provider "registry.opentofu.org/1password/onepassword" {
+ version = "3.3.1"
+ constraints = "~> 3.3"
+ hashes = [
+ "h1:35PCpSNLVubReT1imwfC+FpIP5gQWx+rvG4njkXkZKM=",
+ "h1:GqvBYImDNYNPMi9o3kJTSIJDeEnNO7om46RWA0wjeso=",
+ "h1:Tg8bJ+ATy5pla6ZTn87lb3nIyiTJTgQHJdxOWQjFF9k=",
+ "h1:a8DHdeyXt8YaEngXGEWZC75W27OG742wtw5BmRnhrpI=",
+ "h1:bPFt8A+PWGwEj3wQ3D0GxyrTMmr1piNrwEuPJu7AZTo=",
+ "h1:nThx/0XctUKlAa6aAsSEHZvx/mD1tSxCE6pacSnotxU=",
+ "h1:syh+iS5VPBQTLszL503BOi5rLpL1wGl1IjyYqgpKYIg=",
+ "zh:02d93a7f520ec69ad8944a68dcbf512e2f9920a6696628b8d05e6ad408309f35",
+ "zh:0f91a902da84470af95f0da4dc21127b84e23c856a431ff9ecfe45d9c6775ef0",
+ "zh:161bc55c466214a5d425ba85753d74ed5078212db965f726e6650d2e1524d633",
+ "zh:3de4a9f212e1046016a3ace8816e2cb15cfb7b9579161e468a08b9034d6a5f51",
+ "zh:730105346065ea3d2bd6acc6f5fe36f7b8a2b54c513d20a46bcd51d656e82bb4",
+ "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
+ "zh:c29f6025d099bc8f1f96e7bb4cd66c5d07209b4141d2fd7720228cf20c9c8efd",
+ "zh:d8ea431d396986ca6baf033fa9aaccb73d6a9f9b7d42bea7af8dc73b9ef20297",
+ ]
+}
+
provider "registry.opentofu.org/dnsimple/dnsimple" {
version = "1.10.0"
constraints = "~> 1.10"
diff --git a/docs/secrets.md b/docs/secrets.md
index ebbef70..4452080 100644
--- a/docs/secrets.md
+++ b/docs/secrets.md
@@ -62,7 +62,7 @@ sops host_vars/lotus-land-story.sops.yml
The same command creates a new `host_vars/<host>.sops.yml`, encrypted
through the creation rule.
-## OpenTofu state
+## OpenTofu
OpenTofu keeps state in the gitignored `terraform.tfstate` and encrypts it
with AES-GCM. The key is 32 random bytes, base64-encoded, in the
@@ -70,6 +70,11 @@ with AES-GCM. The key is 32 random bytes, base64-encoded, in the
[external key provider][external] that reads it with `op read`, so each
`tofu` command that touches state asks 1Password for it.
+Provider credentials come from the `boxen-*` items in the same vault, read
+through the 1Password provider's ephemeral `onepassword_item` so they never
+reach state. The provider authenticates through the desktop app, which needs
+Settings → Developer → "Integrate with 1Password SDKs" turned on.
+
[external]: https://opentofu.org/docs/language/state/encryption/
## Losing the key
diff --git a/main.tf b/main.tf
index 480fae6..35888d4 100644
--- a/main.tf
+++ b/main.tf
@@ -30,6 +30,11 @@ terraform {
version = "~> 2.18"
}
+ onepassword = {
+ source = "1Password/onepassword"
+ version = "~> 3.3"
+ }
+
pihole = {
source = "ryanwholey/pihole"
@@ -44,39 +49,57 @@ terraform {
}
}
+provider "onepassword" {
+ account = "alydipha.1password.com"
+}
+
+# Items are looked up by UUID: lookups by title fail with SDK return code -4.
+locals {
+ op_private_vault = "7escx6csroqkgfrmkpdubfrqoa"
+}
+
+# boxen-dnsimple
+ephemeral "onepassword_item" "dnsimple" {
+ vault = local.op_private_vault
+ uuid = "urxqkxvbav5m2trwhzgwkg43pu"
+}
+
+# boxen-linode
+ephemeral "onepassword_item" "linode" {
+ vault = local.op_private_vault
+ uuid = "wgyjufgbnzfv6h6jvqy7g632ma"
+}
+
+# boxen-pihole
+ephemeral "onepassword_item" "pihole" {
+ vault = local.op_private_vault
+ uuid = "xpf55644ii3azfcl74tywiagb4"
+}
+
+# boxen-tailscale
+ephemeral "onepassword_item" "tailscale" {
+ vault = local.op_private_vault
+ uuid = "zoaigxq4d3kg3heqgobxlg4hdu"
+}
+
provider "dnsimple" {
- token = var.dnsimple.token
- account = var.dnsimple.account
+ token = ephemeral.onepassword_item.dnsimple.credential
+ account = ephemeral.onepassword_item.dnsimple.username
}
provider "linode" {
- # token = LINODE_TOKEN
+ token = ephemeral.onepassword_item.linode.credential
}
provider "pihole" {
url = "http://mountain-of-faith:80"
- password = var.pihole.password
+ password = ephemeral.onepassword_item.pihole.credential
}
provider "tailscale" {
- tailnet = "-"
- # oauth_client_id = TAILSCALE_OAUTH_CLIENT_ID
- # oauth_client_secret = TAILSCALE_OAUTH_CLIENT_SECRET
-}
-
-variable "dnsimple" {
- type = object({
- token = string
- account = string
- })
- nullable = false
-}
-
-variable "pihole" {
- type = object({
- password = string
- })
- nullable = false
+ tailnet = "-"
+ oauth_client_id = ephemeral.onepassword_item.tailscale.username
+ oauth_client_secret = ephemeral.onepassword_item.tailscale.credential
}
data "linode_instances" "ramble_hard" {