init: 폴더구조 설계 및 인프라 설계
This commit is contained in:
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install (or upgrade) the MinIO Operator Helm release.
|
||||
#
|
||||
# MinIO Tenant CR (minio.min.io/v2) 는 이 Operator 가 CRD 를 먼저 등록해야
|
||||
# apply 가능하다. bootstrap.sh 의 Phase 1 이전에 실행된다.
|
||||
#
|
||||
# Operator 는 자체 namespace (기본 minio-operator) 에 배포된다. 애플리케이션
|
||||
# namespace (mnt) 와 분리되므로 Tenant CR 만 mnt 에 있어도 동작한다.
|
||||
#
|
||||
# Optional env:
|
||||
# MINIO_OPERATOR_NAMESPACE — 기본 minio-operator
|
||||
# MINIO_OPERATOR_RELEASE — 기본 minio-operator
|
||||
# MINIO_OPERATOR_VERSION — 기본 7.0.0
|
||||
|
||||
set -Eeuo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=../lib/common.sh
|
||||
. "$SCRIPT_DIR/../lib/common.sh"
|
||||
|
||||
: "${MINIO_OPERATOR_NAMESPACE:=minio-operator}"
|
||||
: "${MINIO_OPERATOR_RELEASE:=minio-operator}"
|
||||
: "${MINIO_OPERATOR_VERSION:=7.0.0}"
|
||||
|
||||
helm_install() {
|
||||
log "helm repo: minio-operator 등록 (idempotent)"
|
||||
helm repo add minio-operator https://operator.min.io >/dev/null 2>&1 || true
|
||||
helm repo update minio-operator >/dev/null
|
||||
|
||||
log "helm upgrade --install ${MINIO_OPERATOR_RELEASE} (v=${MINIO_OPERATOR_VERSION} ns=${MINIO_OPERATOR_NAMESPACE})"
|
||||
helm upgrade --install "$MINIO_OPERATOR_RELEASE" minio-operator/operator \
|
||||
--namespace "$MINIO_OPERATOR_NAMESPACE" \
|
||||
--create-namespace \
|
||||
--version "$MINIO_OPERATOR_VERSION" \
|
||||
--wait \
|
||||
--atomic \
|
||||
--timeout 5m
|
||||
}
|
||||
|
||||
wait_ready() {
|
||||
log "MinIO Operator Pod Ready 확인"
|
||||
kubectl -n "$MINIO_OPERATOR_NAMESPACE" wait --for=condition=Ready \
|
||||
pod -l "app.kubernetes.io/name=operator" \
|
||||
--timeout=120s
|
||||
|
||||
log "Tenant CRD 등록 확인"
|
||||
kubectl get crd tenants.minio.min.io >/dev/null \
|
||||
|| die "Tenant CRD 가 여전히 없음. Operator 설치 로그 확인."
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmd helm kubectl
|
||||
helm_install
|
||||
wait_ready
|
||||
log "minio-operator-install 태스크 완료"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env bash
|
||||
# MinIO Tenant 기동 후 docker-registry 용 bucket / 서비스 user / policy 를
|
||||
# 프로비저닝.
|
||||
#
|
||||
# 전제:
|
||||
# - MinIO Tenant Pod 가 Ready (VSO 가 minio-tenant-env Secret 을 이미 동기화)
|
||||
# - Vault 가 init + unseal 완료
|
||||
# - vault-seed 가 다음 경로를 seed 완료:
|
||||
# secret/minio/tenant-env (root 계정, config.env 포맷)
|
||||
# secret/docker-registry/minio (access_key / secret_key)
|
||||
#
|
||||
# 수행 순서:
|
||||
# 1. Vault 에서 MinIO root 자격증명 + registry 전용 AK/SK 읽기
|
||||
# 2. kubectl port-forward 로 127.0.0.1 → MinIO Service 터널 생성
|
||||
# 3. mc alias 등록 → bucket 생성 → policy 작성 → user 생성 → policy 부착
|
||||
#
|
||||
# idempotent: 이미 있는 bucket/user/policy 는 건너뜀 (회전은 별도 절차).
|
||||
#
|
||||
# Required env:
|
||||
# REPO_ROOT
|
||||
#
|
||||
# Optional env:
|
||||
# DOCKER_REGISTRY_BUCKET 기본 docker-registry
|
||||
# DOCKER_REGISTRY_POLICY 기본 docker-registry-rw
|
||||
# PF_LOCAL_PORT 기본 9900 (local port-forward)
|
||||
# MINIO_SERVICE_PORT 기본 443 (Service 쪽 포트 — autoCert HTTPS)
|
||||
|
||||
set -Eeuo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=../lib/common.sh
|
||||
. "$SCRIPT_DIR/../lib/common.sh"
|
||||
# shellcheck source=../lib/vault.sh
|
||||
. "$SCRIPT_DIR/../lib/vault.sh"
|
||||
|
||||
: "${DOCKER_REGISTRY_BUCKET:=docker-registry}"
|
||||
: "${DOCKER_REGISTRY_POLICY:=docker-registry-rw}"
|
||||
: "${PF_LOCAL_PORT:=9900}"
|
||||
: "${MINIO_SERVICE_PORT:=443}"
|
||||
: "${MINIO_NAMESPACE:=mnt}"
|
||||
: "${MINIO_SERVICE:=minio}"
|
||||
|
||||
MC_ALIAS="minio-prov"
|
||||
PF_PID=""
|
||||
|
||||
cleanup_port_forward() {
|
||||
if [[ -n "${PF_PID:-}" ]] && kill -0 "$PF_PID" 2>/dev/null; then
|
||||
kill "$PF_PID" 2>/dev/null || true
|
||||
wait "$PF_PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
wait_minio_ready() {
|
||||
log "MinIO Tenant Pod Ready 대기"
|
||||
retry 30 5 kubectl -n "$MINIO_NAMESPACE" wait --for=condition=Ready --timeout=10s \
|
||||
pod -l "v1.min.io/tenant=$MINIO_SERVICE"
|
||||
}
|
||||
|
||||
start_port_forward() {
|
||||
log "port-forward 터널 생성 (127.0.0.1:${PF_LOCAL_PORT} → svc/${MINIO_SERVICE}:${MINIO_SERVICE_PORT})"
|
||||
kubectl -n "$MINIO_NAMESPACE" port-forward \
|
||||
"svc/${MINIO_SERVICE}" "${PF_LOCAL_PORT}:${MINIO_SERVICE_PORT}" \
|
||||
>/dev/null 2>&1 &
|
||||
PF_PID=$!
|
||||
trap_cleanup_fn cleanup_port_forward
|
||||
|
||||
retry 15 1 curl -ks --max-time 2 \
|
||||
"https://127.0.0.1:${PF_LOCAL_PORT}/minio/health/live" -o /dev/null
|
||||
log " 터널 응답 OK"
|
||||
}
|
||||
|
||||
read_root_credentials() {
|
||||
# secret/minio/tenant-env 는 "config.env" 필드 하나에 쉘 export 문 2 줄이
|
||||
# 들어 있다. 파싱해서 ROOT_USER / ROOT_PASSWORD 전역에 적재.
|
||||
local env_blob
|
||||
env_blob="$(vault_exec kv get -format=json secret/minio/tenant-env \
|
||||
| jq -r '.data.data["config.env"]')"
|
||||
ROOT_USER="$(printf '%s\n' "$env_blob" \
|
||||
| sed -n 's/^export MINIO_ROOT_USER="\(.*\)"$/\1/p')"
|
||||
ROOT_PASSWORD="$(printf '%s\n' "$env_blob" \
|
||||
| sed -n 's/^export MINIO_ROOT_PASSWORD="\(.*\)"$/\1/p')"
|
||||
[[ -n "$ROOT_USER" && -n "$ROOT_PASSWORD" ]] \
|
||||
|| die "MinIO root 자격증명 파싱 실패 — secret/minio/tenant-env 확인"
|
||||
}
|
||||
|
||||
read_registry_credentials() {
|
||||
local json
|
||||
json="$(vault_exec kv get -format=json secret/docker-registry/minio)"
|
||||
REG_AK="$(printf '%s' "$json" | jq -r '.data.data.access_key')"
|
||||
REG_SK="$(printf '%s' "$json" | jq -r '.data.data.secret_key')"
|
||||
[[ -n "$REG_AK" && -n "$REG_SK" && "$REG_AK" != "null" && "$REG_SK" != "null" ]] \
|
||||
|| die "docker-registry 자격증명 읽기 실패 — secret/docker-registry/minio 확인"
|
||||
}
|
||||
|
||||
configure_mc_alias() {
|
||||
log "mc alias 등록 (alias=${MC_ALIAS})"
|
||||
mc --insecure alias set "$MC_ALIAS" \
|
||||
"https://127.0.0.1:${PF_LOCAL_PORT}" \
|
||||
"$ROOT_USER" "$ROOT_PASSWORD" >/dev/null
|
||||
}
|
||||
|
||||
ensure_bucket() {
|
||||
log "bucket 보장 (${DOCKER_REGISTRY_BUCKET})"
|
||||
mc --insecure mb --ignore-existing "${MC_ALIAS}/${DOCKER_REGISTRY_BUCKET}" >/dev/null
|
||||
}
|
||||
|
||||
ensure_policy() {
|
||||
log "policy 작성 (${DOCKER_REGISTRY_POLICY}) — bucket-scoped read/write"
|
||||
local policy_file policy_json
|
||||
policy_file="$(mktemp)"
|
||||
trap_cleanup_path "$policy_file"
|
||||
policy_json="$(jq -n --arg b "$DOCKER_REGISTRY_BUCKET" '{
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"s3:ListBucket",
|
||||
"s3:ListBucketMultipartUploads",
|
||||
"s3:GetBucketLocation"
|
||||
],
|
||||
Resource: ["arn:aws:s3:::\($b)"]
|
||||
},
|
||||
{
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
"s3:DeleteObject",
|
||||
"s3:ListMultipartUploadParts",
|
||||
"s3:AbortMultipartUpload"
|
||||
],
|
||||
Resource: ["arn:aws:s3:::\($b)/*"]
|
||||
}
|
||||
]
|
||||
}')"
|
||||
printf '%s' "$policy_json" > "$policy_file"
|
||||
chmod 600 "$policy_file"
|
||||
# `policy create` 는 idempotent: 이미 있으면 덮어쓴다.
|
||||
mc --insecure admin policy create "$MC_ALIAS" \
|
||||
"$DOCKER_REGISTRY_POLICY" "$policy_file" >/dev/null
|
||||
}
|
||||
|
||||
ensure_user() {
|
||||
if mc --insecure admin user info "$MC_ALIAS" "$REG_AK" >/dev/null 2>&1; then
|
||||
log "user '${REG_AK}' 이미 존재 — 재생성 skip (secret key 회전은 별도 절차)"
|
||||
return 0
|
||||
fi
|
||||
log "user '${REG_AK}' 생성"
|
||||
mc --insecure admin user add "$MC_ALIAS" "$REG_AK" "$REG_SK" >/dev/null
|
||||
}
|
||||
|
||||
attach_policy() {
|
||||
log "policy '${DOCKER_REGISTRY_POLICY}' → user '${REG_AK}' 부착"
|
||||
# 이미 부착돼 있으면 mc 가 exit 1 을 반환. 우리는 idempotent 를 의도하므로
|
||||
# stderr 를 소비해서 noise 만 판별한 뒤 무시한다.
|
||||
local out rc=0
|
||||
out="$(mc --insecure admin policy attach "$MC_ALIAS" \
|
||||
"$DOCKER_REGISTRY_POLICY" --user "$REG_AK" 2>&1)" || rc=$?
|
||||
if (( rc != 0 )) && ! grep -qi "already" <<<"$out"; then
|
||||
err " policy attach 실패: $out"
|
||||
return "$rc"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmd kubectl jq mc curl
|
||||
require_env REPO_ROOT
|
||||
|
||||
local keys_file="${VAULT_KEYS_FILE:-$REPO_ROOT/vault-init-keys.json}"
|
||||
vault_login_root_from_keyfile "$keys_file"
|
||||
|
||||
wait_minio_ready
|
||||
start_port_forward
|
||||
|
||||
read_root_credentials
|
||||
read_registry_credentials
|
||||
|
||||
configure_mc_alias
|
||||
ensure_bucket
|
||||
ensure_policy
|
||||
ensure_user
|
||||
attach_policy
|
||||
|
||||
log "minio-provision-registry 태스크 완료"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Executable
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
# Initialize Vault (Shamir 5-of-3) and bootstrap the Kubernetes auth method,
|
||||
# KV v2 secrets engine, and the vault-secrets-operator policy/role.
|
||||
#
|
||||
# Idempotent: 이미 초기화된 Vault 에서는 init 을 skip 하지만 unseal 과
|
||||
# post-init 구성은 매번 재적용해서 bootstrap.sh 에서 여러 번 호출되어도 안전.
|
||||
#
|
||||
# Required env:
|
||||
# REPO_ROOT — repository root
|
||||
#
|
||||
# Optional env:
|
||||
# VAULT_NAMESPACE / VAULT_POD / VAULT_KEYS_FILE — see lib/vault.sh
|
||||
# ENV_NAME — dev|staging|prod (bootstrap.sh 가 주입). prod 일 때 기본 경로
|
||||
# (repo working tree) 사용을 차단한다 — VAULT_KEYS_FILE 를 명시
|
||||
# 해서 repo 바깥의 안전한 경로로 써야 한다.
|
||||
|
||||
set -Eeuo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=../lib/common.sh
|
||||
. "$SCRIPT_DIR/../lib/common.sh"
|
||||
# shellcheck source=../lib/vault.sh
|
||||
. "$SCRIPT_DIR/../lib/vault.sh"
|
||||
|
||||
KEY_SHARES=5
|
||||
KEY_THRESHOLD=3
|
||||
|
||||
resolve_keys_file() {
|
||||
DEFAULT_KEYS_FILE="$REPO_ROOT/vault-init-keys.json"
|
||||
KEYS_FILE="${VAULT_KEYS_FILE:-$DEFAULT_KEYS_FILE}"
|
||||
# prod 에서 repo 내부 평문 저장 차단 — root token / unseal key 가 repo
|
||||
# working tree 에 남으면 accidental commit / backup / IDE 인덱싱 경로로 유출.
|
||||
if [[ "${ENV_NAME:-}" == "prod" && "$KEYS_FILE" == "$DEFAULT_KEYS_FILE" ]]; then
|
||||
die "env=prod 에서는 VAULT_KEYS_FILE 를 repo 바깥 경로로 반드시 지정해야 합니다. (예: VAULT_KEYS_FILE=/run/secrets/vault-keys.json 또는 sops age 로 암호화)"
|
||||
fi
|
||||
}
|
||||
|
||||
initialize_if_needed() {
|
||||
if vault_is_initialized; then
|
||||
log "Vault 는 이미 초기화되어 있습니다. 초기화 단계를 건너뜁니다."
|
||||
return 0
|
||||
fi
|
||||
log "Vault 초기화 중 (shares=${KEY_SHARES}, threshold=${KEY_THRESHOLD})..."
|
||||
local tmp_out
|
||||
tmp_out="$(mktemp)"
|
||||
trap_cleanup_path "$tmp_out"
|
||||
|
||||
vault_exec operator init \
|
||||
-key-shares="$KEY_SHARES" \
|
||||
-key-threshold="$KEY_THRESHOLD" \
|
||||
-format=json > "$tmp_out"
|
||||
|
||||
chmod 600 "$tmp_out"
|
||||
mv "$tmp_out" "$KEYS_FILE"
|
||||
chmod 600 "$KEYS_FILE"
|
||||
log "unseal keys + root token 저장: $KEYS_FILE (권한 0600)"
|
||||
}
|
||||
|
||||
unseal_if_needed() {
|
||||
if vault_is_sealed; then
|
||||
log "Vault sealed 상태, unseal 진행"
|
||||
vault_unseal_from_keyfile "$KEYS_FILE"
|
||||
else
|
||||
log "Vault unsealed 상태"
|
||||
fi
|
||||
}
|
||||
|
||||
enable_kv_v2() {
|
||||
if vault_secrets_engine_enabled "secret"; then
|
||||
log "secrets engine 'secret/' 이미 활성화됨"
|
||||
return 0
|
||||
fi
|
||||
log "KV v2 secrets engine 을 secret/ 에 활성화"
|
||||
vault_exec secrets enable -path=secret kv-v2
|
||||
}
|
||||
|
||||
enable_k8s_auth() {
|
||||
if vault_auth_method_enabled "kubernetes"; then
|
||||
log "auth method 'kubernetes/' 이미 활성화됨"
|
||||
else
|
||||
log "Kubernetes auth method 활성화"
|
||||
vault_exec auth enable kubernetes
|
||||
fi
|
||||
|
||||
log "Kubernetes auth method 설정 (kubernetes_host + ca + token_reviewer_jwt)"
|
||||
vault_exec_sh '
|
||||
vault write auth/kubernetes/config \
|
||||
kubernetes_host="https://kubernetes.default.svc.cluster.local:443" \
|
||||
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
|
||||
token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
' >/dev/null
|
||||
}
|
||||
|
||||
write_policies_and_roles() {
|
||||
log "policy 'vso-auth-platform' 작성 (identity-postgres/*, auth-server/*, keycloak/*, oauth2-proxy/*)"
|
||||
vault_exec_sh 'cat <<"EOF" | vault policy write vso-auth-platform -
|
||||
path "secret/data/identity-postgres/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "secret/data/auth-server/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "secret/data/keycloak/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "secret/data/oauth2-proxy/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
' >/dev/null
|
||||
|
||||
log "policy 'vso-storage' 작성 (minio/*, docker-registry/*)"
|
||||
vault_exec_sh 'cat <<"EOF" | vault policy write vso-storage -
|
||||
path "secret/data/minio/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "secret/data/docker-registry/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
' >/dev/null
|
||||
|
||||
log "k8s auth role 'vso-auth-platform' 작성"
|
||||
vault_exec write auth/kubernetes/role/vso-auth-platform \
|
||||
bound_service_account_names=vault-secrets-operator \
|
||||
bound_service_account_namespaces="$VAULT_NAMESPACE" \
|
||||
audience=vault \
|
||||
policies=vso-auth-platform \
|
||||
ttl=1h >/dev/null
|
||||
|
||||
log "k8s auth role 'vso-storage' 작성"
|
||||
vault_exec write auth/kubernetes/role/vso-storage \
|
||||
bound_service_account_names=vault-secrets-operator \
|
||||
bound_service_account_namespaces="$VAULT_NAMESPACE" \
|
||||
audience=vault \
|
||||
policies=vso-storage \
|
||||
ttl=1h >/dev/null
|
||||
}
|
||||
|
||||
cleanup_legacy() {
|
||||
log "구 policy/role 정리 (존재하면 삭제)"
|
||||
# 이전 단일 policy (세분화 전) 잔재
|
||||
vault_exec policy delete vault-secrets-operator >/dev/null 2>&1 || true
|
||||
vault_exec delete auth/kubernetes/role/vault-secrets-operator >/dev/null 2>&1 || true
|
||||
# Registry auth 제거로 더 이상 사용 안 하는 policy/role
|
||||
vault_exec policy delete vso-registry >/dev/null 2>&1 || true
|
||||
vault_exec delete auth/kubernetes/role/vso-registry >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmd kubectl jq
|
||||
require_env REPO_ROOT
|
||||
|
||||
resolve_keys_file
|
||||
initialize_if_needed
|
||||
unseal_if_needed
|
||||
vault_login_root_from_keyfile "$KEYS_FILE"
|
||||
enable_kv_v2
|
||||
enable_k8s_auth
|
||||
write_policies_and_roles
|
||||
cleanup_legacy
|
||||
log "vault-init 태스크 완료"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Executable
+286
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env bash
|
||||
# Seed application secrets into Vault KV v2.
|
||||
#
|
||||
# 이미 Vault KV 에 있는 경로는 skip — 운영자가 회전한 값을 절대 덮어쓰지 않는다.
|
||||
# 아직 없는 경로에 대해 다음 순으로 값을 결정:
|
||||
# 1) 환경 변수에 값이 있으면 사용
|
||||
# 2) TTY 면 read -r -s 로 프롬프트 (확인 재입력 + echo 없음, bash history 안전)
|
||||
# 3) 비대화 + env 비어있고 AUTO_GENERATE=yes 면 openssl rand 로 자동 생성
|
||||
# → 생성된 값은 stdout 에 절대 출력하지 않음. 필요 시 나중에:
|
||||
# vault kv get -field=password secret/<path>
|
||||
#
|
||||
# Vault CLI 의 `vault kv put <path> -` 모드로 **JSON payload 를 stdin** 으로 전달해
|
||||
# 비밀번호가 argv / 프로세스 테이블 / 쉘 히스토리 어디에도 노출되지 않는다.
|
||||
#
|
||||
# Required env:
|
||||
# REPO_ROOT
|
||||
#
|
||||
# Optional env (비면 대화형 입력 or AUTO_GENERATE):
|
||||
# POSTGRES_SUPERUSER_USERNAME 기본 postgres
|
||||
# POSTGRES_SUPERUSER_PASSWORD
|
||||
#
|
||||
# KEYCLOAK_DB_PASSWORD
|
||||
#
|
||||
# AUTH_SERVER_DB_USERNAME 기본 auth_server
|
||||
# AUTH_SERVER_DB_PASSWORD
|
||||
#
|
||||
# KEYCLOAK_ADMIN_USERNAME 기본 admin
|
||||
# KEYCLOAK_ADMIN_PASSWORD
|
||||
#
|
||||
# MINIO_ROOT_USER 기본 minioadmin
|
||||
# MINIO_ROOT_PASSWORD
|
||||
#
|
||||
# AUTH_SERVER_INGRESS_CLIENT_SECRET Keycloak realm client 'auth-server-ingress' 의 secret.
|
||||
# KeycloakRealmImport 가 ${AUTH_SERVER_INGRESS_CLIENT_SECRET}
|
||||
# env 치환으로 가져간다. oauth2-proxy 도 동일 값을 받음.
|
||||
#
|
||||
# OAUTH2_PROXY_COOKIE_SECRET oauth2-proxy session cookie signing/encryption key.
|
||||
#
|
||||
# DOCKER_REGISTRY_MINIO_ACCESSKEY docker-registry 가 MinIO 에 접근할 때 쓸 AK.
|
||||
# 기본값 'docker-registry'. MinIO user 이름이 됨.
|
||||
# DOCKER_REGISTRY_MINIO_SECRETKEY docker-registry MinIO SK (대소문자+숫자 8자 이상).
|
||||
#
|
||||
# DOCKER_REGISTRY_PUSH_USERNAME 외부 Ingress push 용 BasicAuth username.
|
||||
# 기본값 'registry-push'.
|
||||
# DOCKER_REGISTRY_PUSH_PASSWORD 외부 Ingress push 용 BasicAuth password.
|
||||
#
|
||||
# Options:
|
||||
# AUTO_GENERATE=yes 대화형 TTY 가 아니고 env 도 비었을 때 랜덤 값 생성
|
||||
#
|
||||
# NOTE: 비밀번호에 큰따옴표 " 는 사용 금지 (MinIO env-file 포맷이 깨짐).
|
||||
|
||||
set -Eeuo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=../lib/common.sh
|
||||
. "$SCRIPT_DIR/../lib/common.sh"
|
||||
# shellcheck source=../lib/vault.sh
|
||||
. "$SCRIPT_DIR/../lib/vault.sh"
|
||||
|
||||
: "${POSTGRES_SUPERUSER_USERNAME:=postgres}"
|
||||
: "${AUTH_SERVER_DB_USERNAME:=auth_server}"
|
||||
: "${KEYCLOAK_ADMIN_USERNAME:=admin}"
|
||||
: "${MINIO_ROOT_USER:=minioadmin}"
|
||||
: "${DOCKER_REGISTRY_MINIO_ACCESSKEY:=docker-registry}"
|
||||
: "${DOCKER_REGISTRY_PUSH_USERNAME:=registry-push}"
|
||||
|
||||
generate_random_password() {
|
||||
openssl rand -base64 48 | tr -d '/+=' | head -c 24
|
||||
}
|
||||
|
||||
resolve_password() {
|
||||
local var_name="$1"
|
||||
local prompt_label="$2"
|
||||
local value="${!var_name:-}"
|
||||
|
||||
if [[ -n "$value" ]]; then
|
||||
printf '%s' "$value"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -t 0 ]]; then
|
||||
local input confirm
|
||||
while true; do
|
||||
read -r -s -p "${prompt_label}: " input
|
||||
echo >&2
|
||||
if [[ -z "$input" ]]; then
|
||||
err " 비밀번호가 비어있습니다. 다시 입력하세요."
|
||||
continue
|
||||
fi
|
||||
if [[ "$input" == *'"'* ]]; then
|
||||
err " 큰따옴표(\") 는 사용할 수 없습니다 (MinIO env 포맷)."
|
||||
continue
|
||||
fi
|
||||
read -r -s -p "${prompt_label} 한 번 더: " confirm
|
||||
echo >&2
|
||||
if [[ "$input" == "$confirm" ]]; then
|
||||
printf '%s' "$input"
|
||||
return 0
|
||||
fi
|
||||
err " 일치하지 않습니다. 다시."
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ "${AUTO_GENERATE:-}" == "yes" ]]; then
|
||||
local generated
|
||||
generated="$(generate_random_password)"
|
||||
warn " ${var_name}: 자동 생성 (vault kv get 으로 조회 가능)"
|
||||
printf '%s' "$generated"
|
||||
return 0
|
||||
fi
|
||||
|
||||
die "${var_name} 가 비어있고 비대화 환경이며 AUTO_GENERATE=yes 도 아닙니다."
|
||||
}
|
||||
|
||||
seed_if_missing() {
|
||||
local path="$1"
|
||||
if vault_kv_exists "$path"; then
|
||||
log " secret/${path}: 이미 존재 → skip (회전된 값 보호)"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
kv_put_json() {
|
||||
local path="$1"; shift
|
||||
local jq_expr="$1"; shift
|
||||
jq -n "$@" "$jq_expr" \
|
||||
| kubectl exec -i -n "$VAULT_NAMESPACE" "$VAULT_POD" -- \
|
||||
vault kv put "secret/${path}" - >/dev/null
|
||||
}
|
||||
|
||||
vault_kv_get_field() {
|
||||
local path="$1"
|
||||
local field="$2"
|
||||
vault_exec kv get -format=json "secret/${path}" \
|
||||
| jq -r --arg field "$field" '.data.data[$field] // empty'
|
||||
}
|
||||
|
||||
seed_identity_postgres_superuser() {
|
||||
seed_if_missing "identity-postgres/superuser" || return 0
|
||||
local pw
|
||||
pw="$(resolve_password POSTGRES_SUPERUSER_PASSWORD "Postgres superuser 비밀번호")"
|
||||
kv_put_json "identity-postgres/superuser" \
|
||||
'{username: $u, password: $p}' \
|
||||
--arg u "$POSTGRES_SUPERUSER_USERNAME" \
|
||||
--arg p "$pw"
|
||||
log " secret/identity-postgres/superuser 작성"
|
||||
}
|
||||
|
||||
seed_keycloak_db() {
|
||||
seed_if_missing "keycloak/db" || return 0
|
||||
local pw
|
||||
pw="$(resolve_password KEYCLOAK_DB_PASSWORD "Keycloak DB 비밀번호")"
|
||||
kv_put_json "keycloak/db" \
|
||||
'{password: $p}' \
|
||||
--arg p "$pw"
|
||||
log " secret/keycloak/db 작성"
|
||||
}
|
||||
|
||||
seed_auth_server_db() {
|
||||
seed_if_missing "auth-server/db" || return 0
|
||||
local pw
|
||||
pw="$(resolve_password AUTH_SERVER_DB_PASSWORD "auth-server DB 비밀번호")"
|
||||
kv_put_json "auth-server/db" \
|
||||
'{SPRING_DATASOURCE_USERNAME: $u, SPRING_DATASOURCE_PASSWORD: $p}' \
|
||||
--arg u "$AUTH_SERVER_DB_USERNAME" \
|
||||
--arg p "$pw"
|
||||
log " secret/auth-server/db 작성"
|
||||
}
|
||||
|
||||
seed_keycloak_bootstrap_admin() {
|
||||
seed_if_missing "keycloak/bootstrap-admin" || return 0
|
||||
local pw
|
||||
pw="$(resolve_password KEYCLOAK_ADMIN_PASSWORD "Keycloak 관리자 비밀번호")"
|
||||
kv_put_json "keycloak/bootstrap-admin" \
|
||||
'{KEYCLOAK_ADMIN: $u, KEYCLOAK_ADMIN_PASSWORD: $p}' \
|
||||
--arg u "$KEYCLOAK_ADMIN_USERNAME" \
|
||||
--arg p "$pw"
|
||||
log " secret/keycloak/bootstrap-admin 작성"
|
||||
}
|
||||
|
||||
seed_minio_tenant_env() {
|
||||
seed_if_missing "minio/tenant-env" || return 0
|
||||
local pw env_content
|
||||
pw="$(resolve_password MINIO_ROOT_PASSWORD "MinIO 루트 비밀번호")"
|
||||
env_content=$(
|
||||
printf 'export MINIO_ROOT_USER="%s"\nexport MINIO_ROOT_PASSWORD="%s"\n' \
|
||||
"$MINIO_ROOT_USER" "$pw"
|
||||
)
|
||||
kv_put_json "minio/tenant-env" \
|
||||
'{"config.env": $cfg}' \
|
||||
--arg cfg "$env_content"
|
||||
log " secret/minio/tenant-env 작성"
|
||||
}
|
||||
|
||||
# docker-registry 가 MinIO 에 접근할 때 쓸 AK/SK. 이 값으로 bootstrap 후속
|
||||
# phase 에서 MinIO admin user 를 만들고 bucket-scoped policy 를 부착한다.
|
||||
# registry Deployment 는 VSO 가 동기화한 Secret `docker-registry-minio` 에서
|
||||
# 동일 값을 REGISTRY_STORAGE_S3_ACCESSKEY / SECRETKEY 로 읽는다.
|
||||
seed_docker_registry_minio() {
|
||||
seed_if_missing "docker-registry/minio" || return 0
|
||||
local sk
|
||||
sk="$(resolve_password DOCKER_REGISTRY_MINIO_SECRETKEY "docker-registry MinIO secret key")"
|
||||
kv_put_json "docker-registry/minio" \
|
||||
'{access_key: $ak, secret_key: $sk}' \
|
||||
--arg ak "$DOCKER_REGISTRY_MINIO_ACCESSKEY" \
|
||||
--arg sk "$sk"
|
||||
log " secret/docker-registry/minio 작성 (access_key=${DOCKER_REGISTRY_MINIO_ACCESSKEY})"
|
||||
}
|
||||
|
||||
# 외부에서 registry.project.com 으로 push 할 때 Traefik BasicAuth 가 검증할
|
||||
# htpasswd 라인을 저장한다. Registry 자체 auth 는 켜지지 않고, 외부 Ingress
|
||||
# 경계에서만 인증한다.
|
||||
seed_docker_registry_basic_auth() {
|
||||
seed_if_missing "docker-registry/basic-auth" || return 0
|
||||
local pw hash users
|
||||
pw="$(resolve_password DOCKER_REGISTRY_PUSH_PASSWORD "docker-registry 외부 push 비밀번호")"
|
||||
hash="$(openssl passwd -apr1 "$pw")"
|
||||
users="${DOCKER_REGISTRY_PUSH_USERNAME}:${hash}"
|
||||
kv_put_json "docker-registry/basic-auth" \
|
||||
'{username: $username, password: $password, users: $users}' \
|
||||
--arg username "$DOCKER_REGISTRY_PUSH_USERNAME" \
|
||||
--arg password "$pw" \
|
||||
--arg users "$users"
|
||||
log " secret/docker-registry/basic-auth 작성 (username=${DOCKER_REGISTRY_PUSH_USERNAME})"
|
||||
}
|
||||
|
||||
# Keycloak realm import 가 client secret 을 ${AUTH_SERVER_INGRESS_CLIENT_SECRET}
|
||||
# env 치환으로 요구한다. oauth2-proxy 는 동일 값을 /etc/oauth2-proxy-secrets/
|
||||
# client-secret 파일로 읽는다. 두 쪽이 같은 값을 써야 OIDC client 인증이 맞는다.
|
||||
seed_keycloak_client_auth_server_ingress() {
|
||||
seed_if_missing "keycloak/clients/auth-server-ingress" || return 0
|
||||
local pw
|
||||
pw="$(resolve_password AUTH_SERVER_INGRESS_CLIENT_SECRET \
|
||||
"Keycloak client 'auth-server-ingress' secret")"
|
||||
AUTH_SERVER_INGRESS_CLIENT_SECRET="$pw"
|
||||
kv_put_json "keycloak/clients/auth-server-ingress" \
|
||||
'{client_secret: $p}' \
|
||||
--arg p "$pw"
|
||||
log " secret/keycloak/clients/auth-server-ingress 작성"
|
||||
}
|
||||
|
||||
seed_oauth2_proxy_forward_auth() {
|
||||
seed_if_missing "oauth2-proxy/forward-auth" || return 0
|
||||
local client_secret cookie_secret
|
||||
if [[ -n "${AUTH_SERVER_INGRESS_CLIENT_SECRET:-}" ]]; then
|
||||
client_secret="$AUTH_SERVER_INGRESS_CLIENT_SECRET"
|
||||
elif vault_kv_exists "keycloak/clients/auth-server-ingress"; then
|
||||
client_secret="$(vault_kv_get_field "keycloak/clients/auth-server-ingress" "client_secret")"
|
||||
else
|
||||
client_secret="$(resolve_password AUTH_SERVER_INGRESS_CLIENT_SECRET \
|
||||
"Keycloak client 'auth-server-ingress' secret")"
|
||||
fi
|
||||
[[ -n "$client_secret" ]] || die "auth-server-ingress client secret 을 확인할 수 없습니다."
|
||||
cookie_secret="$(resolve_password OAUTH2_PROXY_COOKIE_SECRET \
|
||||
"oauth2-proxy cookie secret")"
|
||||
kv_put_json "oauth2-proxy/forward-auth" \
|
||||
'{client_secret: $client_secret, cookie_secret: $cookie_secret}' \
|
||||
--arg client_secret "$client_secret" \
|
||||
--arg cookie_secret "$cookie_secret"
|
||||
log " secret/oauth2-proxy/forward-auth 작성"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmd kubectl jq openssl
|
||||
require_env REPO_ROOT
|
||||
|
||||
local keys_file="${VAULT_KEYS_FILE:-$REPO_ROOT/vault-init-keys.json}"
|
||||
vault_login_root_from_keyfile "$keys_file"
|
||||
log "앱 시크릿 5 개 seed 시작 (이미 존재하는 경로는 skip)"
|
||||
|
||||
seed_identity_postgres_superuser
|
||||
seed_keycloak_db
|
||||
seed_auth_server_db
|
||||
seed_keycloak_bootstrap_admin
|
||||
seed_minio_tenant_env
|
||||
seed_keycloak_client_auth_server_ingress
|
||||
seed_oauth2_proxy_forward_auth
|
||||
seed_docker_registry_minio
|
||||
seed_docker_registry_basic_auth
|
||||
|
||||
log "vault-seed-apps 태스크 완료"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Executable
+180
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env bash
|
||||
# Configure operator authentication for Vault.
|
||||
#
|
||||
# Adds a `userpass` auth method + `vault-admin` policy + one or more admin users.
|
||||
# Operators use these credentials instead of the root token for day-to-day work.
|
||||
# The root token stays offline and is only used for break-glass / rekey.
|
||||
#
|
||||
# Required env:
|
||||
# REPO_ROOT — repo root (for vault-init-keys.json path)
|
||||
#
|
||||
# Interactive inputs (대화형 TTY 에서 자동 프롬프트):
|
||||
# VAULT_ADMIN_USERNAME — login name (비면 프롬프트)
|
||||
# VAULT_ADMIN_PASSWORD — 초기 비밀번호 (비면 무음 입력 프롬프트)
|
||||
#
|
||||
# Non-interactive 시 (CI 등): 위 두 개를 env var 로 전달해야 함.
|
||||
#
|
||||
# Optional env:
|
||||
# VAULT_ADMIN_TTL — token TTL per login (default: 8h)
|
||||
# VAULT_ADMIN_MAX_TTL — maximum TTL (default: 24h)
|
||||
#
|
||||
# 입력 검증:
|
||||
# - username: 영문/숫자/._- 만 허용, 1..64 자
|
||||
# - TTL/MAX_TTL: 숫자+단위(s|m|h|d), 1..16 자
|
||||
# 이 값들은 Vault API 경로/파라미터에 쓰이는데, 과거 구현은 `sh -c` 문자열에
|
||||
# 직접 보간했다. allowlist 를 통과시키면 쉘 메타문자 주입 경로가 닫히고,
|
||||
# 아래 `vault write` 호출은 `sh -c` 없이 kubectl exec 로 직접 실행한다.
|
||||
|
||||
set -Eeuo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=../lib/common.sh
|
||||
. "$SCRIPT_DIR/../lib/common.sh"
|
||||
# shellcheck source=../lib/vault.sh
|
||||
. "$SCRIPT_DIR/../lib/vault.sh"
|
||||
|
||||
USERNAME_RE='^[A-Za-z0-9._-]{1,64}$'
|
||||
TTL_RE='^[0-9]+[smhd]?$'
|
||||
|
||||
validate_inputs() {
|
||||
[[ "$VAULT_ADMIN_USERNAME" =~ $USERNAME_RE ]] \
|
||||
|| die "VAULT_ADMIN_USERNAME 형식 불일치 (영문/숫자/._- 만 허용, 1..64자): '$VAULT_ADMIN_USERNAME'"
|
||||
[[ "$ADMIN_TTL" =~ $TTL_RE ]] \
|
||||
|| die "VAULT_ADMIN_TTL 형식 불일치 (예: 8h, 3600, 30m): '$ADMIN_TTL'"
|
||||
[[ "$ADMIN_MAX_TTL" =~ $TTL_RE ]] \
|
||||
|| die "VAULT_ADMIN_MAX_TTL 형식 불일치 (예: 24h, 86400): '$ADMIN_MAX_TTL'"
|
||||
(( ${#ADMIN_TTL} <= 16 && ${#ADMIN_MAX_TTL} <= 16 )) \
|
||||
|| die "TTL 값이 너무 깁니다."
|
||||
}
|
||||
|
||||
prompt_username_if_missing() {
|
||||
[[ -n "${VAULT_ADMIN_USERNAME:-}" ]] && return 0
|
||||
if [[ ! -t 0 ]]; then
|
||||
die "VAULT_ADMIN_USERNAME 환경 변수가 비어있습니다 (비대화 환경)."
|
||||
fi
|
||||
read -r -p "운영자 username (e.g. alice): " VAULT_ADMIN_USERNAME
|
||||
[[ -n "$VAULT_ADMIN_USERNAME" ]] || die "username 이 비어있습니다."
|
||||
}
|
||||
|
||||
prompt_password_if_missing() {
|
||||
[[ -n "${VAULT_ADMIN_PASSWORD:-}" ]] && return 0
|
||||
if [[ ! -t 0 ]]; then
|
||||
die "VAULT_ADMIN_PASSWORD 환경 변수가 비어있습니다 (비대화 환경)."
|
||||
fi
|
||||
read -r -s -p "${VAULT_ADMIN_USERNAME} 초기 비밀번호: " VAULT_ADMIN_PASSWORD
|
||||
echo >&2
|
||||
[[ -n "$VAULT_ADMIN_PASSWORD" ]] || die "비밀번호가 비어있습니다."
|
||||
|
||||
local confirm
|
||||
read -r -s -p "비밀번호 한 번 더 입력: " confirm
|
||||
echo >&2
|
||||
[[ "$VAULT_ADMIN_PASSWORD" == "$confirm" ]] \
|
||||
|| die "비밀번호가 일치하지 않습니다."
|
||||
unset confirm
|
||||
}
|
||||
|
||||
enable_userpass() {
|
||||
if vault_auth_method_enabled "userpass"; then
|
||||
log "auth method 'userpass/' 이미 활성화됨"
|
||||
else
|
||||
log "userpass auth method 활성화"
|
||||
vault_exec auth enable userpass
|
||||
fi
|
||||
}
|
||||
|
||||
write_admin_policy() {
|
||||
# sudo capability 는 sys/* 일부 엔드포인트 (seal/audit/generate-root) 때문에 필요.
|
||||
# 권한이 강하므로 인프라 담당자에게만 부여.
|
||||
log "policy 'vault-admin' 작성"
|
||||
vault_exec_sh 'cat <<"EOF" | vault policy write vault-admin -
|
||||
# KV v2 — 전 경로 관리
|
||||
path "secret/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
path "secret/data/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
path "secret/metadata/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
|
||||
# Auth method / identity 관리
|
||||
path "auth/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
||||
}
|
||||
path "identity/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
|
||||
# policy 관리 (자기 자신 포함)
|
||||
path "sys/policies/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
path "sys/policy/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
|
||||
# 상태 / 감사 장치 / mount 관리
|
||||
path "sys/health" { capabilities = ["read"] }
|
||||
path "sys/seal-status" { capabilities = ["read"] }
|
||||
path "sys/mounts" { capabilities = ["read", "list"] }
|
||||
path "sys/mounts/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
||||
}
|
||||
path "sys/audit" { capabilities = ["read", "list"] }
|
||||
path "sys/audit/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "sudo"]
|
||||
}
|
||||
EOF
|
||||
' >/dev/null
|
||||
}
|
||||
|
||||
# 사용자 생성/갱신 — `sh -c` 를 쓰지 않는다.
|
||||
#
|
||||
# 비밀번호는 `vault write -` 의 JSON stdin 으로 전달해 argv / 쉘 히스토리 노출이
|
||||
# 전혀 없다. 나머지 파라미터 (username / TTL) 는 allowlist 를 통과한 값만
|
||||
# kubectl exec argv 로 직접 전달된다 (쉘 해석 없음).
|
||||
create_or_update_user() {
|
||||
log "userpass user '${VAULT_ADMIN_USERNAME}' 생성/갱신 (ttl=${ADMIN_TTL} max_ttl=${ADMIN_MAX_TTL})"
|
||||
jq -n --arg p "$VAULT_ADMIN_PASSWORD" \
|
||||
--arg ttl "$ADMIN_TTL" \
|
||||
--arg max "$ADMIN_MAX_TTL" \
|
||||
'{password:$p, token_policies:"vault-admin", token_ttl:$ttl, token_max_ttl:$max}' \
|
||||
| kubectl exec -i -n "$VAULT_NAMESPACE" "$VAULT_POD" -- \
|
||||
vault write "auth/userpass/users/${VAULT_ADMIN_USERNAME}" - >/dev/null
|
||||
}
|
||||
|
||||
print_next_steps() {
|
||||
log "vault-setup-admin 태스크 완료"
|
||||
log ""
|
||||
log "다음 단계:"
|
||||
log " 1) 운영자는 다음 명령으로 로그인 (root token 사용 중단)"
|
||||
log " vault login -method=userpass username=${VAULT_ADMIN_USERNAME}"
|
||||
log " 2) 각 운영자는 첫 로그인 후 비밀번호를 변경"
|
||||
log " vault write auth/userpass/users/${VAULT_ADMIN_USERNAME}/password password='<new>'"
|
||||
log " 3) root token 은 오프라인 금고로 이동 후 vault-init-keys.json 에서 삭제 검토"
|
||||
log " (재발급은 \"vault operator generate-root -init\" 으로 가능)"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmd kubectl jq
|
||||
require_env REPO_ROOT
|
||||
|
||||
prompt_username_if_missing
|
||||
prompt_password_if_missing
|
||||
|
||||
ADMIN_TTL="${VAULT_ADMIN_TTL:-8h}"
|
||||
ADMIN_MAX_TTL="${VAULT_ADMIN_MAX_TTL:-24h}"
|
||||
validate_inputs
|
||||
|
||||
local keys_file="${VAULT_KEYS_FILE:-$REPO_ROOT/vault-init-keys.json}"
|
||||
vault_login_root_from_keyfile "$keys_file"
|
||||
|
||||
enable_userpass
|
||||
write_admin_policy
|
||||
create_or_update_user
|
||||
print_next_steps
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install (or upgrade) the vault-secrets-operator Helm release.
|
||||
#
|
||||
# Idempotent. 이전 실행에서 failed / pending / uninstalling 상태로 남은 릴리즈
|
||||
# 메타데이터가 있으면 먼저 uninstall 한 뒤 깨끗하게 재설치한다 (Helm 의
|
||||
# "no deployed releases" 문제 회피).
|
||||
#
|
||||
# `--atomic` 은 설치 실패 시 자동 rollback 을 수행하는데, rollback 결과
|
||||
# "no deployed releases" 상태가 돼서 다음 helm upgrade --install 이 실패하는
|
||||
# 연쇄 문제를 낳는다. 이 스크립트는 대신 실패 상태를 명시적으로 감지해
|
||||
# uninstall 로 cleanup 하므로 --atomic 없이 실행한다.
|
||||
#
|
||||
# VSO 는 전용 namespace 에 설치된다 (mnt 에 직접 두면 PSS Restricted 라벨과
|
||||
# 차트 Pod spec 의 불일치로 Pod 생성이 막힘). Controller 는 cluster-scoped RBAC
|
||||
# 를 갖고 있어 mnt 의 VaultAuth/VaultStaticSecret 도 정상 watch 한다.
|
||||
#
|
||||
# Optional env:
|
||||
# VSO_NAMESPACE — target namespace (default: vault-secrets-operator-system)
|
||||
# VSO_RELEASE — helm release name (default: vault-secrets-operator)
|
||||
# VSO_VERSION — chart version pin (default: 0.9.0)
|
||||
# VSO_VALUES_FILE — values file path (default: $REPO_ROOT/k8s/base/plugins/vso/helm/values.yaml)
|
||||
|
||||
set -Eeuo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=../lib/common.sh
|
||||
. "$SCRIPT_DIR/../lib/common.sh"
|
||||
|
||||
: "${VSO_NAMESPACE:=vault-secrets-operator-system}"
|
||||
: "${VSO_RELEASE:=vault-secrets-operator}"
|
||||
: "${VSO_VERSION:=0.9.0}"
|
||||
|
||||
resolve_values_file() {
|
||||
: "${VSO_VALUES_FILE:=$REPO_ROOT/k8s/base/plugins/vso/helm/values.yaml}"
|
||||
[[ -f "$VSO_VALUES_FILE" ]] || die "values.yaml 없음: $VSO_VALUES_FILE"
|
||||
}
|
||||
|
||||
register_repo() {
|
||||
log "helm repo: hashicorp 등록 (idempotent)"
|
||||
helm repo add hashicorp https://helm.releases.hashicorp.com >/dev/null 2>&1 || true
|
||||
helm repo update hashicorp >/dev/null
|
||||
}
|
||||
|
||||
# 기존 릴리즈가 dirty 상태 (failed/pending-*/uninstalling/uninstalled) 면 선제
|
||||
# uninstall 해야 다음 upgrade --install 이 성공한다.
|
||||
cleanup_dirty_release() {
|
||||
local status=""
|
||||
if helm -n "$VSO_NAMESPACE" status "$VSO_RELEASE" -o json >/dev/null 2>&1; then
|
||||
status="$(helm -n "$VSO_NAMESPACE" status "$VSO_RELEASE" -o json \
|
||||
| jq -r '.info.status // "unknown"')"
|
||||
fi
|
||||
|
||||
case "$status" in
|
||||
"")
|
||||
log "기존 Helm 릴리즈 없음 → 신규 install"
|
||||
;;
|
||||
"deployed")
|
||||
log "기존 릴리즈 상태=deployed → upgrade 진행"
|
||||
;;
|
||||
"failed"|"pending-install"|"pending-upgrade"|"pending-rollback"|"uninstalling"|"uninstalled")
|
||||
warn "기존 릴리즈 상태=$status (dirty) → helm uninstall 먼저"
|
||||
helm -n "$VSO_NAMESPACE" uninstall "$VSO_RELEASE" --wait --timeout 5m 2>/dev/null \
|
||||
|| warn " uninstall 중 오류 무시 (이미 부분 정리됐을 수 있음)"
|
||||
;;
|
||||
*)
|
||||
warn "기존 릴리즈 상태=$status (예상 외) — 일단 upgrade 시도"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
install_or_upgrade() {
|
||||
log "helm upgrade --install $VSO_RELEASE (v=$VSO_VERSION ns=$VSO_NAMESPACE)"
|
||||
helm upgrade --install "$VSO_RELEASE" hashicorp/vault-secrets-operator \
|
||||
--namespace "$VSO_NAMESPACE" \
|
||||
--create-namespace \
|
||||
--version "$VSO_VERSION" \
|
||||
--values "$VSO_VALUES_FILE" \
|
||||
--wait \
|
||||
--timeout 5m
|
||||
}
|
||||
|
||||
wait_ready() {
|
||||
log "VSO Pod Ready 확인"
|
||||
kubectl -n "$VSO_NAMESPACE" wait --for=condition=Ready \
|
||||
pod -l "app.kubernetes.io/name=vault-secrets-operator" \
|
||||
--timeout=120s
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmd helm kubectl jq
|
||||
require_env REPO_ROOT
|
||||
|
||||
resolve_values_file
|
||||
register_repo
|
||||
cleanup_dirty_release
|
||||
install_or_upgrade
|
||||
wait_ready
|
||||
log "vso-install 태스크 완료"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Reference in New Issue
Block a user