티스토리 뷰
왜 22번 포트는 더 이상 SG에 한 줄도 둘 필요가 없는가 — SSM Session Manager로 Bastion을 완전히 지운 운영 모델
ebson 2026. 5. 11. 21:51들어가며
Bastion 호스트는 오랫동안 사설망의 운영 셸을 위한 가장 짧은 답이었습니다. 사설 서브넷에 떠 있는 워크로드에 누군가가 들어가야 할 일은 늘 생기고, 그 누군가가 IGW나 NAT를 거치지 않으면서도 셸을 잡을 수 있는 자리가 필요했습니다. 그래서 22번 포트를 한 호스트에만 열어 두고, 그 호스트를 통해 안쪽으로 다시 들어가는 결의 토폴로지가 자연스럽게 자리 잡았습니다. 호텔로 비유하자면 정문 앞에 따로 두는 보안 부스 같은 자리였고, 그 부스에서 키와 신분증을 받아 안쪽 객실로 들어가는 결의 운영이었던 셈입니다.
이 글은 그 부스가 더 이상 필요하지 않은 자리, 즉 SSM Session Manager로 셸을 잡는 결정이 비교적 평이하게 성립하는 자리를 정리해 보려는 글입니다. 이 글은 실제 트래픽이 흐르는 운영 단계의 회고가 아니라, 개인 프로젝트로 VPC를 설계하고 IaC로 구축하는 단계에서 검토한 가정과 회계를 정리하는 글이라는 점을 먼저 밝혀 둡니다. 사실관계는 가능한 한 AWS 공식 문서가 안내하는 범위 안에서만 다루고, 그 밖의 결은 결정의 근거로 적어 두는 정도로만 두려 합니다. 글의 척추는 단순합니다. Bastion이 만들어 두던 운영 부채의 정체를 한 번 짚어 보고, 22번 포트를 한 줄도 두지 않으면서도 셸이 열리는 원리를 따라가 본 다음, 진입 자체를 IAM Condition으로 잠그고 모든 세션이 자동으로 감사 로그에 남는 자리까지 한 번에 회계해 보려는 글입니다.
핵심 정리. SSM Session Manager는 SG의 인바운드 22번 포트를 한 줄도 요구하지 않으면서 셸을 제공합니다. CIS AWS Foundations Benchmark가 권고하는 "원격 관리 포트 0.0.0.0/0 금지"가 베스트프랙티스가 아니라 기본값으로 굳어지는 자리이고, IAM Condition으로 진입의 두 자물쇠를 동시에 걸어 둘 수 있는 자리이기도 합니다.
Bastion이라는 운영 부채 — 키, 패치, 감사의 누적
Bastion 호스트가 만든 운영의 무게를 한 줄로 줄여 보면, 세 자리에서 결정 회계가 누적된다는 점이 가장 먼저 떠오릅니다. 첫 자리는 SSH 키의 관리입니다. 누가 어떤 키로 들어왔는지, 어떤 키가 사용 중이고 어떤 키가 만료되었는지를 사람이 추적하는 결은 늘 사고의 자리를 한 자리씩 남깁니다. 키 한 장이 잘못된 자리에 복사되거나, 떠난 동료의 키가 그대로 남아 있거나, 키 회전의 흐름이 어딘가에서 끊기는 자리에서 사고가 자라납니다. 키의 양이 늘어날수록 추적의 비용은 선형이 아니라 점차 가속하는 결로 다가옵니다.
둘째 자리는 호스트 자체의 패치입니다. Bastion은 22번 포트를 인터넷 쪽으로 한 줄이라도 열어 두어야 의미가 있고, 그 한 줄이 열려 있는 한 호스트는 외부 공격면을 짊어진 자리에 서 있습니다. OpenSSH, 베이스 OS, 사이드 도구들의 패치 주기를 따라가야 하고, 패치가 늦어지면 알려진 취약점의 누적이 그대로 위험으로 환산됩니다. "단지 점프 호스트일 뿐인데"라는 인식으로 패치가 미뤄지는 결의 사고는 사내망 운영에서도 자주 마주쳤던 자리이기도 합니다.
셋째 자리는 감사의 자리입니다. 누가 언제 어떤 명령을 실행했는지를 추적하려면 별도의 로그 수집과 보관 체계를 갖춰야 했습니다. auditd나 auth.log를 모아 두는 결의 운영이 따라붙었고, 그 로그를 외부 저장소로 옮기고 보존 기간을 지키는 결정이 또 한 자리에 자리 잡았습니다. 점프 호스트 한 대를 두기 위해 함께 따라오는 자리가 적어도 세 자리는 되는 셈이라고 회계해 두면 결이 잘 맞습니다.
이 세 자리가 합쳐졌을 때 Bastion은 비용 도구라기보다 운영 부채에 가까운 자리가 됩니다. "관리자가 들어가야 하니까 22번은 열어 둘 수밖에 없잖아요"라는 한 줄로 변호되어 왔지만, 같은 결의 결정을 AWS 매니지드 컴포넌트로 옮겨 두면 키와 패치와 감사라는 세 자리의 부채가 한꺼번에 사라지는 자리에 들어서게 됩니다. 그 자리가 바로 SSM Session Manager라고 정리해 두려 합니다.
22번 포트 없이 셸이 열리는 원리 — SSM Session Manager의 통신 모델
AWS 공식 문서가 안내하는 SSM Session Manager의 통신 모델은 22번 포트를 인바운드로 열지 않으면서도 셸이 열리는 결의 자리를 만들어 줍니다. 모델의 핵심은 통신의 방향이 바뀐다는 점에 있습니다. 전통적인 SSH는 사용자가 외부에서 호스트의 22번 포트로 들어오는 결의 인바운드 트래픽인 반면, SSM 모델은 호스트(또는 컨테이너) 안에서 SSM 에이전트가 SSM 엔드포인트로 아웃바운드 연결을 먼저 만들어 두고, 사용자는 IAM 인증을 거쳐 콘솔이나 CLI에서 그 연결 위로 세션을 얹는 결로 동작합니다. 인바운드 22번 포트가 처음부터 필요하지 않은 구조라고 정리할 수 있습니다.
SSM 에이전트가 닿아야 하는 자리는 세 가지 SSM API 엔드포인트입니다. AWS 공식 문서가 안내하는 바에 따르면 ssm, ssmmessages, ec2messages 세 엔드포인트가 SSM의 통신을 담당하며, VPC 안에서 인터넷을 거치지 않고 이 자리들로 닿으려면 같은 이름의 Interface Endpoint를 서브넷에 두어 두는 결로 설계하게 됩니다. ssm은 일반 API 호출, ssmmessages는 Session Manager의 양방향 메시지 채널, ec2messages는 SSM 에이전트와 SSM 서비스 사이의 메시지 전달에 사용됩니다. 본 프로젝트에서는 이 세 자리에 Interface Endpoint를 두고, NAT를 거치지 않으면서도 SSM 모델이 완전히 사설망 안에서 닫히도록 설계해 두었습니다.
사용자가 세션을 시작하는 결은 비교적 간단합니다. AWS 공식 문서가 안내하는 표준 CLI 형태는 다음과 같습니다.
aws ssm start-session --target i-0123456789abcdef0
이 한 줄이면 사설 서브넷에 떠 있는 EC2 인스턴스로 셸이 열립니다. 호스트의 SG에는 22번 포트의 인바운드 룰이 한 줄도 들어 있지 않고, NACL에도 22번 포트가 열려 있지 않습니다. 사용자가 보유한 자격 증명이 ssm:StartSession을 호출할 권한을 가지고 있는가, 그리고 호스트가 SSM 에이전트와 인스턴스 프로파일을 통해 SSM에 닿을 수 있는가 — 두 자리만 만족되면 셸이 열리는 결입니다. 자격 증명과 인스턴스 프로파일이 결합된 형태이므로, SSH 키 한 장과 비교하면 운영의 결정 회계가 한결 단순해진다고 봤습니다.
본 시리즈가 앞서 따라간 PrivateLink와 SG/NACL의 결을 떠올려 보면, SSM Session Manager는 같은 결의 자연스러운 연장이라는 점도 보입니다. PrivateLink가 트래픽을 AWS 백본 안에 가두는 자리를 만들었다면, SSM은 그 안에서 셸 접속이라는 가장 민감한 트래픽이 인터넷을 보지 않고도 성립하는 자리를 만들어 주는 셈입니다. 22번 포트가 SG에 한 줄도 들어 있지 않으면서도 운영이 멈추지 않는 자리는, 이 두 도구의 결이 합쳐졌을 때 비로소 평이해진다고 정리할 수 있을 것 같습니다.
Fargate에서의 같은 모델 — ecs execute-command
컨테이너 워크로드, 특히 Fargate로 떠 있는 Task에 셸이 필요한 자리에서도 결은 같습니다. AWS 공식 문서가 안내하는 ECS Exec 기능은 내부적으로 SSM Session Manager를 그대로 사용하며, 같은 결의 통신 모델 위에서 컨테이너 안으로 셸을 잡을 수 있게 해 줍니다. 공식 문서를 풀어 옮기면, ECS Exec은 실행 중인 컨테이너와의 연결을 SSM Session Manager로 수립하고, 그 진입 자체에 대한 통제는 IAM 정책으로 표현하도록 설계된 기능이라는 결로 정리됩니다. 한 도구의 통신 모델과 한 도구의 정책 모델이 각자의 자리를 그대로 빌려 쓰는 결이라고 봤습니다.
기술적인 흐름은 SSM의 결을 그대로 빌려 옵니다. ECS 에이전트가 SSM 에이전트의 바이너리를 컨테이너 안으로 bind-mount하고, 그 SSM 에이전트가 컨테이너 안에서 SSM 엔드포인트로 아웃바운드 연결을 만들어 둡니다. 사용자가 aws ecs execute-command를 호출하면 IAM 정책이 평가되고, 허용된 자리에서만 그 연결 위로 셸 세션이 얹힙니다. Fargate에서도 호스트의 22번 포트를 한 줄도 열어 두지 않는 구조라는 점이, EC2 기반의 SSM 모델과 결이 같다고 봤습니다.
ECS Exec을 사용하려면 Task가 띄워질 때 --enable-execute-command 플래그가 켜져 있어야 한다는 점, 그리고 Task IAM Role에 SSM Session Manager용 권한이 부여되어 있어야 한다는 점이 함께 따라옵니다. 본 프로젝트에서는 서비스 생성 시 이 플래그를 IaC 정의에 함께 박아 두는 결로 설계했고, 새 Task가 띄워질 때 별도의 결정 없이 Exec이 가능하도록 흐름을 묶어 두었습니다. AWS 공식 문서가 안내하는 표준 CLI 형태는 다음과 같습니다.
aws ecs execute-command \
--cluster prod-cluster \
--task <task-id> \
--container <container-name> \
--interactive \
--command "/bin/sh"
한 가지 짚어 두자면, Fargate Task의 네트워크 모드는 awsvpc이며 사설 서브넷에서 인터넷을 거치지 않으려면 적어도 ssmmessages Interface Endpoint가 같은 자리에 있어야 합니다. AWS 공식 문서가 안내하는 ECS Exec 고려 사항이 이 자리를 분명히 짚어 두고 있고, KMS로 세션 데이터를 자체 키로 암호화하는 결정을 함께 가져갈 경우에는 KMS Interface Endpoint도 같은 자리에 두는 결이 따라옵니다. 작은 자리지만 처음 ECS Exec을 켤 때 한 번씩 빠뜨리기 쉬운 자리이기도 해서, 본 시리즈가 함께 다루는 "PrivateLink로 인터넷을 거치지 않는 결정"과 결이 자연스럽게 맞물리는 자리로 정리해 두면 좋겠습니다.
진입 자체를 잠그는 자리 — IAM Condition의 두 자물쇠
22번 포트를 SG에서 지워 두는 결정이 운영의 무게를 가볍게 만들어 주는 것은 분명하지만, 그 결정만으로 진입 자체가 안전해지는 것은 아닙니다. SSM Session Manager는 IAM이라는 자격 증명의 결을 그대로 빌려 오므로, 자격 증명이 새어 나가는 결의 사고가 일어나면 호스트의 22번 포트가 닫혀 있다는 사실이 단독으로는 충분한 방어가 되지 않습니다. 진입 자체를 잠그는 자리에 한 겹을 더 두는 결정이 그래서 함께 따라옵니다.
본 프로젝트에서는 두 가지 IAM Condition을 함께 묶어 두는 결로 설계해 두었습니다. 첫 자리는 aws:SourceIp 조건 키입니다. AWS 공식 문서가 안내하는 글로벌 조건 키 중 하나이며, 호출의 원천 IP가 지정한 CIDR 범위 안에 있을 때만 정책의 평가가 허용으로 이어집니다. 본 프로젝트에서는 회사 VPN의 출구 CIDR을 이 조건의 값으로 두고, VPN을 거치지 않은 자리에서는 ssm:StartSession이나 ecs:ExecuteCommand가 평가 단계에서 거부되도록 설계해 두었습니다. 사내망에서 점프 호스트를 거쳐 들어가던 결을 클라우드에서 그대로 재현하는 자리라고 봤습니다.
둘째 자리는 aws:MultiFactorAuthPresent 조건 키입니다. 같은 글로벌 조건 키 가운데 하나이며, 호출 시점에 MFA 인증이 함께 제공되었을 때만 평가가 허용으로 이어집니다. 자격 증명의 결정 회계가 단일 인자(예: 액세스 키)에 의존하지 않고, MFA라는 두 번째 인자가 함께 결합되었을 때만 진입이 성립하도록 잠그는 결입니다. 두 조건을 같은 정책 안에서 결합하면, 진입의 결정이 네트워크 경계(IP)와 자격 증명(MFA) 두 자리에서 동시에 잠겨야 비로소 성립하는 결로 자리 잡습니다.
이 두 자물쇠의 자리에서 짚어 두고 싶은 결은, 본 시리즈의 다른 글들이 따라간 "한 도구의 결과로 보안이 성립하지 않는다"는 결과 같은 결이라는 점입니다. SG에서 22번이 닫혀 있다는 사실 한 줄로 진입의 안전이 성립하는 것이 아니라, IAM의 자격 증명 결과 IAM Condition의 두 결, 그리고 SSM 자체의 감사 결이 합쳐졌을 때 비로소 진입 자체가 잠긴다고 회계해 두면 결이 잘 맞습니다. 본 프로젝트의 SG 매트릭스에 명시된 sg-prod-admin-ssm에 22번 포트의 인바운드 룰이 한 줄도 들어 있지 않다는 사실은, 그 결로 가는 출발점일 뿐이라는 인상이 강하게 남았습니다.
ECS Exec 쪽에서는 한 자리를 더 둘 수 있는 결이 따라옵니다. AWS 공식 문서가 안내하는 바에 따르면 ecs:ExecuteCommand 액션에는 ecs:cluster, ecs:container-name, ecs:ResourceTag/* 같은 서비스 고유의 조건 키가 함께 제공됩니다. 운영 환경(예: environment = production) 태그가 붙은 컨테이너에는 특정 보안 주체만 Exec을 호출할 수 있도록 좁히는 결을, 또는 특정 컨테이너 이름(예: production-app)에 대해 Exec을 명시적으로 거부하는 결을 정책 한 자리에 박아 둘 수 있습니다. IAM의 글로벌 조건과 서비스 조건이 함께 결합되었을 때, 진입의 결정 회계가 비로소 정밀하게 맞물리는 자리라고 봤습니다.
감사가 자동으로 남는 자리 — CloudTrail과 세션 로그
SSM 모델이 가진 또 한 가지 결은, 감사 자체가 별도의 운영 부담 없이 따라온다는 점입니다. AWS 공식 문서가 안내하는 바에 따르면 SSM Session Manager의 모든 세션 시작은 CloudTrail에 이벤트로 기록되며, ECS Exec의 경우에도 ExecuteCommand 이벤트가 CloudTrail에 남아 어떤 보안 주체가 어떤 시점에 어떤 컨테이너로 들어갔는지가 사후에 추적 가능한 형태로 보존됩니다. Bastion 시절에 별도의 로그 수집 체계를 갖춰야 했던 자리가, SSM 모델에서는 클라우드 매니지드 컴포넌트 안에 처음부터 들어 있는 자리로 옮겨진다는 결입니다.
세션 안에서 실행된 명령과 그 출력 역시 같은 결입니다. AWS 공식 문서가 안내하는 ECS Exec 로깅 설정을 켜 두면 세션 동안의 명령과 출력 스트림을 CloudWatch Logs의 지정한 LogGroup이나 S3 버킷으로 자동으로 전달할 수 있습니다. 본 프로젝트에서는 클러스터 단위의 executeCommandConfiguration 설정을 IaC 정의에 함께 박아 두어, 새로 띄워지는 Task의 Exec 세션이 자동으로 CloudWatch Logs로 흐르도록 흐름을 묶어 두었습니다. KMS 키로 세션 데이터를 자체 키로 추가 암호화하는 결정도 같은 자리에서 가져갈 수 있으며, 그 경우 Task IAM Role에 kms:Decrypt 권한과 호출자의 정책에 kms:GenerateDataKey 권한을 함께 두는 결로 설계가 자연스럽게 따라옵니다.
이 감사의 자리가 운영의 결을 어떻게 가볍게 만드는지는, 사고 직후의 결정 회계에서 가장 분명하게 드러납니다. 어떤 자리에서 사고가 일어났을 때 가장 먼저 묻는 질문은 "누가 언제 무엇을 했는가"이고, SSM 모델에서는 그 질문의 답이 별도의 수집 체계 없이 CloudTrail과 CloudWatch Logs의 두 자리에서 곧장 따라옵니다. Bastion 시절에는 auth.log와 auditd를 모아 두어야 답할 수 있던 질문이, 같은 결로 매니지드 자리에서 자동으로 답해지는 자리라고 정리해 두면 좋겠습니다.
여기에 한 가지 짚어 두자면, AWS 공식 문서는 ECS Exec 환경에서 ssm:StartSession 액션을 별도 경로로 호출하는 결을 권장하지 않는다는 안내도 함께 적어 두고 있습니다. ECS Exec을 우회해 SSM 세션을 직접 시작하면 ECS의 감사 흐름이 끊어지고 세션 쿼터에도 별도로 잡히는 결의 부작용이 따라오므로, Task에 대해서는 ssm:StartSession을 IAM 정책으로 명시적으로 거부해 두는 결이 베스트프랙티스로 정리되어 있습니다. 한 도구의 결을 깨끗하게 유지하기 위해 다른 도구의 진입로 한 자리를 닫아 두는 결정이라고 봤습니다.
Bastion이 그리워질까 — 일주일이면 잊혀집니다
이 글을 정리하면서 가장 크게 남은 인상은, Bastion이 만들어 두던 결정 회계가 사실은 운영 부채라는 한 줄로 정리되는 자리였다는 점이었습니다. 키와 패치와 감사라는 세 자리에 누적되던 비용이, SSM Session Manager와 ECS Exec이라는 매니지드 모델의 결로 옮겨 가는 순간 한꺼번에 사라지는 자리에 들어섰다고 느꼈습니다. 22번 포트를 SG에 한 줄도 두지 않으면서도 운영이 멈추지 않는다는 사실이, 처음에는 어색했지만 일주일 정도 운영해 보고 나면 자연스럽게 자리 잡는 결이라고 봤습니다.
체크리스트로 정리해 두면 다음 다섯 자리는 새 환경에 SSM 모델을 도입하기 전에 한 번씩 점검해 두는 편이 좋겠습니다. 호스트와 Task에 SSM 에이전트가 떠 있는가, 그리고 IAM Role(또는 인스턴스 프로파일)에 SSM 호출 권한이 부여되어 있는가. 사설 서브넷에서 인터넷을 거치지 않으려면 ssm, ssmmessages, ec2messages 세 Interface Endpoint가 같은 자리에 있는가. ECS Exec을 켜는 자리에서는 Task가 띄워질 때 --enable-execute-command 플래그가 함께 설정되어 있는가. IAM 정책에 aws:SourceIp와 aws:MultiFactorAuthPresent 두 조건이 결합되어 진입의 두 자물쇠가 함께 잠겨 있는가. 그리고 executeCommandConfiguration 설정이 켜져 있어 세션 명령과 출력이 자동으로 감사 로그로 흐르고 있는가. 다섯 자리는 모두 IaC 설계 단계에서 한 번 결정해 두면 그 뒤로는 거의 바뀌지 않는 자리이기도 합니다.
마지막으로 한 가지 결을 덧붙이자면, 22번 포트를 SG에 두지 않는 결정은 CIS AWS Foundations Benchmark의 네트워크 섹션이 권고하는 "원격 서버 관리 포트에 0.0.0.0/0 인바운드 금지" 항목과 같은 자리에 놓여 있다는 점입니다(항목 번호는 벤치마크 버전에 따라 표기가 달라질 수 있으므로 본 글에서는 단정하지 않습니다). Bastion이 그 권고를 우회하는 한 줄의 변명으로 자리 잡아 왔다면, SSM Session Manager는 그 변명 자체를 지우는 결의 도구라고 봤습니다. 베스트프랙티스가 베스트프랙티스가 아니라 기본값으로 굳어지는 자리에서, 운영 결정의 무게가 비로소 가벼워진다는 결을 작은 인사이트로 정리해 두고 싶습니다. Bastion이 그리워질까, 라는 질문에는 일주일이면 잊혀진다고 답해 두면 결이 잘 맞을 것 같습니다.
참고한 공식 문서
- AWS Systems Manager Session Manager: https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html
- AWS Systems Manager — Session Manager용 VPC Endpoint (ssm, ssmmessages, ec2messages): https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-privatelink.html
- ECS Exec (aws ecs execute-command): https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html
- AWS IAM 글로벌 조건 키 (aws:SourceIp, aws:MultiFactorAuthPresent): https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html
- CIS AWS Foundations Benchmark: https://www.cisecurity.org/benchmark/amazon_web_services
'TECH AND AI > DEVOPS' 카테고리의 다른 글
- Total
- Today
- Yesterday
- Redis 캐시 전략
- DB 인덱스 성능
- 트래픽 처리
- Cache Aside
- Enum 기반 싱글톤
- TTL 설계
- Eager Initialization
- 백엔드 성능
- 캐시와 인덱스
- 스레드 생명주기
- 캐시 장애
- Cache Avalanche
- Double-Checked Locking
- Spring Batch
- Redis vs DB
- Initialization-on-Demand Holder Idiom
- DB 트랜잭션
- Java Performance
- Redis 성능 개선
- Hot Key 문제
- 캐시 성능 비교
- Cache Penetration
- 트랜잭션 관리
- mybatis
- 백엔드 성능 설계
- 백엔드 아키텍처
- 동시성처리
- 백엔드 성능 튜닝
- InterruptedException
- spring batch 5
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |

