diff --git a/docs/dynu-dns-inventory.md b/docs/dynu-dns-inventory.md index 777d783..529fa15 100644 --- a/docs/dynu-dns-inventory.md +++ b/docs/dynu-dns-inventory.md @@ -24,6 +24,18 @@ This repository includes a **read-only** Dynu DNS inventory workflow for `lan.dd - `DYNU_BASE_URL` (optional, defaults to `https://api.dynu.com`) - `DYNU_READ_ONLY` (**must** be `true`) +Recommended local secrets file (not committed): `secrets/dynu.env` + +```bash +DYNU_API_KEY=replace-with-real-api-key +DYNU_READ_ONLY=true +DYNU_BASE_URL=https://api.dynu.com +``` + +Notes: +- Keep values unquoted unless required by your shell. +- `scripts/dynu/build_dns_inventory.sh` will auto-load `secrets/dynu.env` when present. + ## Commands Run directly: @@ -36,7 +48,7 @@ DYNU_READ_ONLY=true python3 scripts/dynu/correlate_dynu_with_traefik.py Or run the wrapper: ```bash -DYNU_READ_ONLY=true DYNU_API_KEY=... scripts/dynu/build_dns_inventory.sh +scripts/dynu/build_dns_inventory.sh ``` ## Artifacts diff --git a/docs/security-secrets.md b/docs/security-secrets.md index a318e20..4389901 100644 --- a/docs/security-secrets.md +++ b/docs/security-secrets.md @@ -10,6 +10,7 @@ For machine-readable inventory metadata, use [`../secrets/inventory.json`](../se - Canonical example template: [`../secrets/.env.secrets.example`](../secrets/.env.secrets.example) - Runtime-loaded secret env file (local, non-committed): `../secrets/stack-secrets.env` +- Dynu DNS inventory env file (local, non-committed): `../secrets/dynu.env` - Docker secret files (local, non-committed): `../secrets/*.txt` Treat the example template as the canonical shape for expected environment variables. @@ -20,9 +21,11 @@ Treat the example template as the canonical shape for expected environment varia - Document expected variable names and usage expectations. 2. **Local runtime env file (`stack-secrets.env`)** - Holds local runtime secret values loaded during compose rendering. -3. **Local Docker secret files (`*.txt`)** +3. **Local Dynu env file (`dynu.env`)** + - Holds `DYNU_*` values used by read-only Dynu DNS inventory scripts. +4. **Local Docker secret files (`*.txt`)** - Hold password/token material consumed via `*_FILE` style configuration. -4. **Externally managed secret inputs** +5. **Externally managed secret inputs** - Some values are managed outside shared templates and provided through file mounts or environment substitution. ## Machine-readable inventory @@ -41,6 +44,7 @@ Before running compose operations, follow [`./deployment-prerequisites.md`](./de Never commit: - `secrets/stack-secrets.env` +- `secrets/dynu.env` - real `secrets/*.txt` secret files - real Terraform `.tfvars` files containing credentials - Terraform state files with sensitive runtime metadata diff --git a/scripts/dynu/build_dns_inventory.sh b/scripts/dynu/build_dns_inventory.sh index 8e0d8e9..b783726 100755 --- a/scripts/dynu/build_dns_inventory.sh +++ b/scripts/dynu/build_dns_inventory.sh @@ -4,10 +4,23 @@ set -euo pipefail # This integration is intentionally read-only. # No Dynu mutations are permitted in this repo at this stage. +# Optional convenience: auto-load local Dynu env file when variables are unset. +if [[ -f "secrets/dynu.env" ]]; then + set -a + # shellcheck source=/dev/null + source "secrets/dynu.env" + set +a +fi + if [[ "${DYNU_READ_ONLY:-}" != "true" ]]; then echo "Refusing to run: DYNU_READ_ONLY must be exactly 'true'." >&2 exit 2 fi +if [[ -z "${DYNU_API_KEY:-}" ]]; then + echo "Missing DYNU_API_KEY. Set it in env or secrets/dynu.env." >&2 + exit 2 +fi + python3 scripts/dynu/fetch_dynu_dns.py python3 scripts/dynu/correlate_dynu_with_traefik.py diff --git a/scripts/dynu/fetch_dynu_dns.py b/scripts/dynu/fetch_dynu_dns.py index 047f146..8621ec1 100755 --- a/scripts/dynu/fetch_dynu_dns.py +++ b/scripts/dynu/fetch_dynu_dns.py @@ -54,7 +54,13 @@ def get_json(base_url: str, api_key: str, path: str, query: Optional[Dict[str, A body = resp.read().decode("utf-8") except HTTPError as exc: detail = exc.read().decode("utf-8", errors="replace") - raise RuntimeError(f"Dynu API GET failed at {path}: HTTP {exc.code} {detail}") from exc + hint = "" + if exc.code == 401: + hint = ( + " Check DYNU_API_KEY from secrets/dynu.env, verify it is a valid Dynu API key, " + "and ensure DYNU_BASE_URL points to the Dynu API endpoint." + ) + raise RuntimeError(f"Dynu API GET failed at {path}: HTTP {exc.code} {detail}.{hint}") from exc except URLError as exc: raise RuntimeError(f"Dynu API GET failed at {path}: {exc}") from exc @@ -177,8 +183,12 @@ def main() -> int: if not api_key: print("Missing DYNU_API_KEY. Refusing to call Dynu API.", file=sys.stderr) return 2 + api_key = api_key.strip().strip("'").strip('"') + if not api_key: + print("DYNU_API_KEY is empty after trimming quotes/whitespace.", file=sys.stderr) + return 2 - base_url = os.environ.get("DYNU_BASE_URL", DEFAULT_BASE_URL) + base_url = os.environ.get("DYNU_BASE_URL", DEFAULT_BASE_URL).strip().strip("'").strip('"') domains = list_domains(base_url, api_key) target = [d for d in domains if str(d.get("name", "")).strip(".").lower() == BASE_DOMAIN] diff --git a/secrets/inventory.json b/secrets/inventory.json index 305163b..412e9a1 100644 --- a/secrets/inventory.json +++ b/secrets/inventory.json @@ -2,6 +2,7 @@ "scope_and_authority": { "canonical_example_template": "secrets/.env.secrets.example", "runtime_loaded_secret_env_file": "secrets/stack-secrets.env", + "dns_inventory_secret_env_file": "secrets/dynu.env", "docker_secret_files_pattern": "secrets/*.txt" }, "env_template_variables": [ @@ -140,6 +141,7 @@ ], "commit_safety_rules": [ "Never commit secrets/stack-secrets.env.", + "Never commit secrets/dynu.env.", "Never commit real secrets/*.txt files.", "Never commit real Terraform .tfvars containing credentials.", "Never commit Terraform state files with sensitive runtime metadata."