AWS SSM으로 하는 프라이빗 리소스 접근 제어 및 보안 감사 로깅
EC2
를 접근하기 위해서는 AWS 기준으로 Key File
을 이용하여 SSH
로 접근한다. 그렇다면, 프라이빗 서브넷(인터넷이 차단된)에 있는 EC2는 어떻게 접근할까?
이전에 사용하던 방식은 앞 단에
Bastion Host
를 두고 프록시하여 SSH 접속을 했다.
이렇게 되면 보안 상 취약한 요소가
- 키 파일 사용
- SSH 포트 개방
- Bastion Host 관리
- 서버 접속 사용자의 행동 추적이 어려움
정도가 있을 수 있다. 또, 프라이빗 서브넷에 RDS
혹은 ElastiCache
등 데이터 관련 리소스가 있다고 가정한다면, 이 리소스들도 Bastion Host를 통해 접속해야 하고, 그렇게 되면 터미널로 접속하기 때문에 보통 데이터베이스를 접속할 때 DataGrip
과 같은 데이터베이스 툴을 사용할 수 없어 굉장히 불편하다.
이런 문제나 애로사항들을
AWS SSM
으로 한 번에 해결할 수 있다.
AWS SSM을 사용하면 IAM 사용자 단위로 서버에 접속할 수 있는 권한 설정이 가능하고, 키 파일 공유를 하지 않아도 되고, SSH 포트를 개방하지 않아도 된다.
그리고 SSM Agent를 통해서 접근하는 방식이기 때문에, Bastion Host가 필요가 없어진다.
마지막으로, SSM Agent에서 발생되는 로그를 CloudWatch
로그 그룹으로 전송하고, 로그를 남겨 IAM 사용자 별로 행동 추적을 용이하게 만들어준다.
EC2 인스턴스 생성
SSM 접속 테스트용 EC2 인스턴스를 생성하도록 한다.
원래는 인터넷이 차단된 프라이빗 서브넷에 있어야 하지만, 편의를 위해 퍼블릭 서브넷에 생성한다.
기본적인 설정으로만 진행하면 되고, 네트워크 설정 부분에서 퍼블릭 IP는 할당해야 한다. 그 이유는 아래의 엔드포인트에 대한 아웃바운드 허용이 가능해야 하기 때문이다.
아래의 엔드포인트에 대한 443(HTTPS) 연결이 가능해야 하기 때문에, 보안 그룹은 인바운드 없이, SSM Agent와의 통신을 위해 아웃바운드 허용을 포트 443(HTTPS)
, 대상을 Anywhere-IPv4
로 설정한다.
- ec2messages.region.amazonaws.com
- ssm.region.amazonaws.com
- ssmmessages.region.amazonaws.com
Security Group 설정 시에 도메인을 등록할 수 없고, IP 형태로된 CIDR
만 지정이 가능한데, CIDR로 특정하기엔 까다로운 설정이기 때문에 대상을 전체로 허용한다.
프라이빗 서브넷에 위치한 EC2의 경우에는
NAT Gateway
혹은VPC Endpoint
로 연결하여 SSM Agent와의 통신이 가능하게 설정하면 된다.
IAM 역할 생성 및 연결
EC2에 연결할 IAM 역할을 생성할 때는 AWS에서 제공하는 관리형 정책을 사용하면 된다.
IAM > 역할 > 역할생성 메뉴로 들어가 신뢰할 수 있는 엔터티 중 EC2를 선택한다.
ssm
을 검색하여 AmazonSSMManagedInstanceCore
정책을 연결한다.
역할 이름을 지정해주고 생성한다.
EC2 인스턴스 메뉴 중 보안 > IAM 역할 수정 으로 들어간다.
위에서 만든 IAM 역할을 선택하고 업데이트한다.
이제 해당 EC2 인스턴스는 SSM을 사용할 수 있는 조건을 갖췄다.
IAM 사용자 및 정책 생성, 연결
SSM은 IAM 사용자 단위로 인스턴스의 연결을 관리하기 때문에 사용자를 생성해야 하고, 해당 EC2에 대한 접속 권한을 부여해주어야 한다.
IAM > 정책 > 정책 생성 메뉴로 들어가 아래의 json
정책을 생성한다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:StartSession"
],
"Resource": [
"arn:aws:ec2:ap-northeast-2:${AWS_ACCOUNT_ID}:instance/${AWS_EC2_INSTANCE_ID}"
]
},
{
"Effect": "Allow",
"Action": [
"ssm:TerminateSession"
],
"Resource": [
"arn:aws:ssm:*:*:session/${aws:username}-*"
]
}
]
}
Statement.Resource
에서 AWS_ACCOUNT_ID 에는 AWS 루트 계정의 Account ID를, AWS_EC2_INSTANCE_ID 에는 위에서 생성한 EC2 인스턴스의 ID를 넣어준다.
만약에 EC2 인스턴스 ID 단위로 관리하지 않을 예정이라면, 아래와 같이 EC2를 전체 지정한다.
...
"Resource": [
"arn:aws:ec2:ap-northeast-2:${AWS_ACCOUNT_ID}:instance/i-*"
]
...
정책 이름을 입력하고 생성한다.
IAM > 사용자 > 사용자 생성 메뉴로 들어가 사용자 이름을 등록한다.
위에서 설정한 ssm-example-policy
정책을 연결한다.
사용자를 생성한다.
이제, 보안 자격 증명에서 액세스 키를 발급 받는다.
CLI를 선택하고 다음으로 넘어간다.
액세스 키와 비밀 액세스 키를 저장하고 다음으로 넘어간다.
EC2 연결
이제 발급받은 액세스 키를 AWS CLI
명령어로 등록한다.
> aws configure
AWS Access Key ID [None]: AKIAXTXDUDJK6XB563KE
AWS Secret Access Key [None]: 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
Default region name []: ap-northeast-2
Default output format []: json
aws ssm start-session
명령어로 인스턴스 ID를 통해 EC2에 접속한다.
> aws ssm start-session --target i-04ef9523ff26e555c
Starting session with SessionId: ssm-example-user-0c555a023068240a9
sh-4.2$ bash
[ssm-user@ip-172-16-4-229 bin]$ cd
[ssm-user@ip-172-16-4-229 ~]$ pwd
/home/ssm-user
만약, SSM 접속 실패의 원인을 알 수 없다면 EC2 인스턴스에 접속해서
/var/log/amazon/ssm/amazon-ssm-agent.log
의 SSM Agent 로그를 확인해볼 수 있다.
CloudWatch를 이용한 로깅 및 감사
어떤 IAM 계정에서 어떤 인스턴스에 접속했고, 어떤 명령어를 입력하여 서버에서 작업을 했는지에 대해 로그를 CloudWatch
로 전송할 수 있다.
일단 CloudWatch > 로그 그룹 메뉴로 들어가 로그를 저장할 로그 그룹을 생성한다.
이름과 보존 기간을 설정하고 로그 그룹을 생성한다.
AWS Systems Manager > 세션 관리자 > 기본 설정 메뉴로 들어가 편집을 클릭한다.
CloudWatch logging 설정에서 위에서 만들어준 로그 그룹을 선택하고 저장한다.
마지막으로, 위에서 설정한 EC2 인스턴스의 IAM 역할에서 CloudWatch 로그 그룹에 로그를 스트림할 수 있는 권한을 부여한다.
IAM > 정책 > 정책 생성 메뉴로 들어간다.
아래의 json
정책을 할당한다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:DescribeLogGroups"
],
"Resource": [
"arn:aws:logs:ap-northeast-2:${AWS_ACCOUNT_ID}:log-group:*"
]
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:ap-northeast-2:${AWS_ACCOUNT_ID}:log-group:${AWS_CLOUDWATCH_LOGS_GROUP_NAME}",
"arn:aws:logs:ap-northeast-2:${AWS_ACCOUNT_ID}:log-group:${AWS_CLOUDWATCH_LOGS_GROUP_NAME}:log-stream:*"
]
}
]
}
Statement.Resource
부분에서 AWS_ACCOUNT_ID 에는 AWS 루트 계정의 Account ID를, AWS_CLOUDWATCH_LOGS_GROUP_NAME 에는 위에서 생성한 CloudWatch 로그 그룹 이름을 넣어준다.
이름을 지정해주고, 정책을 생성한다.
IAM > 역할 메뉴로 들어가 기존에 생성한 역할에 들어간다.
Add Permissions > Attach policies 로 들어간다.
위에서 생성한 IAM 정책을 할당하고 저장한다.
이제 EC2 인스턴스에 접속하여 echo
명령어를 콘솔에서 입력하고, 제대로 로깅이 되는지 확인해본다.
> aws ssm start-session --target i-04ef9523ff26e555c
Starting session with SessionId: ssm-example-user-0c555a023068240a9
sh-4.2$ echo cloudwatch-log-test
cloudwatch-log-test
sh-4.2$ echo cloudwatch-log-test
cloudwatch-log-test
sh-4.2$ echo cloudwatch-log-test
cloudwatch-log-test
CloudWatch > 로그 > 로그 그룹 으로 들어가 새로 생긴 로그 스트림으로 들어가 로그를 확인한다.
로그 이벤트를 열어본다.
다음의 로그로 언제(eventTime
), 어떤 IAM 사용자(ssm-example-user
)가, 어떤 EC2 인스턴스(i-04ef9523ff26e555c
)에 어떤 데이터 혹은 어떤 작업(sessionData
)를 수행했는지 확인할 수 있다.
SSM 세션 관리자 커스터마이징
SSM으로 접속은 됐지만, 아쉬운 부분이 존재한다.
- 기본 접속 터미널이
sh
- 기본 접속 계정이
ssm-user
보통 AWS EC2 인스턴스를 접속하면 sh
보단, bash
를, ssm-user
보단, ec2-user
를 사용한다.
이 부분을 SSM 세션 관리자 설정을 통해 변경할 수 있다.
기본 접속 터미널 sh에서 bash로 바꾸기
AWS Systems Manager > 세션 관리자 > 기본 설정 메뉴로 들어가 편집을 클릭한다.
Linux shell profile 설정에서 다음과 같이 설정한다.
exec /bin/bash
cd ~
이렇게 설정하면 SSM 연결을 하자마자, bash로 변경된다.
기본 접속 계정을 ssm-user에서 ec2-user로 바꾸기
AWS Systems Manager > 세션 관리자 > 기본 설정 메뉴로 들어가 편집을 클릭한다.
Operating system user name 설정에서 다음과 같이 설정한다.
이렇게 되면, 모든 SSM Agent로 실행되는 세션 관리자는 ec2-user
로 로그인하게 된다.
만약에, 보안 사항을 준수하기 위해 공통 계정을 사용하지 않고 IAM 사용자 별로 서버에 계정을 생성하여 관리하고 싶다면 이렇게 설정하면 된다.
먼저, EC2 인스턴스에 접속해서 IAM 사용자 계정 이름의 서버 계정을 생성한다.
> aws ssm start-session --target i-04ef9523ff26e555c
Starting session with SessionId: ssm-example-user-03ff0300d6bfdeb01
exec /bin/bash
cd ~
sh-4.2$ exec /bin/bash
[ssm-user@ip-172-16-4-229 bin]$ cd ~
[ssm-user@ip-172-16-4-229 ~]$ sudo -i
[root@ip-172-16-4-229 ~]# adduser ssm-example-user
[root@ip-172-16-4-229 ~]# ll /home
total 0
drwx------ 3 ec2-user ec2-user 95 Feb 6 11:45 ec2-user
drwx------ 2 ssm-example-user ssm-example-user 83 Feb 6 11:45 ssm-example-user
drwx------ 2 ssm-user ssm-user 83 Feb 5 14:39 ssm-user
Operating system user name 설정에서 다음과 같이 공백으로 설정한다.
IAM > 사용자 메뉴에서 위에서 생성한 IAM 계정에 들어간다.
태그 설정에서 새로운 태그 생성 을 클릭한다.
Key를 SSMSessionRunAs
, Value를 현재 IAM 사용자 계정 이름
으로 설정한다.
그리고 터미널에 접속해보면 해당 IAM 사용자 계정으로 로그인이 되어 있다.
> aws ssm start-session --target i-04ef9523ff26e555c
Starting session with SessionId: ssm-example-user-03e9696a0821a39d4
exec /bin/bash
cd ~
sh-4.2$ exec /bin/bash
[ssm-example-user@ip-172-16-4-229 bin]$ cd ~
[ssm-example-user@ip-172-16-4-229 ~]$ pwd
/home/ssm-example-user
이런 설정뿐만 아니라 session timeout, s3 logging 등 다양한 설정이 가능하다.
EC2 인스턴스 말고 다른 리소스 접근하기
이제 SSM으로 EC2 인스턴스는 접속할 수 있게 됐는데, 여전히 인터넷 액세스가 안 되는 프라이빗 서브넷에 위치한 RDS 혹은 ElastiCache 등 여러 AWS 리소스는 아직 접근하기 어렵다.
이 때 SSM Port Forwarding
을 사용하면 로컬에 프라이빗 서브넷에 위치한 AWS 리소스로 접속할 수 있는 프록시 엔드포인트를 생성하게 된다.
프라이빗 서브넷에 위치한 퍼블릭 액세스가 허용되지 않은, 위에서 만든 EC2 인스턴스에서만 접속이 허용된 RDS 인스턴스가 생성되었다고 가정한다.
EC2 인스턴스에 접속하여 telnet
으로 포트 허용이 됐는지 테스트를 해본다.
[ssm-example-user@ip-172-16-4-229 ~]$ telnet ssm-example-rds.c4oexdk2vucc.ap-northeast-2.rds.amazonaws.com 5432
Trying 172.16.64.62...
Connected to ssm-example-rds.c4oexdk2vucc.ap-northeast-2.rds.amazonaws.com.
Escape character is '^]'.
그리고 로컬에서도 테스트 해본다. 연결이 안 되는게 정상이다.
> telnet ssm-example-rds.c4oexdk2vucc.ap-northeast-2.rds.amazonaws.com 5432
Trying 172.16.64.62...
telnet: connect to address 172.16.64.62: Operation timed out
telnet: Unable to connect to remote host
IAM > 정책 메뉴로 들어가서, SSM Port Forwarding을 사용하기 위한 IAM 권한을 수정하도록 한다.
이전에 설정했던 SSM 인스턴스 연결을 위한 IAM 사용자 정책에서 수정하면 된다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:StartSession"
],
"Resource": [
"arn:aws:ec2:ap-northeast-2:${AWS_ACCOUNT_ID}:instance/${AWS_EC2_INSTANCE_ID}",
"arn:aws:ssm:ap-northeast-2::document/AWS-StartPortForwardingSessionToRemoteHost"
]
},
{
"Effect": "Allow",
"Action": [
"ssm:TerminateSession"
],
"Resource": [
"arn:aws:ssm:*:*:session/${aws:username}-*"
]
}
]
}
추가된 항목은 Statement.Resource
의
"arn:aws:ssm:ap-northeast-2::document/AWS-StartPortForwardingSessionToRemoteHost"
부분이다.
자, 이제 AWS CLI 명령어로 SSM Port Forwarding 세션을 만들어보도록 한다.
> aws ssm start-session \
--region ap-northeast-2 \
--target i-04ef9523ff26e555c \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters host="ssm-example-rds.c4oexdk2vucc.ap-northeast-2.rds.amazonaws.com",portNumber="5432",localPortNumber="15432"
Starting session with SessionId: ssm-example-user-046cdbb2a3038e91f
Port 15432 opened for sessionId ssm-example-user-046cdbb2a3038e91f.
Waiting for connections...
target
에는 프록시 역할을 하는 서버 즉, RDS와 통신이 가능한 EC2 인스턴스가 들어간다. 그리고 parameters
의 host
는 RDS의 엔드포인트, portNumber
는 RDS의 포트, localPortNumber
는 로컬에서 오픈할 포트를 넣어준다.
이렇게 세션을 만든 상태에서 다른 터미널로 접속을 테스트한다.
여기서 주의할 점은 연결되는 엔드포인트가 RDS의 엔드포인트가 아니라, 로컬호스트이다. 로컬호스트에 15432
포트로 엔드포인트를 생성하고, 해당 엔드포인트가 EC2 인스턴스를 통해 RDS로 연결될 수 있도록 설정한 것이다.
위의 설명을 쉽게 얘기하면 프록시 했다는 이야기다.
> telnet localhost 15432
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
접속에 성공한 것을 확인할 수 있다.
telnet으로 연결을 하면 세션을 만들어 놓은 터미널의 로그에 연결이 되었다는 로그가 생긴다.
Starting session with SessionId: ssm-example-user-046cdbb2a3038e91f
Port 15432 opened for sessionId ssm-example-user-046cdbb2a3038e91f.
Connection accepted for session [ssm-example-user-046cdbb2a3038e91f]
이렇게 접속된 세션까지 확인해볼 수 있었다.
인터넷이 차단된 RDS를 로컬에서 DB 접속 툴을 이용하여 접근을 할 수 있기 때문에 매우 편리하다. (세션 연결 유지를 위해 터미널을 띄어놓는 것만 빼면...)
RDS 뿐만 아니라 퍼블릭 액세스가 허용되지 않은 EC2 인스턴스의 웹 서버 혹은 ElastiCache 등 다른 AWS 리소스에도 적용이 가능하다.