#!/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