316 lines
12 KiB
YAML
316 lines
12 KiB
YAML
name: Decrypt Release Secrets
|
|
description: Decrypts age-sealed Burrow release secrets and exports CI paths/env.
|
|
inputs:
|
|
root:
|
|
description: Repository root
|
|
required: false
|
|
default: .
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
- shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
ROOT_INPUT='${{ inputs.root }}'
|
|
ROOT="$(cd "$ROOT_INPUT" && pwd)"
|
|
|
|
resolve_nix_bin() {
|
|
local candidate
|
|
for candidate in \
|
|
"${NIX_BIN:-}" \
|
|
"$(command -v nix 2>/dev/null || true)" \
|
|
/nix/var/nix/profiles/default/bin/nix \
|
|
"${HOME:-}/.nix-profile/bin/nix" \
|
|
"${HOME:-}/.local/state/nix/profile/bin/nix" \
|
|
/Users/runner/.nix-profile/bin/nix \
|
|
/Users/runner/.local/state/nix/profile/bin/nix \
|
|
/home/runner/.nix-profile/bin/nix \
|
|
/home/runner/.local/state/nix/profile/bin/nix \
|
|
; do
|
|
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
resolve_decrypt_tool() {
|
|
local candidate
|
|
for candidate in \
|
|
"${AGE_BIN:-}" \
|
|
"$(command -v age 2>/dev/null || true)" \
|
|
/opt/homebrew/bin/age \
|
|
/usr/local/bin/age \
|
|
/usr/bin/age \
|
|
/run/current-system/sw/bin/age \
|
|
/etc/profiles/per-user/runner/bin/age \
|
|
"${HOME:-}/.nix-profile/bin/age" \
|
|
"${HOME:-}/.local/state/nix/profile/bin/age" \
|
|
/Users/runner/.nix-profile/bin/age \
|
|
/Users/runner/.local/state/nix/profile/bin/age \
|
|
/home/runner/.nix-profile/bin/age \
|
|
/home/runner/.local/state/nix/profile/bin/age \
|
|
; do
|
|
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
|
printf 'age:%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
for candidate in \
|
|
"${AGENIX_BIN:-}" \
|
|
"$(command -v agenix 2>/dev/null || true)" \
|
|
; do
|
|
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
|
printf 'agenix:%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
local nix_bin
|
|
nix_bin="$(resolve_nix_bin || true)"
|
|
if [[ -n "$nix_bin" ]]; then
|
|
local built
|
|
built="$("$nix_bin" --extra-experimental-features 'nix-command flakes' build --no-link "$ROOT#age" --print-out-paths 2>/dev/null | tail -n 1 || true)"
|
|
if [[ -n "$built" && -x "$built/bin/age" ]]; then
|
|
printf 'age:%s\n' "$built/bin/age"
|
|
return 0
|
|
fi
|
|
built="$("$nix_bin" --extra-experimental-features 'nix-command flakes' build --no-link "$ROOT#agenix" --print-out-paths 2>/dev/null | tail -n 1 || true)"
|
|
if [[ -n "$built" && -x "$built/bin/agenix" ]]; then
|
|
printf 'agenix:%s\n' "$built/bin/agenix"
|
|
return 0
|
|
fi
|
|
fi
|
|
echo "::error ::age or agenix is required to decrypt release secrets but neither is available." >&2
|
|
exit 1
|
|
}
|
|
|
|
decrypt_tool="$(resolve_decrypt_tool)"
|
|
decrypt_kind="${decrypt_tool%%:*}"
|
|
decrypt_bin="${decrypt_tool#*:}"
|
|
KEY="${AGE_IDENTITY_PATH:-}"
|
|
if [[ -z "$KEY" || ! -s "$KEY" ]]; then
|
|
echo "::error ::Missing age identity. Run Scripts/resolve-age-identity.sh before this action." >&2
|
|
exit 1
|
|
fi
|
|
|
|
decrypt_age_file() {
|
|
local file_path="$1"
|
|
if [[ "$decrypt_kind" == "agenix" ]]; then
|
|
local agenix_path="$file_path"
|
|
if [[ "$file_path" == "$ROOT/"* ]]; then
|
|
agenix_path="${file_path#"$ROOT"/}"
|
|
fi
|
|
(cd "$ROOT" && "$decrypt_bin" --identity "$KEY" --decrypt "$agenix_path")
|
|
else
|
|
"$decrypt_bin" --decrypt -i "$KEY" "$file_path"
|
|
fi
|
|
}
|
|
|
|
write_env() {
|
|
local name="$1"
|
|
local value="$2"
|
|
echo "${name}=${value}" >> "$GITHUB_ENV"
|
|
}
|
|
|
|
mask_multiline() {
|
|
local value="$1"
|
|
echo "::add-mask::${value}"
|
|
while IFS= read -r line; do
|
|
[[ -n "$line" ]] || continue
|
|
echo "::add-mask::${line}"
|
|
done <<<"$value"
|
|
}
|
|
|
|
decrypt_env_optional() {
|
|
local name="$1"
|
|
local file="$2"
|
|
if [[ ! -f "$file" ]]; then
|
|
echo "::notice ::Skipping optional release secret ${file}"
|
|
return 0
|
|
fi
|
|
local value
|
|
value="$(decrypt_age_file "$file")"
|
|
mask_multiline "$value"
|
|
{
|
|
echo "${name}<<EOF"
|
|
printf '%s\n' "$value"
|
|
echo "EOF"
|
|
} >> "$GITHUB_ENV"
|
|
}
|
|
|
|
decrypt_file_optional() {
|
|
local name="$1"
|
|
local file="$2"
|
|
local suffix="$3"
|
|
if [[ ! -f "$file" ]]; then
|
|
echo "::notice ::Skipping optional release secret ${file}"
|
|
return 0
|
|
fi
|
|
local temp_dir="${RUNNER_TEMP:-/tmp}"
|
|
local temp_file
|
|
temp_file="$(mktemp "${temp_dir}/${name}.XXXXXX")"
|
|
if [[ -n "$suffix" ]]; then
|
|
local with_suffix="${temp_file}.${suffix}"
|
|
mv "$temp_file" "$with_suffix"
|
|
temp_file="$with_suffix"
|
|
fi
|
|
decrypt_age_file "$file" > "$temp_file"
|
|
chmod 600 "$temp_file"
|
|
write_env "$name" "$temp_file"
|
|
}
|
|
|
|
decrypt_dotenv_optional() {
|
|
local file="$1"
|
|
if [[ ! -f "$file" ]]; then
|
|
echo "::notice ::Skipping optional release secret ${file}"
|
|
return 0
|
|
fi
|
|
local temp_file
|
|
temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-release-dotenv.XXXXXX")"
|
|
decrypt_age_file "$file" > "$temp_file"
|
|
# shellcheck disable=SC1090
|
|
set -a
|
|
source "$temp_file"
|
|
set +a
|
|
rm -f "$temp_file"
|
|
|
|
if [[ -z "${MINIO_ROOT_USER:-}" || -z "${MINIO_ROOT_PASSWORD:-}" ]]; then
|
|
echo "::error ::${file} must define MINIO_ROOT_USER and MINIO_ROOT_PASSWORD." >&2
|
|
exit 1
|
|
fi
|
|
echo "::add-mask::${MINIO_ROOT_USER}"
|
|
echo "::add-mask::${MINIO_ROOT_PASSWORD}"
|
|
{
|
|
echo "AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER}"
|
|
echo "AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}"
|
|
echo "AWS_DEFAULT_REGION=${BLOB_REGION:-hel1}"
|
|
echo "AWS_REGION=${BLOB_REGION:-hel1}"
|
|
echo "AWS_S3_ENDPOINT_URL=https://${BLOB_ENDPOINT:-hel1.your-objectstorage.com}"
|
|
} >> "$GITHUB_ENV"
|
|
}
|
|
|
|
decode_base64_to_file() {
|
|
local output="$1"
|
|
python3 -c 'import base64, pathlib, sys; pathlib.Path(sys.argv[1]).write_bytes(base64.b64decode(sys.stdin.buffer.read()))' "$output"
|
|
}
|
|
|
|
decrypt_appstore_connect() {
|
|
local file="$ROOT/secrets/apple/appstore-connect.env.age"
|
|
if [[ ! -f "$file" ]]; then
|
|
echo "::notice ::Skipping optional release secret ${file}"
|
|
return 0
|
|
fi
|
|
|
|
local temp_file key_path key_value
|
|
temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-asc-env.XXXXXX")"
|
|
decrypt_age_file "$file" > "$temp_file"
|
|
# shellcheck disable=SC1090
|
|
set -a
|
|
source "$temp_file"
|
|
set +a
|
|
rm -f "$temp_file"
|
|
|
|
if [[ -z "${ASC_API_KEY_ID:-}" || -z "${ASC_API_ISSUER_ID:-}" ]]; then
|
|
echo "::error ::${file} must define ASC_API_KEY_ID and ASC_API_ISSUER_ID." >&2
|
|
exit 1
|
|
fi
|
|
|
|
key_path="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-asc-key.XXXXXX.p8")"
|
|
if [[ -n "${ASC_API_KEY_BASE64:-}" ]]; then
|
|
printf '%s' "$ASC_API_KEY_BASE64" | decode_base64_to_file "$key_path"
|
|
elif [[ -n "${ASC_API_KEY_P8_BASE64:-}" ]]; then
|
|
printf '%s' "$ASC_API_KEY_P8_BASE64" | decode_base64_to_file "$key_path"
|
|
elif [[ -n "${APPSTORE_KEY:-}" ]]; then
|
|
printf '%s\n' "$APPSTORE_KEY" > "$key_path"
|
|
elif [[ -n "${ASC_API_KEY:-}" ]]; then
|
|
printf '%s\n' "$ASC_API_KEY" > "$key_path"
|
|
else
|
|
echo "::error ::${file} must define ASC_API_KEY_BASE64 or ASC_API_KEY." >&2
|
|
exit 1
|
|
fi
|
|
chmod 600 "$key_path"
|
|
key_value="$(cat "$key_path")"
|
|
mask_multiline "$key_value"
|
|
echo "::add-mask::${ASC_API_KEY_ID}"
|
|
echo "::add-mask::${ASC_API_ISSUER_ID}"
|
|
write_env ASC_API_KEY_ID "$ASC_API_KEY_ID"
|
|
write_env ASC_API_ISSUER_ID "$ASC_API_ISSUER_ID"
|
|
write_env ASC_API_KEY_PATH "$key_path"
|
|
}
|
|
|
|
decrypt_distribution_signing() {
|
|
local file="$ROOT/secrets/apple/distribution-signing.env.age"
|
|
if [[ ! -f "$file" ]]; then
|
|
echo "::notice ::Skipping optional release secret ${file}"
|
|
return 0
|
|
fi
|
|
|
|
local temp_file cert_path cert_password cert_base64
|
|
temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-signing-env.XXXXXX")"
|
|
decrypt_age_file "$file" > "$temp_file"
|
|
# shellcheck disable=SC1090
|
|
set -a
|
|
source "$temp_file"
|
|
set +a
|
|
rm -f "$temp_file"
|
|
|
|
cert_base64="${APPLE_SIGNING_CERTIFICATE_BASE64:-${APPLE_DISTRIBUTION_CERTIFICATE_BASE64:-}}"
|
|
cert_password="${APPLE_SIGNING_CERTIFICATE_PASSWORD:-${APPLE_DISTRIBUTION_CERTIFICATE_PASSWORD:-}}"
|
|
if [[ -z "$cert_password" && -n "${APPLE_SIGNING_CERTIFICATE_PASSWORD_BASE64:-}" ]]; then
|
|
cert_password="$(printf '%s' "$APPLE_SIGNING_CERTIFICATE_PASSWORD_BASE64" | python3 -c 'import base64,sys; print(base64.b64decode(sys.stdin.buffer.read()).decode(), end="")')"
|
|
fi
|
|
if [[ -z "$cert_base64" || -z "$cert_password" ]]; then
|
|
echo "::error ::${file} must define APPLE_SIGNING_CERTIFICATE_BASE64 and APPLE_SIGNING_CERTIFICATE_PASSWORD." >&2
|
|
exit 1
|
|
fi
|
|
|
|
cert_path="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-apple-signing.XXXXXX.p12")"
|
|
printf '%s' "$cert_base64" | decode_base64_to_file "$cert_path"
|
|
chmod 600 "$cert_path"
|
|
echo "::add-mask::${cert_password}"
|
|
write_env APPLE_SIGNING_CERTIFICATE_PATH "$cert_path"
|
|
write_env APPLE_SIGNING_CERTIFICATE_PASSWORD "$cert_password"
|
|
write_env APPLE_KEYCHAIN_PASSWORD "$cert_password"
|
|
}
|
|
|
|
decrypt_sparkle() {
|
|
local file="$ROOT/secrets/apple/sparkle.env.age"
|
|
if [[ ! -f "$file" ]]; then
|
|
echo "::notice ::Skipping optional release secret ${file}"
|
|
return 0
|
|
fi
|
|
|
|
local temp_file key_path key_base64 key_value
|
|
temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-sparkle-env.XXXXXX")"
|
|
decrypt_age_file "$file" > "$temp_file"
|
|
# shellcheck disable=SC1090
|
|
set -a
|
|
source "$temp_file"
|
|
set +a
|
|
rm -f "$temp_file"
|
|
|
|
key_base64="${SPARKLE_EDDSA_KEY_BASE64:-${SPARKLE_PRIVATE_KEY_BASE64:-}}"
|
|
key_path="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-sparkle-eddsa.XXXXXX.key")"
|
|
if [[ -n "$key_base64" ]]; then
|
|
printf '%s' "$key_base64" | decode_base64_to_file "$key_path"
|
|
elif [[ -n "${SPARKLE_EDDSA_KEY:-}" ]]; then
|
|
printf '%s\n' "$SPARKLE_EDDSA_KEY" > "$key_path"
|
|
elif [[ -n "${SPARKLE_PRIVATE_KEY:-}" ]]; then
|
|
printf '%s\n' "$SPARKLE_PRIVATE_KEY" > "$key_path"
|
|
else
|
|
echo "::error ::${file} must define SPARKLE_EDDSA_KEY_BASE64 or SPARKLE_EDDSA_KEY." >&2
|
|
exit 1
|
|
fi
|
|
chmod 600 "$key_path"
|
|
key_value="$(cat "$key_path")"
|
|
mask_multiline "$key_value"
|
|
write_env SPARKLE_EDDSA_KEY_PATH "$key_path"
|
|
}
|
|
|
|
decrypt_appstore_connect
|
|
decrypt_distribution_signing
|
|
decrypt_sparkle
|
|
|
|
decrypt_dotenv_optional "$ROOT/secrets/hetzner/object-storage.age"
|