Home
home

섹션 08 - Amazon VPC 보안 구성

1. 기본 환경 구성

이번 실습은 IAM 사용자 계정을 통해 관리 콘솔에 접근하고 액세스 키를 활용해 awscli 도구를 사용합니다.
해당 작업을 수행하지 않았다면 아래 토글을 확장해 작업을 선행하고 본격적인 실습에 들어갑니다.
IAM 사용자 생성 및 액세스 키 생성

1.1. Terraform을 통한 기본 인프라 배포

Terraform을 통한 기본 인프라 배포에 앞서 SSH 키 페어, IAM User Access Key ID, IAM User Secret Access Key를 미리 확인하고 메모해 둡니다.
Terraform으로 기본 인프라 배포
cd cnasg_class_tf/Section08
Bash
복사
# 실습 코드 경로 진입
export TF_VAR_KeyName=[각자 ssh keypair] export TF_VAR_NickName=[각자 닉네임] export TF_VAR_MyIamUserAccessKeyID=[각자 iam 사용자의 access key id] export TF_VAR_MyIamUserSecretAccessKey=[각자 iam 사용자의 secret access key] export TF_VAR_SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32
Bash
복사
# Terraform 환경 변수 저장
terraform init terraform plan
Bash
복사
# Terraform 배포
nohup sh -c "terraform apply -auto-approve" > create.log 2>&1 &
Bash
복사
Note:  Terraform 배포가 완료되면(약 5분 정도 대기) 정상적으로 자원 생성이 되었는지 확인을 합니다.(cat create.log)

1.2. 기본 정보 확인 및 설정

Terraform 배포가 완료 후 출력되는 Outputs 정보에서 bastion_host_ip의 퍼블릭 IP를 확인합니다.
대상 IP로 인스턴스에 SSH로 접속하고 아래 명령어를 통해 정보를 확인합니다.
기본 정보 확인
aws sts get-caller-identity
Bash
복사
# caller id 확인
Note:  인스턴스가 생성되고 너무 빠르게 접속하면 aws 자격 증명이 완료되지 않을 수 있습니다.
그럴 경우에는 약간의 대기 후 다시 접속해 주세요.
기본 변수 선언
FE_VPC_ID=$(aws ec2 describe-vpcs \ --filters "Name=tag:Name,Values=FE-VPC" \ --query 'Vpcs[0].VpcId' --output text) BE_VPC_ID=$(aws ec2 describe-vpcs \ --filters "Name=tag:Name,Values=BE-VPC" \ --query 'Vpcs[0].VpcId' \ --output text)
Bash
복사
# VPC ID 변수 선언
FE_PRIVATE_SUBNET_IDS=$(aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=${FE_VPC_ID}" \ "Name=tag:Name,Values=FE-VPC-private*" \ --query 'Subnets[*].SubnetId' \ --output text) BE_PRIVATE_SUBNET_IDS=$(aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=${BE_VPC_ID}" \ "Name=tag:Name,Values=BE-VPC-private*" \ --query 'Subnets[*].SubnetId' \ --output text)
Bash
복사
# 프라이빗 서브넷 IDs 변수 선언 (ALL)
WEB1_ID=$(aws ec2 describe-instances \ --filters "Name=tag:Name,Values=WEB1" "Name=instance-state-name,Values=running" \ --query 'Reservations[0].Instances[0].InstanceId' --output text) WEB2_ID=$(aws ec2 describe-instances \ --filters "Name=tag:Name,Values=WEB2" "Name=instance-state-name,Values=running" \ --query 'Reservations[0].Instances[0].InstanceId' --output text) DB_API_ID=$(aws ec2 describe-instances \ --filters "Name=tag:Name,Values=DB-API" "Name=instance-state-name,Values=running" \ --query 'Reservations[0].Instances[0].InstanceId' --output text)
Bash
복사
# WEB1 / WEB2 / DB-API Instance ID
WEB1_ENI=$(aws ec2 describe-instances \ --instance-ids ${WEB1_ID} \ --query 'Reservations[0].Instances[0].NetworkInterfaces[0].NetworkInterfaceId' \ --output text) WEB2_ENI=$(aws ec2 describe-instances \ --instance-ids ${WEB2_ID} \ --query 'Reservations[0].Instances[0].NetworkInterfaces[0].NetworkInterfaceId' \ --output text)
Bash
복사
# WEB1 / WEB2 ENI ID
WEB1_SG_ID=$(aws ec2 describe-security-groups \ --filters "Name=vpc-id,Values=${FE_VPC_ID}" "Name=group-name,Values=web1-sec-group" \ --query 'SecurityGroups[0].GroupId' --output text) WEB2_SG_ID=$(aws ec2 describe-security-groups \ --filters "Name=vpc-id,Values=${FE_VPC_ID}" "Name=group-name,Values=web2-sec-group" \ --query 'SecurityGroups[0].GroupId' --output text)
Bash
복사
# WEB1 / WEB2 SG ID
WEB1_IP=192.168.11.100 WEB2_IP=192.168.11.200
Bash
복사
# WEB1 / WEB2 IP
echo "export FE_VPC_ID=${FE_VPC_ID}" >> /etc/profile echo "export BE_VPC_ID=${BE_VPC_ID}" >> /etc/profile echo "export WEB1_ID=${WEB1_ID}" >> /etc/profile echo "export WEB2_ID=${WEB2_ID}" >> /etc/profile echo "export DB_API_ID=${DB_API_ID}" >> /etc/profile echo "export WEB1_ENI=${WEB1_ENI}" >> /etc/profile echo "export WEB2_ENI=${WEB2_ENI}" >> /etc/profile echo "export WEB1_SG_ID=${WEB1_SG_ID}" >> /etc/profile echo "export WEB2_SG_ID=${WEB2_SG_ID}" >> /etc/profile echo "export WEB1_IP=$WEB1_IP" >> /etc/profile echo "export WEB2_IP=$WEB2_IP" >> /etc/profile echo "FE_VPC_ID=${FE_VPC_ID}" echo "BE_VPC_ID=${BE_VPC_ID}" echo "FE_PRIVATE_SUBNET_IDS=${FE_PRIVATE_SUBNET_IDS}" echo "BE_PRIVATE_SUBNET_IDS=${BE_PRIVATE_SUBNET_IDS}" echo "WEB1_ID=${WEB1_ID}" echo "WEB2_ID=${WEB2_ID}" echo "DB_API_ID=${DB_API_ID}" echo "WEB1_ENI=${WEB1_ENI}" echo "WEB2_ENI=${WEB2_ENI}" echo "WEB1_SG_ID=${WEB1_SG_ID}" echo "WEB2_SG_ID=${WEB2_SG_ID}" echo "WEB1_IP=$WEB1_IP" echo "WEB2_IP=$WEB2_IP"
Bash
복사
# 전역 변수 선언 및 변수 값 출력

2. VPC 트래픽 통제 및 확인

2.1. VPC FlowLogs 구성

[VPC FlowLogs] IAM 구성
cat > vpc-flowlogs-trust.json <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "vpc-flow-logs.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF
Bash
복사
# 신뢰 정책 파일 생성
aws iam create-role \ --role-name VPCFlowLogsRole \ --assume-role-policy-document file://vpc-flowlogs-trust.json
Bash
복사
# IAM 역할 생성
cat > vpc-flowlogs-policy.json <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogGroups", "logs:DescribeLogStreams" ], "Resource": "*" } ] } EOF
Bash
복사
# IAM 정책 파일 생성
aws iam create-policy \ --policy-name VPCFlowLogsPolicy \ --policy-document file://vpc-flowlogs-policy.json
Bash
복사
# IAM 정책 생성
aws iam attach-role-policy \ --role-name VPCFlowLogsRole \ --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/VPCFlowLogsPolicy
Bash
복사
# IAM 역할에 IAM 정책 연결
FLOWLOG_ROLE_ARN=arn:aws:iam::${ACCOUNT_ID}:role/VPCFlowLogsRole echo "export FLOWLOG_ROLE_ARN=${FLOWLOG_ROLE_ARN}" >> /etc/profile echo "FLOWLOG_ROLE_ARN=${FLOWLOG_ROLE_ARN}"
Bash
복사
# IAM 역할의 ARN 정보 변수 선언
[VPC FlowLogs] CloudWatch Log Group 생성
LOG_GROUP_NAME=/cnasg/flowlogs/web-eni echo "export LOG_GROUP_NAME=${LOG_GROUP_NAME}" >> /etc/profile
Bash
복사
# CloudWatch Log Group 이름 - 변수 선언
aws logs create-log-group \ --log-group-name ${LOG_GROUP_NAME}
Bash
복사
# CloudWatch Log Group 생성
[관리 콘솔 확인: CloudWatch → 로그 → Log Management]
[VPC FlowLogs] WEB1 / WEB2 ENI에 FlowLogs 생성
aws ec2 create-flow-logs \ --resource-type NetworkInterface \ --resource-ids ${WEB1_ENI} ${WEB2_ENI} \ --traffic-type REJECT \ --log-destination-type cloud-watch-logs \ --log-group-name ${LOG_GROUP_NAME} \ --deliver-logs-permission-arn ${FLOWLOG_ROLE_ARN}
Bash
복사
# FlowLogs 생성 (REJECT만 수집해서 CloudWatch Log Group에 게시)
aws ec2 describe-flow-logs \ --filter Name=resource-id,Values=${WEB1_ENI},${WEB2_ENI} \ --query 'FlowLogs[*].{FlowLogId:FlowLogId,ResourceId:ResourceId,Status:FlowLogStatus,Traffic:TrafficType,LogGroup:LogGroupName}' \ --output table
Bash
복사
# FlowLogs 상태 확인

2.2. 보안 그룹 동작 확인

WEB1 / WEB2 보안 그룹 확인
cat > /etc/profile.d/sgview.sh <<'EOF' sgview () { aws ec2 describe-security-groups --group-ids "$1" --output json | jq -r ' .SecurityGroups[0] as $sg | "SG: \($sg.GroupName)", "[Ingress]", ( $sg.IpPermissions[]? | "- \(.IpProtocol) \((.FromPort // "ALL"))-\((.ToPort // "ALL")) " + ( ( [.IpRanges[]?.CidrIp] + [.UserIdGroupPairs[]?.GroupId] ) | join(", ") ) ), "[Egress]", ( $sg.IpPermissionsEgress[]? | "- \(.IpProtocol) \((.FromPort // "ALL"))-\((.ToPort // "ALL")) " + ( ( [.IpRanges[]?.CidrIp] + [.UserIdGroupPairs[]?.GroupId] ) | join(", ") ) ) ' } EOF source /etc/profile.d/sgview.sh
Bash
복사
# 보안 그룹 확인 - 함수 선언 (sgview)
sgview "${WEB1_SG_ID}" sgview "${WEB2_SG_ID}"
Bash
복사
# WEB1 / WEB2 보안 그룹 확인
WEB1 / WEB2 보안 그룹에 아웃바운드 규칙 삭제
aws ec2 revoke-security-group-egress \ --group-id ${WEB1_SG_ID} \ --ip-permissions '[{"IpProtocol":"-1","IpRanges":[{"CidrIp":"0.0.0.0/0"}]}]' aws ec2 revoke-security-group-egress \ --group-id ${WEB2_SG_ID} \ --ip-permissions '[{"IpProtocol":"-1","IpRanges":[{"CidrIp":"0.0.0.0/0"}]}]'
Bash
복사
# 아웃바운드 규칙 삭제
sgview "${WEB1_SG_ID}" sgview "${WEB2_SG_ID}"
Bash
복사
# WEB1 / WEB2 보안 그룹 확인
보안 그룹 동작의 이해를 돕기 위해 아웃바운드 규칙을 삭제한 것입니다.
[BASTION → WEB1 / WEB2] SSH 접근 확인
ssh ec2-user@${WEB1_IP}
Bash
복사
# WEB1으로 SSH 접근(password: cn@sg!@)
ssh ec2-user@${WEB2_IP}
Bash
복사
# WEB2로 SSH 접근(password: cn@sg!@)
sgview "${WEB1_SG_ID}" sgview "${WEB2_SG_ID}"
Bash
복사
# WEB1 / WEB2 보안 그룹 확인
[사용자 PC → ALB → WEB1 / WEB2] HTTP 통신 확인
ALB_NAME="cnasg-fe-web-alb" aws elbv2 describe-load-balancers \ --names $ALB_NAME \ --query 'LoadBalancers[0].DNSName' \ --output text
Bash
복사
# ALB 도메인 주소 확인
출력된 ALB 도메인 주소로 웹 접근을 수행합니다. (http://ALB 도메인 주소)
[사용자 PC → ALB → WEB1 / WEB2] CloudWatch 로그 스트림 확인
aws logs describe-log-streams \ --log-group-name "${LOG_GROUP_NAME}" \ --order-by LastEventTime \ --descending \ --max-items 10 \ --query 'logStreams[*].logStreamName' \ --output table
Bash
복사
# 생성된 로그 스트림 확인
STREAM1="${WEB1_ENI}-reject" STREAM2="${WEB2_ENI}-reject" echo "export STREAM1=$STREAM1" >> /etc/profile echo "export STREAM2=$STREAM2" >> /etc/profile
Bash
복사
# 로그 스트림 이름 - 변수 선언
aws logs filter-log-events \ --log-group-name "${LOG_GROUP_NAME}" \ --log-stream-names "${STREAM1}" \ --filter-pattern "REJECT" \ --max-items 5 \ --query 'events[*].message' \ --output json | jq
Bash
복사
# WEB1 ENI의 로그 스트림 확인 (REJECT 패킷 5개만)
aws logs filter-log-events \ --log-group-name "${LOG_GROUP_NAME}" \ --log-stream-names "${STREAM2}" \ --filter-pattern "REJECT" \ --max-items 5 \ --query 'events[*].message' \ --output json | jq
Bash
복사
# WEB2 ENI의 로그 스트림 확인 (REJECT 패킷 5개만)
[사용자 PC → ALB → WEB1 / WEB2] ALB의 대상 그룹 확인
TG_ARN=$(aws elbv2 describe-target-groups \ --query 'TargetGroups[?contains(TargetGroupName, `cnasg`)].TargetGroupArn' \ --output text) echo "export TG_ARN=$TG_ARN" >> /etc/profile
Bash
복사
# ALB 대상 그룹의 ARN 변수 선언
aws elbv2 describe-target-health \ --target-group-arn ${TG_ARN} \ --query 'TargetHealthDescriptions[*].{ InstanceId:Target.Id, State:TargetHealth.State }' \ --output table
Bash
복사
# 대상 그룹 상태 정보 확인
모든 대상이 Unhealthy인 이유는??
[사용자 PC → ALB → WEB1 / WEB2] 보안 그룹 Ingress 규칙 추가
watch -d "aws elbv2 describe-target-health \ --target-group-arn ${TG_ARN} \ --query 'TargetHealthDescriptions[*].{ InstanceId:Target.Id, State:TargetHealth.State }' \ --output table"
Bash
복사
# [신규 터미널] 대상 그룹 상태 정보 모니터링
aws ec2 authorize-security-group-ingress \ --group-id ${WEB1_SG_ID} \ --protocol tcp \ --port 80 \ --cidr 0.0.0.0/0
Bash
복사
# WEB1 보안 그룹의 Ingress에 80 포트 허용 (모든 IP 대상)
sgview "${WEB1_SG_ID}"
Bash
복사
# WEB1 보안 그룹 정보 확인
잠시 기다리면 WEB1 대상이 Healthy로 전환됩니다. (WEB2는 Unhealthy인 이유는??) ALB 도메인 주소로 웹 접근을 수행하면 WEB1 페이지로만 접근됩니다.
aws ec2 authorize-security-group-ingress \ --group-id ${WEB2_SG_ID} \ --protocol tcp \ --port 80 \ --cidr 0.0.0.0/0
Bash
복사
# WEB2 보안 그룹도 Ingress에 80 포트 허용 (모든 IP 대상)
sgview "${WEB2_SG_ID}"
Bash
복사
# WEB2 보안 그룹 정보 확인
잠시 기다리면 모든 대상이 Healthy로 전환됩니다. ALB 도메인 주소로 웹 접근을 수행하면 WEB1 / WEB2 페이지 모두에 접근됩니다.
[WEB1 → NAT → 인터넷 구간] HTTP 통신 확인
ssh ec2-user@${WEB1_IP}
Bash
복사
# WEB1으로 SSH 접근(password: cn@sg!@)
curl --connect-timeout 5 wttr.in
Bash
복사
# [WEB1] 외부 인터넷 구간으로 HTTP 통신
WEB1에서 외부 인터넷 구간으로 HTTP 통신이 불가한 이유는??
[WEB1 → NAT → 인터넷 구간] 보안 그룹 Egress 규칙 추가
aws ec2 authorize-security-group-egress \ --group-id ${WEB1_SG_ID} \ --protocol -1 \ --cidr 0.0.0.0/0
Bash
복사
# [BASTION] WEB1 보안 그룹의 Egress에 모든 통신 허용
sgview "${WEB1_SG_ID}"
Bash
복사
# [BASTION] WEB1 보안 그룹 정보 확인
curl --connect-timeout 5 wttr.in
Bash
복사
# [WEB1] 외부 인터넷 구간으로 HTTP 통신
aws ec2 authorize-security-group-egress \ --group-id ${WEB2_SG_ID} \ --protocol -1 \ --cidr 0.0.0.0/0
Bash
복사
# [BASTION] WEB2 보안 그룹의 Egress에 모든 통신 허용
WEB2도 마찬가지로 규칙을 추가합니다. (설정 전/후로 각자 통신 확인)

2.3. NACL 동작 확인

watch -d "aws elbv2 describe-target-health \ --target-group-arn ${TG_ARN} \ --query 'TargetHealthDescriptions[*].{ InstanceId:Target.Id, State:TargetHealth.State }' \ --output table"
Bash
복사
# [신규 터미널] 대상 그룹 상태 정보 모니터링
ALB 대상 그룹을 지속적으로 모니터링해서 NACL 동작을 이해합니다.
NACL(네트워크 ACL)에 대한 설정은 AWS CLI로 구성할 경우 복잡도가 높아 실습의 이해가 떨어질 수 있습니다. 그런 측면에서 이번 실습은 AWS 관리 콘솔에서 NACL를 관리하겠습니다.
기본 NACL 확인
VPC → 네트워크 ACL (진입)
FE-VPC-default 대상 선택
인바운드 규칙 탭
아웃바운드 규칙 탭
서브넷 연결 탭
신규 NACL 생성
VPC → 네트워크 ACL (진입) ⇒ 네트워크 ACL 생성 (클릭)
이름: NEW-FE-PRIVATE-0-NACL
VPC: FE-VPC
네트워크 ACL 생성 (클릭)
신규 NACL 확인 및 설정
VPC → 네트워크 ACL (진입)
NEW-FE-PRIVATE-0-NACL 대상 선택
인바운드 규칙 탭
아웃바운드 규칙 탭
서브넷 연결 탭 ⇒ 서브넷 연결 편집 (클릭)
FE-VPC-private-ap-northeast-2a (선택)
변경 사항 저장 (클릭)
잠시 기다리면 모든 대상이 Unhealthy로 전환됩니다.
신규 NACL - 인바운드 규칙 추가
VPC → 네트워크 ACL (진입)
NEW-FE-PRIVATE-0-NACL 대상 선택
인바운드 규칙 탭 ⇒ 인바운드 규칙 편집 (클릭)
새 규칙 추가 (클릭)
규칙 번호: 100
포트 범위: 80
소스: 0.0.0.0/0
허용/거부: 허용
변경 사항 저장 (클릭)
여전히 모든 대상이 Unhealthy를 유지하는데 이유는??
신규 NACL - 아웃바운드 규칙 추가
VPC → 네트워크 ACL (진입)
NEW-FE-PRIVATE-0-NACL 대상 선택
아웃바운드 규칙 탭 ⇒ 아웃바운드 규칙 편집 (클릭)
새 규칙 추가 (클릭)
규칙 번호: 100
포트 범위: 1024-65535
소스: 0.0.0.0/0
허용/거부: 허용
변경 사항 저장 (클릭)
잠시 기다리면 모든 대상이 Healthy로 전환됩니다.
WEB1에 SSH 접근하고, 외부 인터넷 구간이 통신하도록 NACL 규칙을 수정해 보세요.
기본 NACL로 다시 연결
VPC → 네트워크 ACL (진입)
FE-VPC-default 대상 선택
서브넷 연결 탭 ⇒ 서브넷 연결 편집 (클릭)
FE-VPC-private-ap-northeast-2a (선택)
변경 사항 저장 (클릭)
신규 NACL 삭제
VPC → 네트워크 ACL (진입)
NEW-FE-PRIVATE-0-NACL 대상 선택
작업 (선택) ⇒ 네트워크 ACL 삭제 (선택)
삭제 (입력) → 삭제 (클릭)

3. PrivateLink 구성

ALB_NAME="cnasg-fe-web-alb" aws elbv2 describe-load-balancers \ --names $ALB_NAME \ --query 'LoadBalancers[0].DNSName' \ --output text
Bash
복사
# ALB 도메인 주소 확인
출력된 ALB 도메인 주소로 웹 접근을 수행합니다. (http://ALB 도메인 주소) ALB를 통해 WEB1/2로 부하 분산되서 통신되지만, 아직 DB와 연결되지 못했습니다.

3.1. DB 생성 및 DB-API 연결 (퍼블릭)

AWS RDS 생성 및 확인
DB_ID=cnasg-db DB_NAME=fruitdb DB_USER=cnasg DB_PASS='Cnasg1234!' DB_CLASS=db.t3.micro DB_PORT=3306 echo "export DB_ID=${DB_ID}" >> /etc/profile echo "export DB_NAME=${DB_NAME}" >> /etc/profile echo "export DB_USER=${DB_USER}" >> /etc/profile echo "export DB_PASS=${DB_PASS}" >> /etc/profile echo "export DB_CLASS=${DB_CLASS}" >> /etc/profile echo "export DB_PORT=${DB_PORT}" >> /etc/profile
Bash
복사
# 기본 변수 선언
RDS_SN_GROUP="cnasg-db-subnet-group" echo "export RDS_SN_GROUP=$RDS_SN_GROUP" >> /etc/profile aws rds create-db-subnet-group \ --db-subnet-group-name ${RDS_SN_GROUP} \ --db-subnet-group-description "CNASG BE VPC Private Subnets" \ --subnet-ids ${BE_PRIVATE_SUBNET_IDS}
Bash
복사
# RDS DB 서브넷 그룹 생성
aws rds describe-db-subnet-groups \ --db-subnet-group-name ${RDS_SN_GROUP} \ --query 'DBSubnetGroups[0].Subnets[*].SubnetIdentifier' \ --output table
Bash
복사
# RDS DB 서브넷 그룹 확인
[관리 콘솔 확인: Aurora and RDS → 서브넷 그룹]
RDS_SG_ID=$(aws ec2 describe-security-groups \ --filters "Name=vpc-id,Values=${BE_VPC_ID}" \ "Name=group-name,Values=rds-sec-group" \ --query 'SecurityGroups[0].GroupId' \ --output text) echo "export RDS_SG_ID=${RDS_SG_ID}" >> /etc/profile echo "RDS_SG_ID=${RDS_SG_ID}"
Bash
복사
# RDS 보안 그룹 ID - 변수 선언
sgview "${RDS_SG_ID}"
Bash
복사
# RDS 보안 그룹 정보 확인
aws rds create-db-instance \ --db-instance-identifier ${DB_ID} \ --engine mysql \ --engine-version 8.0 \ --db-instance-class ${DB_CLASS} \ --allocated-storage 20 \ --storage-type gp3 \ --master-username ${DB_USER} \ --master-user-password ${DB_PASS} \ --db-name ${DB_NAME} \ --port ${DB_PORT} \ --vpc-security-group-ids ${RDS_SG_ID} \ --db-subnet-group-name ${RDS_SN_GROUP} \ --no-publicly-accessible \ --backup-retention-period 0 \ --no-multi-az \ --deletion-protection \ --tags Key=Name,Value=CNASG-RDS
Bash
복사
# RDS - MySQL DB 인스턴스 생성
watch -d "aws rds describe-db-instances \ --db-instance-identifier ${DB_ID} \ --query 'DBInstances[0].DBInstanceStatus' \ --output text"
Bash
복사
# RDS - MySQL DB 인스턴스 상태 확인 (모니터링)
RDS DB 인스턴스 생성까지 약 5분 정도의 대기 시간이 필요합니다.
[관리 콘솔 확인: Aurora and RDS → 데이터베이스]
aws rds describe-db-instances \ --db-instance-identifier ${DB_ID} \ --query 'DBInstances[0].Endpoint.{Address:Address,Port:Port}' \ --output table
Bash
복사
# DB 인스턴스의 RDS 엔드포인트 확인
DB_ENDPOINT=$(aws rds describe-db-instances \ --db-instance-identifier ${DB_ID} \ --query 'DBInstances[0].Endpoint.Address' \ --output text) echo "export echo DB_ENDPOINT=${DB_ENDPOINT}" >> /etc/profile echo $DB_ENDPOINT
Bash
복사
# RDS 엔드포인트 변수 선언
[DB-API 접속] AWS RDS 엔드포인트 접속 및 DB 생성
Terraform 배포가 완료 후 출력되는 Outputs 정보에서 db_api_host_ip의 퍼블릭 IP를 확인하고 SSH로 접속합니다.
DB_EP_ADDR=[DB_ENDPOINT_ADDRESS 입력]
Bash
복사
# 기본 변수 선언
DB_NAME=fruitdb DB_USER=cnasg DB_PASS='Cnasg1234!'
Bash
복사
mysql -h ${DB_EP_ADDR} \ -P 3306 \ -u ${DB_USER} \ --password=${DB_PASS}
Bash
복사
# MySQL 접속
USE fruitdb;
Bash
복사
# DB 선택 (fruitdb)
CREATE TABLE IF NOT EXISTS products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, price INT NOT NULL );
Bash
복사
# 테이블 생성 (products)
INSERT INTO products (name, price) VALUES ('사과', 500), ('바나나', 1000), ('수박', 2000);
Bash
복사
# 데이터 입력
SELECT * FROM products ORDER BY price ASC, name ASC;
Bash
복사
# 테이블 확인 (products)
exit
Bash
복사
# MySQL에서 빠져 나오기
[DB-API 접속] DB-API 설정
curl http://127.0.0.1/products.php
Bash
복사
# DB-API 동작 수행
cat > /etc/httpd/conf.d/db-api-env.conf <<EOF SetEnv DB_HOST ${DB_EP_ADDR} SetEnv DB_NAME ${DB_NAME} SetEnv DB_USER ${DB_USER} SetEnv DB_PASS ${DB_PASS} SetEnv DB_PORT 3306 EOF
Bash
복사
# db-api-env.conf에 환경 변수 선언
systemctl restart httpd
Bash
복사
# httpd 재시작
curl http://127.0.0.1/products.php
Bash
복사
# DB-API 동작 수행
[BASTION 접속] WEB1 / WEB2에 DB-API 설정
DB_API_PUBLIC_IP=$(aws ec2 describe-instances \ --filters "Name=tag:Name,Values=DB-API" \ "Name=instance-state-name,Values=running" \ --query 'Reservations[0].Instances[0].PublicIpAddress' \ --output text) echo "DB_API_PUBLIC_IP=${DB_API_PUBLIC_IP}"
Bash
복사
# DB-API 퍼블릭 IP를 변수 선언
WEB_SSH_PASS='cn@sg!@'
Bash
복사
# WEB1 / WEB2 SSH 암호를 변수 선언
sshpass -p "$WEB_SSH_PASS" ssh -T -o StrictHostKeyChecking=no ec2-user@$WEB1_IP <<EOF sudo tee /etc/httpd/conf.d/api-env.conf >/dev/null <<CONF SetEnv API_URL http://${DB_API_PUBLIC_IP}/products.php CONF sudo systemctl restart httpd EOF
Bash
복사
# WEB1에 API_URL 환경 변수를 선언하고 httpd 재시작
# WEB2 설정 sshpass -p "$WEB_SSH_PASS" ssh -T -o StrictHostKeyChecking=no ec2-user@$WEB2_IP <<EOF sudo tee /etc/httpd/conf.d/api-env.conf >/dev/null <<CONF SetEnv API_URL http://${DB_API_PUBLIC_IP}/products.php CONF sudo systemctl restart httpd EOF
Bash
복사
# WEB2에 API_URL 환경 변수를 선언하고 httpd 재시작
출력된 ALB 도메인 주소로 웹 접근을 수행합니다. (HTTP)
과일 품목을 추가해 보기

3.2. DB-API 연결 (PrivateLink)

Internal NLB와 대상 그룹 생성
NLB_TG_ARN=$(aws elbv2 create-target-group \ --name cnasg-dbapi-tg \ --protocol TCP \ --port 80 \ --vpc-id ${BE_VPC_ID} \ --target-type instance \ --query 'TargetGroups[0].TargetGroupArn' \ --output text) echo "export NLB_TG_ARN=${NLB_TG_ARN}" >> /etc/profile echo "NLB_TG_ARN=${NLB_TG_ARN}"
Bash
복사
# NLB 대상 그룹 생성 및 변수 선언
aws elbv2 register-targets \ --target-group-arn ${NLB_TG_ARN} \ --targets Id=${DB_API_ID},Port=80
Bash
복사
# NLB 대상 그룹에 DB-API 인스턴스 등록
NLB_ARN=$(aws elbv2 create-load-balancer \ --name cnasg-dbapi-nlb \ --type network \ --scheme internal \ --subnets ${BE_PRIVATE_SUBNET_IDS} \ --query 'LoadBalancers[0].LoadBalancerArn' \ --output text) echo "export NLB_ARN=${NLB_ARN}" >> /etc/profile echo "NLB_ARN=${NLB_ARN}"
Bash
복사
# NLB 생성(BE-VPC 프라이빗 서브넷) 및 ARN 변수 선언
aws elbv2 create-listener \ --load-balancer-arn ${NLB_ARN} \ --protocol TCP \ --port 80 \ --default-actions Type=forward,TargetGroupArn=${NLB_TG_ARN}
Bash
복사
# NLB에 리스너 생성(TCP 80 → 대상 그룹)
watch -d "aws elbv2 describe-target-health \ --target-group-arn ${NLB_TG_ARN} \ --query 'TargetHealthDescriptions[*].{ InstanceId:Target.Id, State:TargetHealth.State }' \ --output table"
Bash
복사
# NLB 대상 그룹에 속한 대상 확인
NLB 생성 시점으로 약 3분 정도의 대기가 필요합니다.
[PrivateLink 구성] BE-VPC에 엔드포인트 서비스 생성
EP_SVC_NAME=$(aws ec2 create-vpc-endpoint-service-configuration \ --network-load-balancer-arns ${NLB_ARN} \ --no-acceptance-required \ --query 'ServiceConfiguration.ServiceName' \ --output text) echo "export EP_SVC_NAME=${EP_SVC_NAME}" >> /etc/profile echo "EP_SVC_NAME=${EP_SVC_NAME}"
Bash
복사
# NLB에 엔드포인트 서비스 생성
[관리 콘솔 확인: VPC → PrivateLink 및 Lattice → 엔드포인트 서비스]
[PrivateLink 구성] FE-VPC에 VPC 인터페이스 엔드포인트 생성
VPCE_SG_ID=$(aws ec2 create-security-group \ --group-name cnasg-vpce-sg \ --description "CNASG Interface Endpoint SG" \ --vpc-id ${FE_VPC_ID} \ --query 'GroupId' --output text) echo "export VPCE_SG_ID=${VPCE_SG_ID}" >> /etc/profile echo "VPCE_SG_ID=${VPCE_SG_ID}"
Bash
복사
# VPC 엔드포인트 전용 보안 그룹 생성 및 ID 변수 선언
aws ec2 authorize-security-group-ingress \ --group-id ${VPCE_SG_ID} \ --protocol tcp \ --port 80 \ --source-group ${WEB1_SG_ID} aws ec2 authorize-security-group-ingress \ --group-id ${VPCE_SG_ID} \ --protocol tcp \ --port 80 \ --source-group ${WEB2_SG_ID}
Bash
복사
# 위 보안 그룹의 인바운드 규칙 추가 (tcp 80 트래픽에 대해 WEB1과 WEB2만 허용)
sgview "${VPCE_SG_ID}"
Bash
복사
# VPCE 보안 그룹 정보 확인
VPCE_ID=$(aws ec2 create-vpc-endpoint \ --vpc-id ${FE_VPC_ID} \ --vpc-endpoint-type Interface \ --service-name ${EP_SVC_NAME} \ --subnet-ids ${FE_PRIVATE_SUBNET_IDS} \ --security-group-ids ${VPCE_SG_ID} \ --query 'VpcEndpoint.VpcEndpointId' \ --output text) echo "export VPCE_ID=${VPCE_ID}" >> /etc/profile echo "VPCE_ID=${VPCE_ID}"
Bash
복사
# VPC 인터페이스 엔드포인트 생성 (FE-VPC의 프라이빗 서브넷)
VPCE_DNS=$(aws ec2 describe-vpc-endpoints \ --vpc-endpoint-ids ${VPCE_ID} \ --query 'VpcEndpoints[0].DnsEntries[0].DnsName' \ --output text) echo "export VPCE_DNS=${VPCE_DNS}" >> /etc/profile echo "VPCE_DNS=${VPCE_DNS}"
Bash
복사
# VPC 인터페이스 엔드포인트의 DNS 주소 변수 선언
[관리 콘솔 확인: VPC → PrivateLink 및 Lattice → 엔드포인트]
WEB1 / WEB2에 DB-API 수정 (PrivateLink)
sshpass -p "$WEB_SSH_PASS" ssh -T -o StrictHostKeyChecking=no ec2-user@$WEB1_IP <<EOF sudo tee /etc/httpd/conf.d/api-env.conf >/dev/null <<CONF SetEnv API_URL http://${VPCE_DNS}/products.php CONF sudo systemctl restart httpd EOF
Bash
복사
# WEB1에 API_URL 환경 변수를 선언하고 httpd 재시작
sshpass -p "$WEB_SSH_PASS" ssh -T -o StrictHostKeyChecking=no ec2-user@$WEB2_IP <<EOF sudo tee /etc/httpd/conf.d/api-env.conf >/dev/null <<CONF SetEnv API_URL http://${VPCE_DNS}/products.php CONF sudo systemctl restart httpd EOF
Bash
복사
# WEB2에 API_URL 환경 변수를 선언하고 httpd 재시작
출력된 ALB 도메인 주소로 웹 접근을 수행합니다. (HTTP)

4. 실습 환경 삭제

VPC FLowLogs 삭제
FLOWLOG_IDS=$(aws ec2 describe-flow-logs \ --filter Name=resource-id,Values=${WEB1_ENI},${WEB2_ENI} \ --query 'FlowLogs[*].FlowLogId' \ --output text) aws ec2 delete-flow-logs \ --flow-log-ids ${FLOWLOG_IDS}
Bash
복사
# VPC FlowLogs 삭제
aws logs delete-log-group \ --log-group-name ${LOG_GROUP_NAME}
Bash
복사
# CloudWatch Log Group 삭제
aws iam detach-role-policy \ --role-name VPCFlowLogsRole \ --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/VPCFlowLogsPolicy
Bash
복사
# VPC FlowLogs IAM 역할에서 정책 분리
aws iam delete-policy \ --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/VPCFlowLogsPolicy
Bash
복사
# VPC FlowLogs IAM 정책 삭제
aws iam delete-role \ --role-name VPCFlowLogsRole
Bash
복사
# VPC FlowLogs IAM 역할 삭제
PrivateLink를 위한 엔드포인트 삭제
aws ec2 delete-vpc-endpoints \ --vpc-endpoint-ids ${VPCE_ID}
Bash
복사
# VPC 엔드포인트 삭제 (FE-VPC)
watch -d "aws ec2 describe-vpc-endpoints \ --query 'VpcEndpoints[*].{ID:VpcEndpointId,State:State}' \ --output table"
Bash
복사
# VPC 엔드포인트 확인 (FE-VPC)
aws ec2 delete-security-group \ --group-id ${VPCE_SG_ID}
Bash
복사
# VPC 엔드포인트 전용 보안 그룹 삭제 (FE-VPC)
VPC 엔드포인트 삭제를 확인한 후에 보안 그룹을 삭제할 수 있습니다.
aws ec2 describe-vpc-endpoint-service-configurations \ --query 'ServiceConfigurations[*].{ServiceId:ServiceId,ServiceName:ServiceName}' \ --output table
Bash
복사
# 엔드포인트 서비스 확인 (BE-VPC)
EP_SVC_ID=$(aws ec2 describe-vpc-endpoint-service-configurations \ --query "ServiceConfigurations[?ServiceName=='${EP_SVC_NAME}'].ServiceId | [0]" \ --output text) aws ec2 delete-vpc-endpoint-service-configurations \ --service-ids ${EP_SVC_ID}
Bash
복사
# 엔드포인트 서비스 삭제 (BE-VPC)
Internal NLB 삭제 (BE-VPC)
NLB_LISTENER_ARN=$(aws elbv2 describe-listeners \ --load-balancer-arn ${NLB_ARN} \ --query 'Listeners[*].ListenerArn' \ --output text) echo ${NLB_LISTENER_ARN} aws elbv2 delete-listener \ --listener-arn ${NLB_LISTENER_ARN}
Bash
복사
# NLB 리스너 확인 및 삭제
aws elbv2 delete-load-balancer \ --load-balancer-arn ${NLB_ARN}
Bash
복사
# NLB 삭제
aws elbv2 delete-target-group \ --target-group-arn ${NLB_TG_ARN}
Bash
복사
# NLB 대상 그룹 삭제
AWS RDS 삭제
aws rds modify-db-instance \ --db-instance-identifier "${DB_ID}" \ --no-deletion-protection \ --apply-immediately
Bash
복사
# DeletionProtection 해제
aws rds describe-db-instances \ --db-instance-identifier "${DB_ID}" \ --query 'DBInstances[0].{Status:DBInstanceStatus,DeletionProtection:DeletionProtection}' \ --output table
Bash
복사
# DeletionProtection 확인
aws rds delete-db-instance \ --db-instance-identifier "${DB_ID}" \ --skip-final-snapshot \ --delete-automated-backups
Bash
복사
# DB 인스턴스 삭제
watch -d "aws rds describe-db-instances \ --db-instance-identifier ${DB_ID} \ --query 'DBInstances[0].DBInstanceStatus' \ --output text"
Bash
복사
# DB 인스턴스 확인
DB 인스턴스 삭제는 약 2분 정도 대기가 필요합니다.
aws rds delete-db-subnet-group \ --db-subnet-group-name "${RDS_SN_GROUP}"
Bash
복사
# RDS DB 서브넷 그룹 삭제
Terraform 자원 삭제
nohup sh -c "terraform destroy -auto-approve" > delete.log 2>&1 &
Bash
복사
# terraform 자원 삭제
Note:  Terraform 자원 삭제가 완료되면(약 3분 정도 대기) 정상적으로 자원 삭제가 되었는지 확인을 합니다.(cat delete.log)
여기까지 섹션 08 실습 - Amazon VPC 보안 구성을 마칩니다.
수고하셨습니다 :)