#!/usr/bin/env bash
# exec-env — fetch a JSON secret from AWS Secrets Manager and exec a command
# with every key exported into its environment (sops exec-env style).
#
#   exec-env <secret-id> [--region <region>] -- <command [args...]>
#
# Design rules (docs/secrets-injection-plan.md):
#   - Uses the ambient AWS credential chain (Roles Anywhere / profile);
#     the wrapped app itself never needs AWS access.
#   - Never writes anything to disk — values live only in the child env.
#   - Fail closed: exits non-zero on any fetch/parse failure and never
#     prints a secret value (aws/python3 stderr is suppressed).
#   - Env keys must match [A-Za-z0-9_]+; values must be single-line strings.
#   - bash 3.2+ (macOS default) and any box Linux bash.

set -euo pipefail

REGION="${AWS_DEFAULT_REGION:-eu-west-1}"

usage() {
  echo "usage: exec-env <secret-id> [--region <region>] -- <command [args...]>" >&2
}

[[ $# -gt 0 ]] || { usage; exit 2; }
case "$1" in
  -h|--help) usage; exit 0 ;;
esac
SECRET_ID="$1"
shift

while [[ $# -gt 0 && "$1" != "--" ]]; do
  case "$1" in
    --region)
      [[ $# -ge 2 ]] || { echo "exec-env: --region needs a value" >&2; exit 2; }
      REGION="$2"
      shift 2
      ;;
    *)
      echo "exec-env: unknown option '$1' — expected '--' before the command" >&2
      exit 2
      ;;
  esac
done
[[ ${1:-} == "--" ]] || { echo "exec-env: missing '--' before the command" >&2; usage; exit 2; }
shift
[[ $# -gt 0 ]] || { echo "exec-env: no command given after '--'" >&2; exit 2; }

# Fetch. The secret never leaves this process and is never printed.
json="$(aws secretsmanager get-secret-value \
    --secret-id "$SECRET_ID" \
    --region "$REGION" \
    --query SecretString \
    --output text 2>/dev/null)" || {
  echo "exec-env: failed to fetch secret '$SECRET_ID' (region $REGION) — check the role's GetSecretValue grant and the ambient AWS profile" >&2
  exit 1
}

# Parse into key=value lines. Values must be single-line strings (no NUL,
# no newline); keys are re-validated below before export.
pairs="$(printf '%s' "$json" | python3 -c "
import json, sys
data = json.load(sys.stdin)
if not isinstance(data, dict):
    sys.exit('payload is not a JSON object')
for key, value in data.items():
    if not key or not all('a' <= c <= 'z' or 'A' <= c <= 'Z' or '0' <= c <= '9' or c == '_' for c in key):
        sys.exit('invalid env key')
    if not isinstance(value, str) or chr(0) in value or chr(10) in value or chr(13) in value:
        sys.exit('unsupported value for key ' + key)
    sys.stdout.write(key + '=' + value + chr(10))
" 2>/dev/null)" || {
  echo "exec-env: failed to parse secret '$SECRET_ID' (expected a flat JSON object of single-line strings)" >&2
  exit 1
}
unset json

exported=0
while IFS='=' read -r key value; do
  case "$key" in
    ""|*[!A-Za-z0-9_]*)
      echo "exec-env: secret '$SECRET_ID' contains an invalid env key — refusing to export" >&2
      exit 1
      ;;
  esac
  export "$key=$value"
  exported=$((exported + 1))
done <<< "$pairs"
unset pairs

[[ "$exported" -gt 0 ]] || { echo "exec-env: secret '$SECRET_ID' has no keys to export" >&2; exit 1; }

exec "$@"
