목차


들어가기: 더 좋은 방법

더 좋은 것이 있으면 그걸로!

대학에 다닐 때 영어구문론(English Syntax) 과목을 수강한 적이 있습니다. (전공은 컴공인데, 타과 과목도 조금 들었습니다.) 담당 교수님은, 자신은 시험 답안을 채점할 때, 차례대로 점수를 매기다가 앞의 것보다 더 좋은 답안을 쓴 답안지가 있으면 다시 처음으로 돌아가서 먼저 번에 높은 점수를 줬던 답안의 점수를 한 단계씩 내리면서 등수를 정한다고 말씀하셨습니다.(=내가 여러 번 검토했으니 이의제기 하지 말라는!) 그래서 ‘아아 그러시구나…’ 했는데, 성적을 받아보니 딱 A+였죠. (자랑? 지금 그걸로 먹고 살지도 않아서 의미 없음)

이와 같이, 이전에 좋았던 방법도 더 좋은 방법이 나오면 대체를 하는 법입니다.

불과 한 달 전에 AWS Client VPN 서비스가 서울 리전에 오픈 되어, 제가 AWS Client VPN Endpoint 사용하기 글을 통해 소개했습니다.

이제 VPN만 있으면 나름 FM대로 네트워크 보안을 준수하면서 AWS를 사용할 수 있겠다고 생각했지만, 계산해보니 비용도 무지막지하게 나오고, 무엇보다도 사용이 편리하질 않았습니다. (위 블로그 게시물 한 번 읽어보세요.)

그래서 물어봤지요. AWSKRUG(AWS 한국 사용자 그룹) 슬랙 채널에,

Q. AWS Client VPN Endpoint를 이용하지 않고, 회사 사무실에서 VPC Private Subnet에 있는 EC2나 RDS 인스턴스에 직접 액세스할 수 있는 다른 방법이 있을까요?

(AWS Client VPN이 나오면 해결될 거라고 생각했는데, 요금이 무시못할 수준이네요.)

몇몇 분들이 아래와 같은 답을 주셨습니다.

A. AWS Systems Manager(SSM)를 사용해보세요.

그래서 사용을 해봤습니다. 확실히 EC2 인스턴스에 Key Pair를 생성하고 SSH를 통해 접속하는 것 보다 더 나은 방법이라는 것을 알게 되었습니다. 그래서 다른 분들에게도 알려드리려고 이렇게 기록을 남김니다.

그렇게 우린 더 나은 방법을 찾았습니다. (늘 그랬듯이…)


대상 독자

  • AWS VPC - Private Subnet에 있는 호스트를 회사 사무실에서 쉽게 접근할 수 있도록 고민하셨던 분
  • Client VPN이 서울 리전에 풀렸을 때, ‘그래 이거다!’ 했다가 비싼 요금에 좌절하셨던 분
  • AWS에 권장하는 EC2 인스턴스 관리 프랙티스를 따르고 싶은 개발자, 관리자 혹은 DevOps


SSM: AWS Systems Manager

AWS Systems Manager (왜 ASM이 아닌 걸까? 혹시 SystemS Manager인가? 그것도 이상해)

원격 호스트 접속 방법 비교: SSH (기존) vs. SSM (개선)

image

위 그림은 AWS VPC의 Private Subnet의 리소스에 접근하기 위한 두 가지 방법(기존 vs. 개선)을 나타낸 것입니다.

왼쪽은 기존 SSH를 이용해 Bastion Host를 경유하여 Private Subnet에 도달하는 방법이고, 오른쪽은 AWS SSM을 이용해 AWS Systems Manager를 거쳐서 가는 방법입니다.

SSH와 Bastion Host를 이용한 방법 (기존)

잘 알고 있는 일반적인 방법입니다. 이 방법을 위해서 필요한 EC2 외의 추가 리소스는 다음과 같습니다.

  • Public Subnet에 호스팅된 Bastion Host (EC2)
  • Bastion Host에 SSH(22) 접근을 허용하는 보안 그룹 (Security Group)
  • SSH 접속 인증을 위한 EC2 Key Pair (EC2 생성시)

그리고 이렇게 접근하면 됩니다. (Windows PC 사용자는 Putty 이용)

$ ssh -i <keypair 경로> <사용자>@<호스트 주소>

Bastion Host는 같은 VPC 네트워크의 Private Subnet에 있는 호스트에 접근할 수 있습니다.

  • Bastion Host에서 Private Subnet에 있는 EC2 인스턴스에 SSH 연결
  • SSH Tunneling으로 Private Subnet에 있는 RDS 인스턴스에 연결
    • (SSH Tunneling은 MySQL Workbench나 기타 DB 관리 도구의 연결 설정에 가보면 볼 수 있지요?)
  • SSH의 Port Forwarding 기능을 통해 Private Subnet 호스트의 포트를 로컬 포트에 바인딩
    • 예를 들면, 원격 3306(MySQL) 포트를 로컬 3306 포트로 바인딩하면, SSH가 연결된 동안 localhost:3306으로 MySQL 서버 인스턴스에 연결 가능
    • $ ssh -L 3306:<RDS Instance Host>:3306 ...

SSH 방법(기존)의 단점

SSH를 이용해 AWS EC2 인스턴스에 접속하는 방법에는 아래와 같은 단점이 있습니다.

  • VPC 외부에서 Private Subnet에 접근하기 위해 Bastion Host를 경유해야 한다.
    • 즉 Bastion Host 인스턴스(EC2)를 따로 만들어야 한다.
  • 그 Bastion Host에 접속할 때 사용할 X.509 Key Pair를 생성해야 한다.
    • 원래 FM대로라면 EC2 인스턴스 마다 키를 별도로 만들어야 합니다. 그리고 그것은 관리자 단 한 사람을 위한 것입니다.
    • 그런데, 전사적으로 Key Pair를 딱 하나만 만들어놓고는 EC2를 만들 때마다 같은 걸로 등록하고, 모든 사용자에게 키를 공유하고 돌려가면서 쓴다든지 하기 십상입니다. (완전 AM이죠.)
  • (진짜 FM대로 한다면,) 접속하는 사용자마다 adduser를 하고, ~/.ssh/authorized_keys를 설정해야 한다.
    • 귀차니즘 이러니까 다들 그냥 가라로 하지 (…)
    • 하지만, EC2에 개나소나 들어가는 건 더 이상하다.
  • SSH 연결을 허용하기 위한 보안 그룹(Security Group) 생성
    • 적절한 IP 대역에 22(SSH) 포트를 허용해줍니다. (Bastion Host는 Public 네트워크에 있으므로 최대한 보호해야 합니다.)

AWS SSM을 사용하면 이런 거 안 해도 됩니다.

SSM을 이용한 방법(개선)

SSM을 사용해서 호스트에 접속하는 방법은 상대적으로 아래와 같은 장점이 있습니다.

  • Bastion Host가 필요없다.
  • Key Pair가 필요없다.
  • Security Group + Rule이 필요 없다.
  • 그럼에도 불구하고, SSH로 할 수 있는 건 다 할 수 있다.
  • Private Subnet에 있는 EC2 인스턴스에 바로 접속할 수 있다. (마치 VPN에 있는 것처럼)
  • AWS Client VPN을 사용하는 것에 비해서 비용이 적게 든다. (돈 + 수고로움)

위의 그림을 다시 살펴봅시다.

우선, Bastion Host가 필요 없습니다.

ssh 명령으로 Bastion Host에 접속하는 대신, AWS CLI의 ssm 서비스의 start-session 명령으로 AWS Systems Manager에 연결합니다. (생각에 따라서 SSM이 Bastion Host를 대신한다고 볼 수도 있습니다.)

SSM은 HTTPS 프로토콜을 사용합니다. 따라서 Key Pair도 만들 필요가 없고, 포트 허용을 위해 Security Group을 만들 필요도 없습니다.

그럼 인증은 뭘로 하느냐? AWS CLI를 사용하기 위해 등록한 AWS Credentials를 사용합니다.

AWS Credentials은 팀원 각 사람마다 IAM User를 만들어서 발급해주셨겠지요?

이제 직접 한 번 해보겠습니다. (실습으로)


[실습] AWS System Manager 환경 구성하기

이미 설치되어 있더라도, 최신 버전으로 업그레이드하는게 좋습니다.

  • AWS CLI 설치 (macOS 사용자)
    • $ brew install awscli
  • AWS Session Manager Plugin 설치 (macOS 사용자)
    • $ brew cask install session-manager-plugin 전에는 이거였는데, 아래와 같이 바뀌었습니다.
    • $ brew tap dkanejs/aws-session-manager-plugin
    • $ brew install aws-session-manager-plugin

참고

타 OS 사용자는 이거 보세요.


[실습] SSM Agent 실행을 위해 EC2 인스턴스 역할에 AmazonSSMManagedInstanceCore 정책 연결하기

EC2 인스턴스를 생성할 때 사용하는 AMI의 최신 버전에는 SSM Agent가 포함되어 있습니다.

AMI를 선택할 때, 리눅스 배포판 어떤 걸 쓸지 고민 되면 그냥 Amazon Linux 2를 쓰십시오. 가장 무난합니다.

SSM Agent가 자동으로 설치되기는 하지만, 해당 EC2 인스턴스가 AWS Systems Manager에 접근하려면 권한이 있어야 합니다.

이를 위해 EC2 인스턴스에 설정된 역할에 AmazonSSMManagedInstanceCore 정책을 연결합니다. (연결한다는 말 무슨 뜻인지 아시죠?)

AmazonSSMManagedInstanceCore 정책은 관리형 정책이라서 IAM에 이미 만들어져 있습니다.

만약, 기존에 정책을 연결하지 않았다가 나중에 추가했다면, SSM Agent 서비스를 재시작 해야합니다.


[실습] SSM 사용을 위해 사용자 역할에 IAM 정책 연결

IAM 사용자에게 특정 EC2 인스턴스에 접속(ssm start-session 명령을 실행)할 수 있는 권한을 부여합니다.

관리자라면 모든 게 다 허용되는 AdministratorAccess 역할을 사용할 수도 있습니다.

하지만 팀원들에게는 업무상 관련있는 EC2 인스턴스에만 연결을 허용하는게 좋겠지요.

다음은 가장 간단한 IAM 정책 작성 예제입니다. (기타 예제는 아래 참고를 보세요.)

예제1: 특정 인스턴스에 대한 액세스 제한

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:StartSession"
            ],
            "Resource": [
                "arn:aws:ec2:<리전>:<AWS Account>:instance/i-xxx",
                "arn:aws:ec2:<리전>:<AWS Account>:instance/i-yyy",
                "arn:aws:ec2:<리전>:<AWS Account>:instance/i-zzz"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:TerminateSession"
            ],
            "Resource": [
                "arn:aws:ssm:*:*:session/${aws:username}-*"
            ]
        }
    ]
}

위 정책의 내용은 이렇습니다.

  • 이 정책과 연결된 보안 주체는,
    • i-xxx, i-yyy, i-zzz EC2 인스턴스에 대해서 $ aws ssm start-session을 할 수 있다. (ssm:StartSession)
    • $ aws ssm start-session 해서 로그인한 세션에서 로그아웃 할 수 있다. (ssm:TerminateSession)
      • (세션 이름은 {IAM 사용자 ID}-{세션 UID}로 만들어집니다.)
      • 즉 자신이 로그인한 세션을 닫을 권한

참고


[실습] SSM으로 EC2 인스턴스에 연결

이제 다음과 같이 SSM으로 EC2 인스턴스에 연결할 수 있습니다. (세션 시작)

$ aws ssm start-session --target <EC2 인스턴스 ID>

연결되면 SSH에 연결한 것과 같이 쉘이 뜹니다.

sh-4.2$

기본 로그인 쉘은 sh입니다. (불편하면 bash를 실행하세요.)

사용자는 ssm-user 입니다.

sh-4.2$ whoami
ssm-user

(SSH는 아니지만) SSH로 쉘에 로그인 한 것과 거의 똑같습니다. 이제부터 알아서 하시면 됩니다.


트러블슈팅

혹시 안 되면 에러 메시지를 잘 보고 아래 유형에 해당하는지 살펴보세요.


TargetNotConnected

An error occurred (TargetNotConnected) when calling the StartSession operation: i-xxx... is not connected.

가장 많이 볼 수 있는 경우

이 오류가 출력되는 원인은 다음과 같습니다.


AccessDeniedException

An error occurred (AccessDeniedException) when calling the StartSession operation: User: arn:aws:iam::<AWS Account>:user/<IAM User> is not authorized to perform: ssm:StartSession on resource: arn:aws:ec2:<region>:<AWS Account>:instance/<EC2 인스턴스 ID>

IAM 사용자에 ssm:StartSession 권한이 없습니다.

[실습] SSM 사용을 위해 IAM 정책 연결 참조


UnrecognizedClientException

An error occurred (UnrecognizedClientException) when calling the StartSession operation: The security token included in the request is invalid.

AWS Credentials가 잘못되었으니 aws configure를 다시 해보십시오.


ERROR: Unauthorized request

SessionId: xxx-zzzzz... : 
----------ERROR-------

Setting up data channel with id xxx-zzzzz... failed: failed to create websocket for datachannel with error: CreateDataChannel failed with no output or error: createDataChannel request failed: unexpected response from the service <BadRequest xmlns=""><message>Unauthorized request.</message></BadRequest>

  • 원인:
    • EC2 인스턴스의 역할에 AmazonSSMManagedInstanceCore 정책이 연결되어 있지 않아서 SSM Agent가 정상적으로 동작하지 않습니다.
  • 해결:
    • EC2 인스턴스의 역할에 AmazonSSMManagedInstanceCore (관리형) 정책을 연결하세요.
    • 이때 SSM Agent를 반드시 재시작 해야합니다.


ERROR: createDataChannel

SessionId: xxx-zzzzz... : 
----------ERROR-------

Setting up data channel with id xxx-zzzzz... failed: failed to create websocket for datachannel with error: CreateDataChannel failed with no output or error: createDataChannel request failed: failed to sign the request: EC2RoleRequestError: no EC2 instance role found

caused by: EC2MetadataError: failed to make EC2Metadata request
caused by: <?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
  <title>404 - Not Found</title>
 </head>
 <body>
  <h1>404 - Not Found</h1>
 </body>
</html>

ERROR: Unauthorized request 경우와 같은데, EC2 인스턴스에 아무 역할을 주지 않으면 이런 오류가 나옵니다.


이도 저도 안 되면…

안 되면 될 때까지!


[실습] SSH처럼 위장하기 + 포트 포워딩 (고급)

기존에 ssh를 사용하셨던 분들을 위해, 아예 ssh로 접속하는 것처럼 위장하는 고급 사용법입니다.


~/.ssh/config 설정하기

SSH를 사용하는 일반적인 방법은 아래와 같습니다.

$ ssh -i ~/.ssh/my-ec2.pem ec2-user@ec2-12-34-56-78.ap-northeast-2.compute.amazonaws.com

그런데 ~/.ssh/config 파일에 alias를 설정해놓으면 더 간단하게 할 수 있습니다.

# ~/.ssh/config 파일 내용

Host my-ec2
    HostName ec2-12-34-56-78.ap-northeast-2.compute.amazonaws.com
    User ec2-user
    IdentityFile ~/.ssh/my-ec2.pem

그리고,

$ ssh my-ec2

간단하지요?

다 아는 거 굳이 설명드려 죄송합니다.


SSH 사용하는 것처럼 SSM 사용하기

SSM으로 EC2 인스턴스에 접속하면 마치 SSH로 연결한 것과 같이 쉘 프롬프트가 뜹니다.

이제 그냥 아예 SSH로 접속하는 것처럼 바꿔보겠습니다.

# ~/.ssh/config

Host i-* mi-*
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
    User ssm-user
    IdentityFile ~/.ssh/my-ec2.pem

그냥 SSM만 사용할 때는 IdentityFile이 필요 없었지만, SSH처럼 사용하기 위해서는 X.509 개인키 파일을 Identity File로 등록해야 합니다.

만약 EC2 인스턴스를 만들 때 같이 생성한 Key Pair가 있으면 그걸 사용하시면 됩니다. 혹시 미리 Key Pair를 생성하지 않았더라도, 사후에 Key Pair를 생성한 후에 Public Key를 EC2 인스턴스의 /home/ssm-user/.ssh/authorized_keys에 추가하면 됩니다.

ssm-user 말고 다른 사용자를 사용하고 싶으면 위에서 ssm-user가 들어가는 자리에 다른 사용자를 넣으면 됩니다.

그리고 이렇게 접속합니다. (여기서 i-xxx…는 EC2 인스턴스 ID를 가리킵니다.)

$ ssh i-xxx...


포트 포워딩 1 (SSH 방식으로)

SSH에는 포트 포워딩 기능이 있습니다.

The How To Of Port Forwarding With SSH

위에서 SSM을 SSH로 위장해서 사용하도록 했으므로 이제 기존에 SSH를 쓰던 방법대로 포트 포워딩 옵션을 추가하면 됩니다.

$ ssh -L 3306:my-rds.xxxxx.ap-northeast-2.rds.amazonaws.com:3306 i-xxx...

이렇게 EC2 인스턴스에서 원격 호스트 포트를 바인딩하도록 설정할 수 있습니다.

SSM으로 RDS 인스턴스에 바로 접속할 수는 없지만, RDS 인스턴스에 접근가능한 EC2 인스턴스에서 포트 포워딩을 통해 RDS 인스턴스에 접근할 수 있습니다.

포트 포워딩을 사용하면 위와 같이 ssh 세션이 유지되는 동안, 로컬에서 개발할 때 localhost:3306를 사용해서 DB에 접근이 가능하게 됩니다.

내부 자원은 Private Subnet에

제가 이전에 진작 이 방법을 몰라서 개발 환경 RDS 인스턴스를 Public Subnet에 생성해놓고 사용했습니다. (로컬에서 접근할 수 있어야 하므로 인터넷에 연결되도록) 원래 DB 인스턴스는 Private Subnet에 있는 게 FM입니다. DB도 그렇고, 인터넷에서 요청을 접수하는 웹 서버나 로드 밸런서를 제외하면 나머지는 대개 Private Subnet에 호스팅해야 안전합니다. (Private Subnet을 더 세분화해서 Outbound를 차단한 Isolated Subnet이라는 개념도 있습니다. DB 인스턴스와 같이 외부로 향하는 요청을 발생하지 않는 리소스를 배치하는데 적합합니다.) 그러니까 이제 RDS 인스턴스를 생성할 때 인터넷 액세스 가능 옵션을 활성화하지 않아도 됩니다.

아쉬운 것은, 포트 포워딩이 되서 좋긴 좋은데, Key Pair를 생성하지 않아도 되었던 SSM의 장점이 사라져 버렸습니다.

좀 더 쉽게 할 수는 없는 걸까요?


포트 포워딩 2 (SSM 방식으로, 단 원격 호스트로 포트 포워딩 안됨)

원래 SSM에도 자체 포트 포워딩 기능이 있습니다. 그러면 Key Pair를 생성하고 등록하지 않아도 포트 포워딩이 가능하다는 말이죠.

$ aws ssm start-session --target i-xxx... --document AWS-StartPortForwardingSession --parameters '{"portNumber":["4000"], "localPortNumber":["3000"]}'

이렇게 하면 EC2 인스턴스 localhost:4000를 내 컴퓨터의 localhost:3000로 바인딩합니다.

다만 현재는 대상 EC2 인스턴스의 localhost의 포트만 포워딩할 수 있습니다. (아쉬운 제한)

즉, $ ssh -L 3306:localhost:3306와 같이, 가운데 대상 호스트를 입력하는 부분이 localhost로 고정됩니다.

그래서 참 아쉽습니다. 그런데 이미 저와 같이 아쉬워 하는 사람들이 많이 있었습니다.

issues/208: Feature request: Forward to remote port

이렇게 좀 쓰게 해달라고요:

$ aws ssm start-session --target i-xxx... --document AWS-StartPortForwardingSession --parameters '{"host": "my-rds.xxxxx.ap-northeast-2.rds.amazonaws.com", "portNumber":["3306"], "localPortNumber":["3306"]}'

이것과 같습니다.

$ ssh -L 3306:my-rds.xxxxx.ap-northeast-2.rds.amazonaws.com:3306

이렇게 되면 정말 더 쓸만해지겠군요.

이 기능 요청이 언제 승인이 될지 기다려집니다.

AWS, 빨리 안 해주고 뭐합니까?!


요약

  • Private Subnet에 리소스를 배치하는 것이 Public Subnet에 두는 것 보다 안전하다.
  • 대신 Private Subnet은 AWS 네트워크 바깥에서 접근하기가 까다롭다. (Bastion Host 혹은 VPN 필요)
  • 그래서 AWS Client VPN으로 VPC로 들어가면 되겠다고 생각했는데, Client VPN은 무지 비싸고 쓰기 힘들었다.
  • SSH 비슷한 AWS SSM(Systems Manager)이라는 서비스가 있다.
  • AWS SSM은 AWS Client VPN 보다 비용이 덜 든다.
  • AWS EC2 인스턴스에 접근할 때, SSH 보다 AWS SSM을 쓰는 것이 더 간단하다.
  • SSM은 SSH 기능을 다 포함하고, 포트 포워딩도 된다.
  • 더 좋은 방법이 기존 방법을 대체하는 것은 세상의 이치, 그러므로 SSM을 쓰자.


당부의 말씀

이 문서에서 AWS SSM에 관해 튜토리얼 형식으로 간단하게 소개를 해드렸습니다.

더 자세한 내용은 아래 AWS 설명서를 참조하시기 바랍니다.

AWS Systems Manager 설정

혹시 더 궁금하신 점이나, 내용에 오류가 있어서 지적할 부분이 있으면 코멘트 남겨주십시오.

감사합니다. (유용했다면 널리 공유 바랍니다.)


References