332 lines
9.8 KiB
Bash
Executable File
332 lines
9.8 KiB
Bash
Executable File
#!/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
|