Created
April 5, 2026 07:55
-
-
Save ninejuan/7431cff155d42ef56fd4d86454eb8316 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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