217 lines
7.1 KiB
Bash
Executable File
217 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
# 비밀값이 명령 추적 출력에 노출되지 않도록 호출자가 활성화한 xtrace도 끈다.
|
|
set +x
|
|
|
|
readonly REQUIRED_CONFIRMATION="APPLY"
|
|
readonly DB_USERNAME="gitea"
|
|
readonly TOTAL_SECRET_CONTRACTS=3
|
|
readonly -a SECRET_CONTRACTS=(
|
|
"platform-data/gitea-db-credentials"
|
|
"gitea/gitea-db-credentials"
|
|
"gitea/gitea-admin"
|
|
)
|
|
|
|
fail() {
|
|
printf 'ERROR: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: bash scripts/bootstrap/create-phase1-secrets.sh --execute
|
|
|
|
Creates these Secrets only when all three are absent:
|
|
platform-data/gitea-db-credentials
|
|
gitea/gitea-db-credentials
|
|
gitea/gitea-admin
|
|
|
|
When all three already exist, validates and reuses their data unchanged. The
|
|
script refuses a partial state and does not perform credential rotation. It may
|
|
repair only the cnpg.io/reload=true label on the platform-data DB Secret.
|
|
USAGE
|
|
}
|
|
|
|
[[ "${1:-}" == "--execute" && "$#" -eq 1 ]] || {
|
|
usage
|
|
exit 2
|
|
}
|
|
|
|
command -v kubectl >/dev/null 2>&1 || fail "kubectl is required"
|
|
command -v base64 >/dev/null 2>&1 || fail "base64 is required"
|
|
|
|
for namespace in platform-data gitea; do
|
|
kubectl get namespace "$namespace" >/dev/null 2>&1 || \
|
|
fail "namespace ${namespace} does not exist; apply namespaces first"
|
|
done
|
|
|
|
existing_secret_count=0
|
|
for contract in "${SECRET_CONTRACTS[@]}"; do
|
|
namespace="${contract%%/*}"
|
|
name="${contract#*/}"
|
|
existing_resource="$(
|
|
kubectl --namespace "$namespace" get secret "$name" \
|
|
--ignore-not-found --output=name
|
|
)"
|
|
if [[ -n "$existing_resource" ]]; then
|
|
((existing_secret_count += 1))
|
|
fi
|
|
done
|
|
|
|
if (( existing_secret_count > 0 && existing_secret_count < TOTAL_SECRET_CONTRACTS )); then
|
|
fail "partial Phase 1 Secret state detected (${existing_secret_count}/${TOTAL_SECRET_CONTRACTS}); refusing creation or rotation"
|
|
fi
|
|
|
|
secret_data_b64() {
|
|
local namespace="$1"
|
|
local name="$2"
|
|
local key="$3"
|
|
|
|
kubectl --namespace "$namespace" get secret "$name" \
|
|
--output="jsonpath={.data.${key}}"
|
|
}
|
|
|
|
validate_basic_auth_secret() {
|
|
local namespace="$1"
|
|
local name="$2"
|
|
local secret_type
|
|
local username_b64
|
|
local password_b64
|
|
|
|
secret_type="$(
|
|
kubectl --namespace "$namespace" get secret "$name" \
|
|
--output='jsonpath={.type}'
|
|
)"
|
|
[[ "$secret_type" == "kubernetes.io/basic-auth" ]] || \
|
|
fail "${namespace}/${name} must have type kubernetes.io/basic-auth"
|
|
|
|
username_b64="$(secret_data_b64 "$namespace" "$name" username)"
|
|
password_b64="$(secret_data_b64 "$namespace" "$name" password)"
|
|
[[ -n "$username_b64" ]] || fail "${namespace}/${name} is missing non-empty data.username"
|
|
[[ -n "$password_b64" ]] || fail "${namespace}/${name} is missing non-empty data.password"
|
|
}
|
|
|
|
if (( existing_secret_count == TOTAL_SECRET_CONTRACTS )); then
|
|
validate_basic_auth_secret platform-data gitea-db-credentials
|
|
validate_basic_auth_secret gitea gitea-db-credentials
|
|
validate_basic_auth_secret gitea gitea-admin
|
|
|
|
platform_db_username_b64="$(
|
|
secret_data_b64 platform-data gitea-db-credentials username
|
|
)"
|
|
platform_db_password_b64="$(
|
|
secret_data_b64 platform-data gitea-db-credentials password
|
|
)"
|
|
gitea_db_username_b64="$(
|
|
secret_data_b64 gitea gitea-db-credentials username
|
|
)"
|
|
gitea_db_password_b64="$(
|
|
secret_data_b64 gitea gitea-db-credentials password
|
|
)"
|
|
expected_db_username_b64="$(printf '%s' "$DB_USERNAME" | base64)"
|
|
|
|
[[ "$platform_db_username_b64" == "$expected_db_username_b64" ]] || \
|
|
fail "platform-data/gitea-db-credentials username must be gitea"
|
|
[[ "$gitea_db_username_b64" == "$expected_db_username_b64" ]] || \
|
|
fail "gitea/gitea-db-credentials username must be gitea"
|
|
[[ "$platform_db_username_b64" == "$gitea_db_username_b64" ]] || \
|
|
fail "the two DB Secret usernames do not match"
|
|
[[ "$platform_db_password_b64" == "$gitea_db_password_b64" ]] || \
|
|
fail "the two DB Secret passwords do not match"
|
|
|
|
reload_label="$(
|
|
kubectl --namespace platform-data get secret gitea-db-credentials \
|
|
--output='jsonpath={.metadata.labels.cnpg\.io/reload}'
|
|
)"
|
|
if [[ "$reload_label" != "true" ]]; then
|
|
kubectl --namespace platform-data label secret gitea-db-credentials \
|
|
cnpg.io/reload=true --overwrite
|
|
printf 'Repaired cnpg.io/reload=true without changing Secret data.\n'
|
|
fi
|
|
|
|
unset platform_db_username_b64 platform_db_password_b64
|
|
unset gitea_db_username_b64 gitea_db_password_b64 expected_db_username_b64
|
|
printf 'Existing Phase 1 Secret contracts are valid and were reused unchanged.\n'
|
|
printf 'No credential rotation was performed.\n'
|
|
exit 0
|
|
fi
|
|
|
|
[[ -t 0 ]] || fail "an interactive terminal is required for initial Secret creation"
|
|
|
|
read_secret_twice() {
|
|
local prompt="$1"
|
|
local first
|
|
local second
|
|
|
|
read -r -s -p "${prompt}: " first
|
|
printf '\n' >&2
|
|
read -r -s -p "Confirm ${prompt}: " second
|
|
printf '\n' >&2
|
|
|
|
[[ "$first" == "$second" ]] || fail "the two values do not match"
|
|
(( ${#first} >= 16 )) || fail "use at least 16 characters"
|
|
printf '%s' "$first"
|
|
}
|
|
|
|
read -r -p 'Gitea administrator username: ' admin_username
|
|
[[ "$admin_username" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || \
|
|
fail "administrator username contains unsupported characters"
|
|
|
|
db_password="$(read_secret_twice 'Gitea database password')"
|
|
admin_password="$(read_secret_twice 'Gitea administrator password')"
|
|
|
|
secret_temp_dir="$(mktemp -d "${TMPDIR:-/tmp}/platform-phase1-secrets.XXXXXX")"
|
|
db_user_file="${secret_temp_dir}/db-username"
|
|
db_password_file="${secret_temp_dir}/db-password"
|
|
admin_user_file="${secret_temp_dir}/admin-username"
|
|
admin_password_file="${secret_temp_dir}/admin-password"
|
|
|
|
cleanup() {
|
|
unset db_password admin_password
|
|
rm -f -- "$db_user_file" "$db_password_file" "$admin_user_file" "$admin_password_file"
|
|
rmdir -- "$secret_temp_dir" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
trap 'exit 130' INT
|
|
trap 'exit 143' TERM
|
|
|
|
umask 077
|
|
printf '%s' "$DB_USERNAME" >"$db_user_file"
|
|
printf '%s' "$db_password" >"$db_password_file"
|
|
printf '%s' "$admin_username" >"$admin_user_file"
|
|
printf '%s' "$admin_password" >"$admin_password_file"
|
|
unset db_password admin_password
|
|
|
|
printf '\nThe script will create exactly three Secrets. Type %s to continue: ' "$REQUIRED_CONFIRMATION"
|
|
read -r confirmation
|
|
[[ "$confirmation" == "$REQUIRED_CONFIRMATION" ]] || fail "cancelled"
|
|
|
|
apply_basic_auth_secret() {
|
|
local namespace="$1"
|
|
local name="$2"
|
|
local username_file="$3"
|
|
local password_file="$4"
|
|
|
|
kubectl --namespace "$namespace" create secret generic "$name" \
|
|
--type=kubernetes.io/basic-auth \
|
|
--from-file="username=${username_file}" \
|
|
--from-file="password=${password_file}" \
|
|
--dry-run=client \
|
|
--output=yaml \
|
|
| kubectl apply --filename=-
|
|
}
|
|
|
|
apply_basic_auth_secret \
|
|
platform-data gitea-db-credentials "$db_user_file" "$db_password_file"
|
|
kubectl --namespace platform-data label secret gitea-db-credentials \
|
|
cnpg.io/reload=true --overwrite
|
|
|
|
apply_basic_auth_secret \
|
|
gitea gitea-db-credentials "$db_user_file" "$db_password_file"
|
|
apply_basic_auth_secret \
|
|
gitea gitea-admin "$admin_user_file" "$admin_password_file"
|
|
|
|
printf 'Initial Phase 1 Secret contracts were created. No values were written to the repository.\n'
|
|
printf 'This script does not perform credential rotation.\n'
|