20edbaef0b
Co-authored-by: Copilot <copilot@github.com>
103 lines
3.5 KiB
YAML
103 lines
3.5 KiB
YAML
name: SOPS decrypt
|
|
description: |
|
|
Decrypt one or more SOPS-encrypted files using the host's age key.
|
|
|
|
Each line of `decrypt` is a `src -> dst` pair. The destination's extension
|
|
determines the output format (`.env`/dotenv, `.json`, `.yaml|.yml`, `.ini`,
|
|
anything else = raw/binary). Plaintext files are written with mode 0600.
|
|
|
|
Cleanup is the caller's responsibility. The list of decrypted destinations
|
|
is exported via `SOPS_DECRYPT_CLEANUP` (path to a newline-separated file)
|
|
and as the step output `cleanup-list`. The reusable workflow
|
|
`deploy-stack.yml` already wires an `if: always()` cleanup step around it;
|
|
if you call this action directly, do the same.
|
|
|
|
inputs:
|
|
decrypt:
|
|
description: |
|
|
Multi-line list of `src -> dst` pairs. Example:
|
|
secrets/env.sops.yaml -> .env
|
|
secrets/config.sops.yaml -> config/app.yaml
|
|
secrets/credentials.sops.json -> credentials.json
|
|
Blank lines and `#` comments are ignored.
|
|
required: true
|
|
age-key-file:
|
|
description: Path to the host's age private key.
|
|
required: false
|
|
default: /etc/charlie/age.key
|
|
workdir:
|
|
description: Working directory the `src` and `dst` paths are resolved against.
|
|
required: false
|
|
default: ${{ github.workspace }}
|
|
|
|
outputs:
|
|
cleanup-list:
|
|
description: Path to a newline-separated file listing decrypted destinations.
|
|
value: ${{ steps.decrypt.outputs.cleanup-list }}
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
- id: decrypt
|
|
shell: bash
|
|
env:
|
|
SOPS_AGE_KEY_FILE: ${{ inputs.age-key-file }}
|
|
DECRYPT_INPUT: ${{ inputs.decrypt }}
|
|
WORKDIR: ${{ inputs.workdir }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
command -v sops >/dev/null 2>&1 || { echo "::error::sops binary not on PATH"; exit 1; }
|
|
if [ ! -r "$SOPS_AGE_KEY_FILE" ]; then
|
|
echo "::error::age key not readable at $SOPS_AGE_KEY_FILE (need root or appropriate perms)"
|
|
exit 1
|
|
fi
|
|
|
|
cd "$WORKDIR"
|
|
|
|
cleanup_list="${RUNNER_TEMP:-/tmp}/sops-decrypt-cleanup.$$.list"
|
|
: > "$cleanup_list"
|
|
|
|
while IFS= read -r raw; do
|
|
line="${raw%%#*}"
|
|
line="$(printf '%s' "$line" | awk '{$1=$1;print}')"
|
|
[ -z "$line" ] && continue
|
|
|
|
case "$line" in
|
|
*"->"*) ;;
|
|
*) echo "::error::invalid decrypt entry (missing '->'): $raw"; exit 1 ;;
|
|
esac
|
|
|
|
src="$(printf '%s' "${line%%->*}" | awk '{$1=$1;print}')"
|
|
dst="$(printf '%s' "${line#*->}" | awk '{$1=$1;print}')"
|
|
|
|
if [ -z "$src" ] || [ -z "$dst" ]; then
|
|
echo "::error::invalid decrypt entry: $raw"
|
|
exit 1
|
|
fi
|
|
if [ ! -f "$src" ]; then
|
|
echo "::error::source not found: $src"
|
|
exit 1
|
|
fi
|
|
|
|
case "$dst" in
|
|
*.env|.env) out_format="dotenv" ;;
|
|
*.json) out_format="json" ;;
|
|
*.yaml|*.yml) out_format="yaml" ;;
|
|
*.ini) out_format="ini" ;;
|
|
*) out_format="binary" ;;
|
|
esac
|
|
|
|
dst_dir="$(dirname -- "$dst")"
|
|
[ "$dst_dir" = "." ] || mkdir -p "$dst_dir"
|
|
|
|
umask 077
|
|
sops --decrypt --output-type "$out_format" --output "$dst" "$src"
|
|
chmod 600 "$dst"
|
|
printf '%s\n' "$dst" >> "$cleanup_list"
|
|
echo "decrypted: $src -> $dst"
|
|
done <<< "$DECRYPT_INPUT"
|
|
|
|
echo "cleanup-list=$cleanup_list" >> "$GITHUB_OUTPUT"
|
|
echo "SOPS_DECRYPT_CLEANUP=$cleanup_list" >> "$GITHUB_ENV"
|