Skip to content

Instantly share code, notes, and snippets.

@ninejuan
Created April 5, 2026 07:55
Show Gist options
  • Select an option

  • Save ninejuan/7431cff155d42ef56fd4d86454eb8316 to your computer and use it in GitHub Desktop.

Select an option

Save ninejuan/7431cff155d42ef56fd4d86454eb8316 to your computer and use it in GitHub Desktop.
#!/bin/bash
set -euo pipefail
VPC_NAME="${1:?Usage: $0 <vpc-name-tag>}"
REGION="${AWS_DEFAULT_REGION:-ap-northeast-2}"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'
info() { echo -e "${CYAN}ℹ $1${NC}" >&2; }
success() { echo -e "${GREEN}✅ $1${NC}" >&2; }
warn() { echo -e "${YELLOW}⚠ $1${NC}" >&2; }
error() { echo -e "${RED}❌ $1${NC}" >&2; exit 1; }
header() { echo -e "\n${BOLD}━━━ $1 ━━━${NC}" >&2; }
dim() { echo -e "${DIM} $1${NC}" >&2; }
MAIN_ROUTE_TABLE_ID=""
ask() {
local var=$1 prompt=$2 default=$3 input
if [[ -n "$default" ]]; then
read -rp "$(echo -e " ${CYAN}›${NC} $prompt ${DIM}[${default}]${NC}: ")" input
eval "$var=\"${input:-$default}\""
else
while true; do
read -rp "$(echo -e " ${CYAN}›${NC} $prompt: ")" input
[[ -n "$input" ]] && break
warn "값을 입력해주세요."
done
eval "$var=\"$input\""
fi
}
ask_optional() {
local var=$1 prompt=$2 default="${3-}" input
if [[ -n "$default" ]]; then
read -rp "$(echo -e " ${CYAN}›${NC} $prompt ${DIM}[${default}]${NC}: ")" input
eval "$var=\"${input:-$default}\""
else
read -rp "$(echo -e " ${CYAN}›${NC} $prompt ${DIM}(비우면 없음)${NC}: ")" input
eval "$var=\"$input\""
fi
}
ask_choice() {
local var=$1 prompt=$2 default_idx=$3; shift 3
local opts=("$@")
echo -e " ${CYAN}›${NC} $prompt"
for i in "${!opts[@]}"; do
if (( i + 1 == default_idx )); then
echo -e " ${BOLD}$((i+1))${NC}) ${opts[$i]} ${DIM}(default)${NC}"
else
echo -e " ${BOLD}$((i+1))${NC}) ${opts[$i]}"
fi
done
while true; do
read -rp " 선택 (1-${#opts[@]}) [${default_idx}]: " n
n="${n:-$default_idx}"
[[ "$n" =~ ^[0-9]+$ ]] && (( n >= 1 && n <= ${#opts[@]} )) && break
warn "1~${#opts[@]} 중에서 선택하세요."
done
eval "$var=\"${opts[$((n-1))]}\""
}
get_vpc_id() {
local name=$1
local id
id=$(aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=$name" \
--query 'Vpcs[0].VpcId' --output text --region "$REGION")
if [[ "$id" == "None" || -z "$id" ]]; then
error "VPC를 찾을 수 없습니다: $name"
fi
echo "$id"
}
get_vpc_cidr() {
aws ec2 describe-vpcs \
--vpc-ids "$1" \
--query 'Vpcs[0].CidrBlock' --output text --region "$REGION"
}
get_subnet_ids() {
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$1" \
--query 'Subnets[*].SubnetId' --output text --region "$REGION"
}
ensure_sg() {
local name=$1 desc=$2 vpc_id=$3
local existing
existing=$(aws ec2 describe-security-groups \
--filters "Name=group-name,Values=$name" "Name=vpc-id,Values=$vpc_id" \
--query 'SecurityGroups[0].GroupId' --output text --region "$REGION")
if [[ "$existing" != "None" && -n "$existing" ]]; then
warn "이미 존재 → 재사용: $name ($existing)"
echo "$existing"
else
local sg_id
sg_id=$(aws ec2 create-security-group \
--group-name "$name" \
--description "$desc" \
--vpc-id "$vpc_id" \
--tag-specifications "ResourceType=security-group,Tags=[{Key=Name,Value=$name}]" \
--query 'GroupId' --output text --region "$REGION")
success "SG 생성: $name ($sg_id)"
echo "$sg_id"
fi
}
add_ingress_sg() {
local sg_id=$1 port=$2 src_sg=$3 label=$4
aws ec2 authorize-security-group-ingress \
--group-id "$sg_id" \
--protocol tcp \
--port "$port" \
--source-group "$src_sg" \
--region "$REGION" > /dev/null 2>&1 \
&& echo -e " ${GREEN}+${NC} inbound TCP $port ← SG $src_sg ${DIM}($label)${NC}" \
|| echo -e " ${YELLOW}~${NC} 중복 스킵 TCP $port ← SG $src_sg"
}
add_ingress_cidr() {
local sg_id=$1 port=$2 cidr=$3
aws ec2 authorize-security-group-ingress \
--group-id "$sg_id" \
--protocol tcp \
--port "$port" \
--cidr "$cidr" \
--region "$REGION" > /dev/null 2>&1 \
&& echo -e " ${GREEN}+${NC} inbound TCP $port ← $cidr" \
|| echo -e " ${YELLOW}~${NC} 중복 스킵 TCP $port ← $cidr"
}
add_egress_cidr() {
local sg_id=$1 port=$2 cidr=$3
aws ec2 authorize-security-group-egress \
--group-id "$sg_id" \
--ip-permissions "IpProtocol=tcp,FromPort=$port,ToPort=$port,IpRanges=[{CidrIp=$cidr}]" \
--region "$REGION" > /dev/null 2>&1 \
&& echo -e " ${GREEN}+${NC} outbound TCP $port → $cidr" \
|| echo -e " ${YELLOW}~${NC} 중복 스킵 TCP $port → $cidr"
}
add_egress_sg() {
local sg_id=$1 port=$2 dst_sg=$3 label=$4
aws ec2 authorize-security-group-egress \
--group-id "$sg_id" \
--ip-permissions "IpProtocol=tcp,FromPort=$port,ToPort=$port,UserIdGroupPairs=[{GroupId=$dst_sg}]" \
--region "$REGION" > /dev/null 2>&1 \
&& echo -e " ${GREEN}+${NC} outbound TCP $port → SG $dst_sg ${DIM}($label)${NC}" \
|| echo -e " ${YELLOW}~${NC} 중복 스킵 TCP $port → SG $dst_sg"
}
get_main_route_table_id() {
if [[ -n "$MAIN_ROUTE_TABLE_ID" ]]; then
echo "$MAIN_ROUTE_TABLE_ID"
return
fi
MAIN_ROUTE_TABLE_ID=$(aws ec2 describe-route-tables \
--filters "Name=vpc-id,Values=$VPC_ID" "Name=association.main,Values=true" \
--query 'RouteTables[0].RouteTableId' \
--output text \
--region "$REGION")
if [[ -z "$MAIN_ROUTE_TABLE_ID" || "$MAIN_ROUTE_TABLE_ID" == "None" ]]; then
error "Main route table을 찾을 수 없습니다: $VPC_ID"
fi
echo "$MAIN_ROUTE_TABLE_ID"
}
get_effective_route_table_id_for_subnet() {
local subnet_id=$1
local rtb_id
rtb_id=$(aws ec2 describe-route-tables \
--filters "Name=association.subnet-id,Values=$subnet_id" \
--query 'RouteTables[0].RouteTableId' \
--output text \
--region "$REGION" 2>/dev/null || true)
if [[ -z "$rtb_id" || "$rtb_id" == "None" ]]; then
rtb_id=$(get_main_route_table_id)
fi
if [[ -z "$rtb_id" || "$rtb_id" == "None" ]]; then
error "유효 route table을 찾을 수 없습니다: subnet=$subnet_id"
fi
echo "$rtb_id"
}
get_subnet_egress_flags() {
local subnet_id=$1
local rtb_id gateway_id nat_id has_igw has_nat
rtb_id=$(get_effective_route_table_id_for_subnet "$subnet_id")
gateway_id=$(aws ec2 describe-route-tables \
--route-table-ids "$rtb_id" \
--query "RouteTables[0].Routes[?DestinationCidrBlock=='0.0.0.0/0'] | [0].GatewayId" \
--output text \
--region "$REGION" 2>/dev/null || true)
nat_id=$(aws ec2 describe-route-tables \
--route-table-ids "$rtb_id" \
--query "RouteTables[0].Routes[?DestinationCidrBlock=='0.0.0.0/0'] | [0].NatGatewayId" \
--output text \
--region "$REGION" 2>/dev/null || true)
has_igw="false"
has_nat="false"
if [[ "$gateway_id" == igw-* ]]; then
has_igw="true"
fi
if [[ "$nat_id" == nat-* ]]; then
has_nat="true"
fi
echo "$has_igw $has_nat $rtb_id"
}
pick_subnet_for_az() {
local az=$1
shift
local az_candidates=("$@")
local subnet_id has_igw has_nat rtb_id
local -a no_igw_candidates=()
local -a no_nat_candidates=()
local -a stage1_candidates=()
local -a final_candidates=()
local profile_summary=""
for subnet_id in "${az_candidates[@]}"; do
read -r has_igw has_nat rtb_id <<< "$(get_subnet_egress_flags "$subnet_id")"
profile_summary+="$subnet_id(igw=$has_igw,nat=$has_nat,rtb=$rtb_id) "
if [[ "$has_igw" == "false" ]]; then
no_igw_candidates+=("$subnet_id")
fi
done
if [[ ${#no_igw_candidates[@]} -gt 0 ]]; then
stage1_candidates=("${no_igw_candidates[@]}")
else
stage1_candidates=("${az_candidates[@]}")
fi
for subnet_id in "${stage1_candidates[@]}"; do
read -r has_igw has_nat rtb_id <<< "$(get_subnet_egress_flags "$subnet_id")"
if [[ "$has_nat" == "false" ]]; then
no_nat_candidates+=("$subnet_id")
fi
done
if [[ ${#no_nat_candidates[@]} -gt 0 ]]; then
final_candidates=("${no_nat_candidates[@]}")
else
final_candidates=("${stage1_candidates[@]}")
fi
local pick_index=0
if [[ ${#final_candidates[@]} -gt 1 ]]; then
pick_index=$((RANDOM % ${#final_candidates[@]}))
fi
local selected_subnet="${final_candidates[$pick_index]}"
info "AZ $az 후보: $profile_summary"
info "AZ $az 선택: $selected_subnet (igw 미연결 우선 → nat 미연결 우선 → 동률 랜덤)"
echo "$selected_subnet"
}
check_vpce_service_supported() {
local service_name=$1
local lookup
if ! lookup=$(aws ec2 describe-vpc-endpoint-services \
--service-names "$service_name" \
--query 'ServiceDetails[0].ServiceName' \
--output text \
--region "$REGION" 2>&1); then
warn "서비스 조회 실패: $service_name"
warn " AWS 오류: $lookup"
return 1
fi
if [[ "$lookup" != "$service_name" ]]; then
warn "서비스 미지원/미노출: $service_name"
return 1
fi
return 0
}
check_vpc_dns_attributes() {
local vpc_id=$1
local dns_support dns_hostnames dns_support_lc dns_hostnames_lc
dns_support=$(aws ec2 describe-vpc-attribute \
--vpc-id "$vpc_id" \
--attribute enableDnsSupport \
--query 'EnableDnsSupport.Value' \
--output text \
--region "$REGION" 2>/dev/null || true)
dns_hostnames=$(aws ec2 describe-vpc-attribute \
--vpc-id "$vpc_id" \
--attribute enableDnsHostnames \
--query 'EnableDnsHostnames.Value' \
--output text \
--region "$REGION" 2>/dev/null || true)
info "VPC DNS 속성: enableDnsSupport=${dns_support:-unknown}, enableDnsHostnames=${dns_hostnames:-unknown}"
dns_support_lc=$(printf '%s' "${dns_support:-}" | tr '[:upper:]' '[:lower:]')
dns_hostnames_lc=$(printf '%s' "${dns_hostnames:-}" | tr '[:upper:]' '[:lower:]')
if [[ "$PRIVATE_DNS" == "yes" ]]; then
if [[ "$dns_support_lc" != "true" ]]; then
error "enableDnsSupport=true가 필요합니다. (Private DNS 사용 시 필수)"
fi
if [[ "$dns_hostnames_lc" != "true" ]]; then
error "enableDnsHostnames=true가 필요합니다. (Private DNS 사용 시 필수)"
fi
fi
}
ensure_vpce() {
local vpc_id=$1 svc=$2 vpce_sg_id=$3 private_dns=$4
local service_name="com.amazonaws.${REGION}.${svc}"
local existing
if ! check_vpce_service_supported "$service_name"; then
echo ""
return
fi
existing=$(aws ec2 describe-vpc-endpoints \
--filters "Name=vpc-id,Values=$vpc_id" \
"Name=service-name,Values=$service_name" \
"Name=vpc-endpoint-state,Values=available,pending,pendingAcceptance" \
--query 'VpcEndpoints[0].VpcEndpointId' \
--output text \
--region "$REGION")
if [[ "$existing" != "None" && -n "$existing" ]]; then
warn "이미 존재 → 재사용: $svc ($existing)"
echo "$existing"
return
fi
local cmd=(aws ec2 create-vpc-endpoint
--vpc-id "$vpc_id"
--service-name "$service_name"
--vpc-endpoint-type Interface
--subnet-ids "${ENDPOINT_SUBNET_ARRAY[@]}"
--security-group-ids "$vpce_sg_id"
--tag-specifications "ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=endpoint-${svc}-${VPC_NAME}}]"
--region "$REGION"
--query 'VpcEndpoint.VpcEndpointId'
--output text
)
if [[ "$private_dns" == "yes" ]]; then
cmd+=(--private-dns-enabled)
fi
local ep_id err_file aws_err
err_file=$(mktemp)
if ep_id=$("${cmd[@]}" 2>"$err_file"); then
success "Endpoint 생성: $svc ($ep_id)"
rm -f "$err_file"
echo "$ep_id"
else
aws_err=$(cat "$err_file" 2>/dev/null || true)
warn "Endpoint 생성 실패: $svc"
if [[ -n "$aws_err" ]]; then
warn " AWS 오류: $aws_err"
fi
rm -f "$err_file"
echo ""
fi
}
wait_vpce_available() {
local svc=$1 ep_id=$2
local timeout_sec interval_sec elapsed state
timeout_sec="${VPCE_WAIT_TIMEOUT_SEC:-300}"
interval_sec="${VPCE_WAIT_INTERVAL_SEC:-5}"
elapsed=0
if ! [[ "$timeout_sec" =~ ^[0-9]+$ ]]; then
timeout_sec=300
fi
if ! [[ "$interval_sec" =~ ^[0-9]+$ ]] || [[ "$interval_sec" -eq 0 ]]; then
interval_sec=5
fi
while (( elapsed <= timeout_sec )); do
state=$(aws ec2 describe-vpc-endpoints \
--vpc-endpoint-ids "$ep_id" \
--query 'VpcEndpoints[0].State' \
--output text \
--region "$REGION" 2>/dev/null || true)
case "$state" in
available|Available)
success "Endpoint available: $svc ($ep_id)"
return 0
;;
failed|rejected|deleted|deleting|expired)
warn "Endpoint available 대기 실패: $svc ($ep_id) 상태=$state"
return 1
;;
None|"" )
warn "Endpoint 상태 조회 실패/미응답: $svc ($ep_id)"
;;
*)
dim "Endpoint 대기 중: $svc ($ep_id) 상태=$state (${elapsed}s/${timeout_sec}s)"
;;
esac
sleep "$interval_sec"
elapsed=$((elapsed + interval_sec))
done
warn "Endpoint available 대기 타임아웃: $svc ($ep_id) 마지막상태=${state:-unknown}"
return 1
}
normalize_csv_to_array() {
local raw=$1
local out_name=$2
local p
eval "$out_name=()"
raw="${raw// /}"
if [[ -z "$raw" ]]; then
return
fi
IFS=',' read -r -a parts <<< "$raw"
for p in "${parts[@]+"${parts[@]}"}"; do
if [[ -n "$p" ]]; then
eval "$out_name+=(\"$p\")"
fi
done
}
dedupe_subnets_by_az() {
local input_ids=("$@")
local desc subnet_id az row_az chosen_subnet
ENDPOINT_SUBNET_ARRAY=()
local seen_az=" "
local -a az_list=()
local -a az_candidates=()
if [[ ${#input_ids[@]} -eq 0 ]]; then
error "Endpoint 서브넷 입력이 비어 있습니다."
fi
desc=$(aws ec2 describe-subnets \
--subnet-ids "${input_ids[@]}" \
--query 'Subnets[*].[SubnetId,AvailabilityZone]' \
--output text \
--region "$REGION")
if [[ -z "$desc" || "$desc" == "None" ]]; then
error "Endpoint 서브넷 정보를 조회하지 못했습니다."
fi
while read -r subnet_id az; do
[[ -z "${subnet_id:-}" || -z "${az:-}" ]] && continue
if [[ "$seen_az" != *" $az "* ]]; then
az_list+=("$az")
seen_az="${seen_az}${az} "
fi
done <<< "$desc"
for az in "${az_list[@]}"; do
az_candidates=()
while read -r subnet_id row_az; do
[[ -z "${subnet_id:-}" || -z "${row_az:-}" ]] && continue
[[ "$row_az" == "$az" ]] && az_candidates+=("$subnet_id")
done <<< "$desc"
[[ ${#az_candidates[@]} -eq 0 ]] && continue
chosen_subnet=$(pick_subnet_for_az "$az" "${az_candidates[@]}")
ENDPOINT_SUBNET_ARRAY+=("$chosen_subnet")
done
if [[ ${#ENDPOINT_SUBNET_ARRAY[@]} -eq 0 ]]; then
error "유효한 Endpoint 서브넷이 없습니다."
fi
}
echo -e "\n${BOLD}╔══════════════════════════════════════════════════════╗"
echo -e "║ Aurora MySQL → Lambda SG/VPCE 원샷 프로비저닝 ║"
echo -e "╚══════════════════════════════════════════════════════╝${NC}"
VPC_ID=$(get_vpc_id "$VPC_NAME")
VPC_CIDR=$(get_vpc_cidr "$VPC_ID")
ALL_SUBNET_IDS=$(get_subnet_ids "$VPC_ID")
if [[ -z "$ALL_SUBNET_IDS" ]]; then
error "VPC 서브넷을 찾지 못했습니다: $VPC_NAME ($VPC_ID)"
fi
echo -e "${DIM} VPC: $VPC_NAME ($VPC_ID) / CIDR: $VPC_CIDR${NC}"
echo -e "${DIM} Subnets: $ALL_SUBNET_IDS${NC}\n"
header "기본 SG 이름/포트"
ask DB_SG_NAME "DB SG 이름" "db-sg"
ask LAMBDA_SG_NAME "Lambda SG 이름" "lambda-sg"
ask CLOUDSHELL_SG_NAME "CloudShell SG 이름" "cloudshell-sg"
ask DB_PORT "DB 포트" "3306"
header "통신 경로"
ask_choice NETWORK_MODE "Aurora → Lambda API 경로" 1 \
"vpce (Interface Endpoint 사용)" \
"nat (NAT Gateway 사용)"
header "DB inbound 허용 소스"
ask_optional DB_ALLOW_FROM_SG_IDS "추가 SG ID 허용(쉼표구분)"
ask_optional DB_ALLOW_FROM_CIDRS "추가 CIDR 허용(쉼표구분)"
ask_choice ALLOW_LAMBDA_DB "Lambda SG에서 DB 포트 허용 여부" 2 \
"yes" "no"
CREATE_VPCE="no"
CREATE_RELATED_SCOPE=""
PRIVATE_DNS="yes"
VPCE_SG_NAME="vpc-endpoint-sg-${VPC_NAME}"
WAIT_VPCE_AVAILABLE="yes"
ENDPOINT_SUBNET_IDS="$ALL_SUBNET_IDS"
ENDPOINT_SUBNET_ARRAY=()
if [[ "$NETWORK_MODE" == "vpce (Interface Endpoint 사용)" ]]; then
header "VPCE 설정"
ask VPCE_SG_NAME "VPCE SG 이름" "$VPCE_SG_NAME"
ask_choice CREATE_VPCE "VPCE 생성/재사용 실행" 1 "yes" "no"
if [[ "$CREATE_VPCE" == "yes" ]]; then
ask_choice CREATE_RELATED_SCOPE "생성할 Endpoint 범위" 2 \
"lambda only" \
"common (lambda,logs,monitoring,sts,kms,secretsmanager)" \
"extended (+events,sqs,sns,xray,ssm,ssmmessages,ec2messages,ecr.api,ecr.dkr)" \
"custom"
if [[ "$CREATE_RELATED_SCOPE" == "custom" ]]; then
ask CUSTOM_ENDPOINTS "custom 서비스명(쉼표구분, 예: lambda,logs,kms)" "lambda"
fi
ask_choice SUBNET_SELECT_MODE "Endpoint 배치 서브넷" 1 "all subnets in VPC" "custom subnet IDs"
if [[ "$SUBNET_SELECT_MODE" == "custom subnet IDs" ]]; then
ask ENDPOINT_SUBNET_IDS "서브넷 ID들(공백 구분)" "$ALL_SUBNET_IDS"
fi
ask_choice PRIVATE_DNS "Private DNS 활성화" 1 "yes" "no"
ask_choice WAIT_VPCE_AVAILABLE "Endpoint가 available 될 때까지 대기" 1 "yes" "no"
fi
fi
header "설정 요약"
echo -e " ${BOLD}VPC${NC}: $VPC_NAME ($VPC_ID)"
echo -e " ${BOLD}DB SG${NC}: $DB_SG_NAME"
echo -e " ${BOLD}Lambda SG${NC}: $LAMBDA_SG_NAME"
echo -e " ${BOLD}CloudShell SG${NC}: $CLOUDSHELL_SG_NAME"
echo -e " ${BOLD}DB Port${NC}: $DB_PORT"
echo -e " ${BOLD}경로${NC}: $NETWORK_MODE"
echo -e " ${BOLD}DB 추가 SG 소스${NC}: ${DB_ALLOW_FROM_SG_IDS:-<none>}"
echo -e " ${BOLD}DB 추가 CIDR 소스${NC}: ${DB_ALLOW_FROM_CIDRS:-<none>}"
echo -e " ${BOLD}Lambda→DB 허용${NC}: $ALLOW_LAMBDA_DB"
if [[ "$NETWORK_MODE" == "vpce (Interface Endpoint 사용)" ]]; then
echo -e " ${BOLD}VPCE SG${NC}: $VPCE_SG_NAME"
echo -e " ${BOLD}VPCE 생성${NC}: $CREATE_VPCE"
if [[ "$CREATE_VPCE" == "yes" ]]; then
echo -e " ${BOLD}Endpoint 범위${NC}: $CREATE_RELATED_SCOPE"
echo -e " ${BOLD}Endpoint available 대기${NC}: $WAIT_VPCE_AVAILABLE"
fi
fi
echo ""
read -rp "$(echo -e "${CYAN}?${NC} 위 설정으로 생성할까요? (y/N): ")" CONFIRM
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
echo "취소됨."
exit 0
fi
header "1/4 SG 생성/조회"
DB_SG_ID=$(ensure_sg "$DB_SG_NAME" "Aurora DB SG" "$VPC_ID")
LAMBDA_SG_ID=$(ensure_sg "$LAMBDA_SG_NAME" "Lambda SG" "$VPC_ID")
CLOUDSHELL_SG_ID=$(ensure_sg "$CLOUDSHELL_SG_NAME" "CloudShell SG" "$VPC_ID")
VPCE_SG_ID=""
if [[ "$NETWORK_MODE" == "vpce (Interface Endpoint 사용)" ]]; then
VPCE_SG_ID=$(ensure_sg "$VPCE_SG_NAME" "Lambda VPCE SG" "$VPC_ID")
fi
header "2/4 DB inbound 규칙"
if [[ "$ALLOW_LAMBDA_DB" == "yes" ]]; then
add_ingress_sg "$DB_SG_ID" "$DB_PORT" "$LAMBDA_SG_ID" "$LAMBDA_SG_NAME"
fi
add_ingress_sg "$DB_SG_ID" "$DB_PORT" "$CLOUDSHELL_SG_ID" "$CLOUDSHELL_SG_NAME"
normalize_csv_to_array "$DB_ALLOW_FROM_SG_IDS" DB_SOURCE_SG_ARRAY
for sg in "${DB_SOURCE_SG_ARRAY[@]+"${DB_SOURCE_SG_ARRAY[@]}"}"; do
add_ingress_sg "$DB_SG_ID" "$DB_PORT" "$sg" "custom source SG"
done
normalize_csv_to_array "$DB_ALLOW_FROM_CIDRS" DB_SOURCE_CIDR_ARRAY
for cidr in "${DB_SOURCE_CIDR_ARRAY[@]+"${DB_SOURCE_CIDR_ARRAY[@]}"}"; do
add_ingress_cidr "$DB_SG_ID" "$DB_PORT" "$cidr"
done
header "3/4 Aurora outbound 규칙"
if [[ "$NETWORK_MODE" == "vpce (Interface Endpoint 사용)" ]]; then
add_ingress_cidr "$VPCE_SG_ID" "443" "$VPC_CIDR"
add_ingress_sg "$VPCE_SG_ID" "443" "$DB_SG_ID" "$DB_SG_NAME"
add_egress_sg "$DB_SG_ID" "443" "$VPCE_SG_ID" "$VPCE_SG_NAME"
else
add_egress_cidr "$DB_SG_ID" "443" "0.0.0.0/0"
fi
ENDPOINT_RESULTS=()
VPCE_ID_LIST=()
VPCE_FAILED_SERVICES=()
header "4/4 VPCE 생성"
if [[ "$NETWORK_MODE" == "vpce (Interface Endpoint 사용)" && "$CREATE_VPCE" == "yes" ]]; then
read -r -a RAW_ENDPOINT_SUBNET_ARRAY <<< "$ENDPOINT_SUBNET_IDS"
if [[ ${#RAW_ENDPOINT_SUBNET_ARRAY[@]} -eq 0 ]]; then
error "Endpoint 서브넷이 비어 있습니다."
fi
dedupe_subnets_by_az "${RAW_ENDPOINT_SUBNET_ARRAY[@]}"
info "Endpoint 서브넷(1 AZ당 1개): ${ENDPOINT_SUBNET_ARRAY[*]}"
check_vpc_dns_attributes "$VPC_ID"
declare -a TARGET_SERVICES
case "$CREATE_RELATED_SCOPE" in
"lambda only")
TARGET_SERVICES=("lambda")
;;
"common (lambda,logs,monitoring,sts,kms,secretsmanager)")
TARGET_SERVICES=("lambda" "logs" "monitoring" "sts" "kms" "secretsmanager")
;;
"extended (+events,sqs,sns,xray,ssm,ssmmessages,ec2messages,ecr.api,ecr.dkr)")
TARGET_SERVICES=("lambda" "logs" "monitoring" "sts" "kms" "secretsmanager" "events" "sqs" "sns" "xray" "ssm" "ssmmessages" "ec2messages" "ecr.api" "ecr.dkr")
;;
"custom")
normalize_csv_to_array "${CUSTOM_ENDPOINTS:-lambda}" TARGET_SERVICES
;;
*)
TARGET_SERVICES=("lambda")
;;
esac
for svc in "${TARGET_SERVICES[@]+"${TARGET_SERVICES[@]}"}"; do
ep_id=$(ensure_vpce "$VPC_ID" "$svc" "$VPCE_SG_ID" "$PRIVATE_DNS")
if [[ -n "$ep_id" ]]; then
ENDPOINT_RESULTS+=("$svc:$ep_id")
VPCE_ID_LIST+=("$ep_id")
if [[ "$WAIT_VPCE_AVAILABLE" == "yes" ]]; then
if ! wait_vpce_available "$svc" "$ep_id"; then
warn "Endpoint 대기 실패(계속 진행): $svc"
fi
fi
else
VPCE_FAILED_SERVICES+=("$svc")
fi
done
if [[ ${#VPCE_FAILED_SERVICES[@]} -gt 0 ]]; then
warn "Endpoint 생성 실패 서비스: ${VPCE_FAILED_SERVICES[*]}"
fi
if [[ ${#ENDPOINT_RESULTS[@]} -eq 0 ]]; then
error "Endpoint가 하나도 생성/재사용되지 않았습니다. 위 AWS 오류를 확인하세요."
fi
else
dim "VPCE 생성 단계 스킵"
fi
header "완료 요약"
echo -e "\n${BOLD}[ Security Groups ]${NC}"
printf " %-22s %s\n" "DB SG" "$DB_SG_ID"
printf " %-22s %s\n" "Lambda SG" "$LAMBDA_SG_ID"
printf " %-22s %s\n" "CloudShell SG" "$CLOUDSHELL_SG_ID"
if [[ -n "$VPCE_SG_ID" ]]; then
printf " %-22s %s\n" "VPCE SG" "$VPCE_SG_ID"
fi
if [[ ${#ENDPOINT_RESULTS[@]} -gt 0 ]]; then
echo -e "\n${BOLD}[ VPC Endpoints ]${NC}"
for pair in "${ENDPOINT_RESULTS[@]+"${ENDPOINT_RESULTS[@]}"}"; do
svc="${pair%%:*}"
ep="${pair##*:}"
printf " %-22s %s\n" "$svc" "$ep"
done
if [[ ${#VPCE_ID_LIST[@]} -gt 0 ]]; then
echo ""
aws ec2 describe-vpc-endpoints \
--vpc-endpoint-ids "${VPCE_ID_LIST[@]}" \
--query 'VpcEndpoints[*].{ID:VpcEndpointId,Service:ServiceName,State:State,PrivateDNS:PrivateDnsEnabled}' \
--output table \
--region "$REGION"
fi
fi
echo ""
success "Aurora→Lambda용 SG/VPCE 프로비저닝 로직 실행 완료"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment