diff --git a/.gitignore b/.gitignore index 414b9bb..087072f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ every_channel_ed25519 *.p12 *.pfx **/.env + +# NEVER commit plaintext secrets +secrets/token.txt diff --git a/scripts/agenix-import-token-file.sh b/scripts/agenix-import-token-file.sh new file mode 100755 index 0000000..9c24ccb --- /dev/null +++ b/scripts/agenix-import-token-file.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${root}" + +in_file="${1:-secrets/token.txt}" +out_file="${2:-secrets/codeberg-token.age}" + +rules_file="${EVERY_CHANNEL_AGE_RULES_FILE:-${root}/secrets.nix}" +identity_file="${EVERY_CHANNEL_AGE_IDENTITY_FILE:-$HOME/.config/every.channel/keys/founder_ed25519}" + +if [[ ! -f "${in_file}" ]]; then + echo "error: input file not found: ${in_file}" >&2 + exit 2 +fi +if [[ ! -f "${rules_file}" ]]; then + echo "error: agenix RULES file not found: ${rules_file}" >&2 + exit 2 +fi +if [[ ! -f "${identity_file}" ]]; then + echo "error: agenix identity file not found: ${identity_file}" >&2 + exit 2 +fi + +if ! command -v agenix >/dev/null 2>&1; then + echo "error: agenix not found in PATH (run: nix develop)" >&2 + exit 2 +fi +if ! command -v ssh-keygen >/dev/null 2>&1; then + echo "error: ssh-keygen not found in PATH" >&2 + exit 2 +fi + +# Verify that the configured identity corresponds to the recipient in secrets.nix. +founder_key="$( + sed -nE 's/^[[:space:]]*founder[[:space:]]*=[[:space:]]*"([^"]+)".*/\\1/p' secrets.nix | head -n 1 +)" +if [[ -z "${founder_key}" ]]; then + echo "error: could not parse founder key from secrets.nix" >&2 + exit 2 +fi + +if ! grep -q "\"${out_file}\"" "${rules_file}"; then + echo "error: no rule for ${out_file} in ${rules_file}" >&2 + echo "hint: add it to secrets.nix or pass a different output path" >&2 + exit 2 +fi + +priv_line="$(ssh-keygen -y -f "${identity_file}" 2>/dev/null || true)" +if [[ -z "${priv_line}" ]]; then + echo "error: unable to derive public key from identity file: ${identity_file}" >&2 + echo "hint: if the key is passphrase-protected, run an interactive shell or provide a different identity" >&2 + exit 2 +fi +priv_pub="$(printf '%s\n' "${priv_line}" | cut -d' ' -f1-2)" +founder_pub="$(printf '%s\n' "${founder_key}" | cut -d' ' -f1-2)" +if [[ "${priv_pub}" != "${founder_pub}" ]]; then + echo "error: identity public key does not match secrets recipient" >&2 + echo "identity_file: ${identity_file}" >&2 + echo "identity_pub: ${priv_pub}" >&2 + echo "recipient_pub: ${founder_pub}" >&2 + echo "hint: set EVERY_CHANNEL_AGE_IDENTITY_FILE to the correct private key path" >&2 + exit 2 +fi + +tmp_dec="$(mktemp "${TMPDIR:-/tmp}/ec-agenix-dec.XXXXXX")" +trap 'rm -f "${tmp_dec}"' EXIT + +# Encrypt: non-interactive mode reads plaintext from stdin. +RULES="${rules_file}" cat "${in_file}" | agenix -e "${out_file}" -i "${identity_file}" >/dev/null + +# Decrypt and verify byte-for-byte equality. +RULES="${rules_file}" agenix -d "${out_file}" -i "${identity_file}" > "${tmp_dec}" +if ! cmp -s "${tmp_dec}" "${in_file}"; then + echo "error: verification failed: decrypted secret does not match input file" >&2 + exit 2 +fi + +rm -f "${in_file}" +echo "ok: wrote ${out_file} and removed ${in_file} after verification" diff --git a/secrets/secrets.nix b/secrets.nix similarity index 57% rename from secrets/secrets.nix rename to secrets.nix index 7e956dc..59e4b6b 100644 --- a/secrets/secrets.nix +++ b/secrets.nix @@ -3,7 +3,7 @@ let founder = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJCBTSEEcBOhOkf3WF1e8xmblAZHvgTibFsqck2GY8D/"; in { - "cloudflare-api-token.age".publicKeys = [ founder ]; - "codeberg-token.age".publicKeys = [ founder ]; + "secrets/cloudflare-api-token.age".publicKeys = [ founder ]; + "secrets/codeberg-token.age".publicKeys = [ founder ]; } diff --git a/secrets/README.md b/secrets/README.md index baaae4e..7459f35 100644 --- a/secrets/README.md +++ b/secrets/README.md @@ -21,15 +21,13 @@ nix develop Encrypt (create) a secret: ```sh -cd secrets -agenix -e cloudflare-api-token.age +agenix -e secrets/cloudflare-api-token.age ``` Decrypt (inspect) a secret: ```sh -cd secrets -agenix -d cloudflare-api-token.age +agenix -d secrets/cloudflare-api-token.age ``` ## Decryption identity diff --git a/secrets/codeberg-token.age b/secrets/codeberg-token.age new file mode 100644 index 0000000..7623509 --- /dev/null +++ b/secrets/codeberg-token.age @@ -0,0 +1,5 @@ +age-encryption.org/v1 +-> ssh-ed25519 29OJ4A G6byj6PhWofxSh8K5FGSqBs5W5uKtyJ2MGY1JFb+STc +d25eWVNmz2+0zKVVRZ/Pib4YZClhJrML6s3hbLh9rMU +--- t/6aoMSRLI8vay71VugNOGwKHjHteiC+SinD6gQARYM +8E/?ᢿw JlSzi 8)`;B4LT?;z\ \ No newline at end of file