#!/usr/bin/env bash # Provision the docker-registry MinIO dependency after the Tenant is ready. # # The registry credential is generated by MinIO as an access key with an inline, # bucket-scoped policy. The generated secret is captured once and written to # Vault through stdin. Root and application credentials are never passed as # command-line arguments. # # Required env: # REPO_ROOT # ENV_NAME # # Optional env: # DOCKER_REGISTRY_BUCKET default: docker-registry # PF_LOCAL_PORT default: 9900 # MINIO_NAMESPACE default: mnt # MINIO_SERVICE default: minio # MINIO_SCHEME lab default: http; other envs: https # MINIO_SERVICE_PORT lab default: 80; other envs: 443 # MINIO_TLS_INSECURE default: no # ALLOW_INSECURE_MINIO_TLS required when non-lab explicitly uses insecure TLS 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}" : "${PF_LOCAL_PORT:=9900}" : "${MINIO_NAMESPACE:=mnt}" : "${MINIO_SERVICE:=minio}" ENV_NAME="${ENV_NAME:-}" MC_ALIAS="minio-provision" REGISTRY_CHECK_ALIAS="registry-check" MC_CONFIG_DIR="" PF_PID="" CREATED_ACCESS_KEY="" ACCESS_KEY_PERSISTED="no" ROOT_USER="" ROOT_PASSWORD="" POLICY_FILE="" MC_GLOBAL_ARGS=() resolve_transport_defaults() { case "$ENV_NAME" in lab) : "${MINIO_SCHEME:=http}" : "${MINIO_SERVICE_PORT:=80}" : "${MINIO_TLS_INSECURE:=no}" ;; *) : "${MINIO_SCHEME:=https}" : "${MINIO_SERVICE_PORT:=443}" : "${MINIO_TLS_INSECURE:=no}" ;; esac case "$MINIO_SCHEME" in http | https) ;; *) die "MINIO_SCHEME은 http 또는 https만 허용됩니다: $MINIO_SCHEME" ;; esac case "$MINIO_TLS_INSECURE" in yes | no) ;; *) die "MINIO_TLS_INSECURE는 yes 또는 no만 허용됩니다." ;; esac if [[ "$ENV_NAME" != "lab" && "$MINIO_TLS_INSECURE" == "yes" && "${ALLOW_INSECURE_MINIO_TLS:-}" != "yes" ]]; then die "lab 외 환경에서 TLS 검증 비활성화는 ALLOW_INSECURE_MINIO_TLS=yes 가 추가로 필요합니다." fi } 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 } mc_cmd() { mc "${MC_GLOBAL_ARGS[@]}" "$@" } cleanup_created_access_key() { if [[ -z "${CREATED_ACCESS_KEY:-}" || "$ACCESS_KEY_PERSISTED" == "yes" ]]; then return 0 fi warn "Vault 기록 전 실패: 이번 실행에서 만든 MinIO access key 정리" mc_cmd admin accesskey rm "$MC_ALIAS" "$CREATED_ACCESS_KEY" >/dev/null 2>&1 \ || warn "생성한 access key 자동 정리 실패 — MinIO IAM을 점검하세요." } 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 local curl_args=(-fsS --max-time 2) if [[ "$MINIO_SCHEME" == "https" && "$MINIO_TLS_INSECURE" == "yes" ]]; then curl_args+=(-k) fi retry 15 1 curl "${curl_args[@]}" \ "${MINIO_SCHEME}://127.0.0.1:${PF_LOCAL_PORT}/minio/health/live" \ -o /dev/null log "MinIO health endpoint 응답 확인" } read_root_credentials() { 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' )" unset env_blob [[ -n "$ROOT_USER" && -n "$ROOT_PASSWORD" ]] \ || die "MinIO root 자격증명 파싱 실패 — secret/minio/tenant-env 확인" } init_mc_config() { MC_CONFIG_DIR="$(mktemp -d -t project-infra-mc.XXXXXX)" chmod 700 "$MC_CONFIG_DIR" trap_cleanup_path "$MC_CONFIG_DIR" MC_GLOBAL_ARGS=(--config-dir "$MC_CONFIG_DIR") if [[ "$MINIO_SCHEME" == "https" && "$MINIO_TLS_INSECURE" == "yes" ]]; then MC_GLOBAL_ARGS+=(--insecure) fi mc_cmd admin accesskey create --help >/dev/null \ || die "현재 mc는 'admin accesskey create'를 지원하지 않습니다." } import_mc_alias() { local alias_name="$1" local endpoint="$2" local access_key="$3" local secret_key="$4" # NUL-delimited stdin keeps both credentials out of jq and mc argv. printf '%s\0%s\0%s\0' "$endpoint" "$access_key" "$secret_key" \ | jq -Rs ' split("\u0000") | { url: .[0], accessKey: .[1], secretKey: .[2], api: "s3v4", path: "auto" } ' \ | mc_cmd alias import "$alias_name" >/dev/null } configure_root_alias() { local endpoint="${MINIO_SCHEME}://127.0.0.1:${PF_LOCAL_PORT}" import_mc_alias "$MC_ALIAS" "$endpoint" "$ROOT_USER" "$ROOT_PASSWORD" unset ROOT_USER ROOT_PASSWORD } ensure_bucket() { log "MinIO bucket 보장 (${DOCKER_REGISTRY_BUCKET})" mc_cmd mb --ignore-existing "${MC_ALIAS}/${DOCKER_REGISTRY_BUCKET}" >/dev/null } write_inline_policy() { POLICY_FILE="$MC_CONFIG_DIR/docker-registry-policy.json" jq -nc --arg bucket "$DOCKER_REGISTRY_BUCKET" '{ Version: "2012-10-17", Statement: [ { Effect: "Allow", Action: [ "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation" ], Resource: ["arn:aws:s3:::\($bucket)"] }, { Effect: "Allow", Action: [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListMultipartUploadParts", "s3:AbortMultipartUpload" ], Resource: ["arn:aws:s3:::\($bucket)/*"] } ] }' >"$POLICY_FILE" chmod 600 "$POLICY_FILE" } read_vault_registry_field() { local field="$1" vault_exec kv get -format=json secret/docker-registry/minio \ | jq -er --arg field "$field" '.data.data[$field]' } verify_registry_credentials() { local access_key="$1" local secret_key="$2" local endpoint="${MINIO_SCHEME}://127.0.0.1:${PF_LOCAL_PORT}" import_mc_alias "$REGISTRY_CHECK_ALIAS" "$endpoint" "$access_key" "$secret_key" mc_cmd stat "${REGISTRY_CHECK_ALIAS}/${DOCKER_REGISTRY_BUCKET}" >/dev/null \ || die "Vault의 docker-registry MinIO credential 검증 실패" } use_existing_access_key() { vault_kv_exists "docker-registry/minio" || return 1 local access_key secret_key access_key="$(read_vault_registry_field access_key)" \ || die "secret/docker-registry/minio.access_key 읽기 실패" secret_key="$(read_vault_registry_field secret_key)" \ || die "secret/docker-registry/minio.secret_key 읽기 실패" if mc_cmd admin accesskey info "$MC_ALIAS" "$access_key" >/dev/null 2>&1; then verify_registry_credentials "$access_key" "$secret_key" unset access_key secret_key log "기존 docker-registry MinIO access key 검증 완료" return 0 fi if mc_cmd admin user info "$MC_ALIAS" "$access_key" >/dev/null 2>&1; then unset access_key secret_key die "기존 Vault 값은 legacy MinIO user를 가리킵니다. 승인된 credential rotation 절차로 먼저 전환하세요." fi unset access_key secret_key die "Vault에는 registry credential이 있지만 대응하는 MinIO access key가 없습니다. 자동 재생성하지 않습니다." } persist_generated_access_key() { local access_key="$1" local secret_key="$2" if ! printf '%s\0%s\0' "$access_key" "$secret_key" \ | jq -Rs ' split("\u0000") | { access_key: .[0], secret_key: .[1] } ' \ | vault_exec kv put secret/docker-registry/minio - >/dev/null; then die "생성된 docker-registry credential의 Vault 기록 실패" fi ACCESS_KEY_PERSISTED="yes" } create_access_key() { log "bucket-scoped docker-registry MinIO access key 생성" local creation_json access_key secret_key creation_json="$( mc_cmd --json admin accesskey create "$MC_ALIAS" \ --name docker-registry \ --description "docker-registry bucket access" \ --policy "$POLICY_FILE" )" || die "MinIO access key 생성 실패" access_key="$( jq -ser 'map(select(.accessKey? or .access_key?)) | last | .accessKey // .access_key' \ <<<"$creation_json" )" || die "생성된 MinIO access key 응답 파싱 실패" secret_key="$( jq -ser 'map(select(.secretKey? or .secret_key?)) | last | .secretKey // .secret_key' \ <<<"$creation_json" )" || die "생성된 MinIO secret key 응답 파싱 실패" unset creation_json [[ -n "$access_key" && -n "$secret_key" ]] \ || die "MinIO가 생성한 access key 응답이 비어 있습니다." CREATED_ACCESS_KEY="$access_key" verify_registry_credentials "$access_key" "$secret_key" persist_generated_access_key "$access_key" "$secret_key" unset access_key secret_key CREATED_ACCESS_KEY log "생성된 docker-registry credential을 Vault에 기록" } main() { require_cmd kubectl jq mc curl sed require_env REPO_ROOT ENV_NAME resolve_transport_defaults 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 init_mc_config configure_root_alias trap_cleanup_fn cleanup_created_access_key ensure_bucket write_inline_policy use_existing_access_key || create_access_key log "minio-provision-registry 태스크 완료" } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi