*본 게시글의 내용은 개인 학습내용과 더불어 가시다님의 노션 페이지와 스터디 자료인 '24단계 실습으로 정복하는 쿠버네티스 도서' 를 기반으로 작성하였습니다.
EC2 meta data
IMDSv2(Instance Meta Data Service)
EC2 인스턴스에서 인스턴스의 메타데이터를 검색할 수 있도록 하는 서비스이다. 일반적으로는 인스턴스에서 실행중인 애플리케이션이 AWS에서 제공하는 인스턴스의 메타 데이터에 접근해야 할 때 사용된다.
EC2 인스턴스 메타데이터는 EC2 인스턴스의 IP, 보안그룹, IAM 롤 등 인스턴스 전반에 대한 정보를 담고 있는데, IMDSv2에 의해 암호화 및 권한에 대한 검증을 거치기 때문에 인증된 EC2 인스턴스만 IMDSv2를 통해 인스턴스 메타데이터에 접근할 수 있다.
스터디 자료와는 순서가 조금 다르지만, 비교를 위해 먼저 kOps로 구성한 쿠버네티스 클러스터에 파드를 생성하고, 파드 내에서 인스턴스 메타데이터에 접근해보았다.
# 파드 생성
$ cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:etshoot-pod
name: netshoot-pod
spec:licas: 2
replicas: 2
selector:bels:
matchLabels:oot-pod
app: netshoot-pod
template:a:
metadata:
labels:netshoot-pod
app: netshoot-pod
spec:tainers:
containers:shoot-pod
- name: netshoot-podtshoot
image: nicolaka/netshoot
command: ["tail"]v/null"]
args: ["-f", "/dev/null"]ds: 0
terminationGracePeriodSeconds: 0
EOF
# 파드 이름을 변수로 지정
$ PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].metadata.name})
$ POD1에서 인스턴스 메타데이터 접근 시도
$ kubectl exec -it $PODNAME1 -- curl 169.254.169.254 ;echo
위와 같이 수행하면 아무것도 출력되지 않는다. IMDSv2에 의해 인증된 EC2에서만 메타데이터에 접근할 수 있기 때문이다. 만약 파드가 노출되어 누군가 쉘을 획득한다고 해도, 실제 호스트인 EC2에 대한 정보를 획득할 수 없는 상태이므로 영향 범위를 제한할 수 있다.
다음으로 클러스터의 워커노드 1대에서 IMDSv2에 의한 메타데이터 보안 접근을 해제한 뒤 다시 메타데이터에 접근해보았다.
$ kops edit ig nodes-ap-northeast-2a
# 아래 3줄 제거
instanceMetadata:
httpPutResponseHopLimit: 1
httpTokens: required
$ kops update cluster --yes && echo && sleep 3 && kops rolling-update cluster --yes
$ kubectl exec -it $PODNAME1 -- curl 169.254.169.254 ;echo
1.0
2007-01-19
...
2022-09-24
latest
$ kubectl exec -it $PODNAME1 -- curl 169.254.169.254/latest/meta-data/iam/security-credentials/nodes.$KOPS_CLUSTER_NAME | jq
{
"Code": "Success",
"LastUpdated": "2023-04-08T15:01:46Z",
"Type": "AWS-HMAC",
"AccessKeyId": "ABC...R",
"SecretAccessKey": "AS....FASDF",
"Token": "...6y2W4=",
"Expiration": "2023-04-08T21:37:35Z"
}
위와 같은 상태에서 컨테이너를 탈취하고 EC2 메타데이터에 접근한다면, EC2에 부여된 IAM Role(Instance Profile)의 임시 토큰을 획득할 수 있으므로 AWS 영역까지 영향범위가 확대된다.
다양한 명령을 테스트 해보기 위해 EC2에 연결된 IAM Role에 기존 권한에 더해 AdministratorAccess 권한을 추가해보았다.
기존에는 EC2, IAM, ECR, S3에 대한 일부 권한만이 부여되어 있었다.
# python3 boto3 사용을 위한 파드 생성
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: boto3-pod
spec:
replicas: 2
selector:
matchLabels:
app: boto3
template:
metadata:
labels:
app: boto3
spec:
containers:
- name: boto3
image: jpbarto/boto3
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 파드 이름 변수 지정
PODNAME1=$(kubectl get pod -l app=boto3 -o jsonpath={.items[0].metadata.name}) PODNAME2=$(kubectl get pod -l app=boto3 -o jsonpath={.items[1].metadata.name})
# 파드 쉘 접근
kubectl exec -it $PODNAME1 -- sh
kubectl exec -it $PODNAME2 -- sh
쉘로 접근한 파드 내에서 아래 파이썬 코드를 실행해보면 두 파드가 서로 다른 결과를 보여준다.
import boto3
ec2 = boto3.client('ec2', region_name = 'ap-northeast-2')
response = ec2.describe_instances()
print(response)
POD1
~/dev # python test.py
Traceback (most recent call last):
File "test.py", line 4, in <module>
response = ec2.describe_instances()
...
File "/usr/local/lib/python2.7/site-packages/botocore/auth.py", line 315, in add_auth
raise NoCredentialsError
botocore.exceptions.NoCredentialsError: Unable to locate credentials
POD2
~/dev # python test.py
{u'Reservations': [{u'OwnerId': '201554454372',
...
'date': 'Sat, 08 Apr 2023 15:25:12 GMT', 'content-type': 'text/xml;charset=UTF-8'}}}
워커노드 두 대중 한 대만 IMDSv2에 대한 보안 설정을 해제하였으므로, 한 쪽에서는 권한을 획득하지 못해 NoCredentialsError에러를, 한 쪽에서는 EC2 메타데이터를 모두 읽어와 결과를 출력하였다.
일반 리눅스 컨테이너에서 AWS CLI를 설치하고 AWS CLI로 명령을 실행하더라도 같은 결과를 얻을 수 있었다.
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: linux-pod
spec:
replicas: 2
selector:
matchLabels:
app: linux
template:
metadata:
labels:
app: linux
spec:
containers:
- name: linux
image: ubuntu
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
POD1
# aws s3 ls
Unable to locate credentials. You can configure credentials by running "aws configure".
POD2
# aws s3 ls
2023-03-11 15:59:12 ilikebigmac
# # aws ec2 describe-vpcs
{
"Vpcs": [
{
"CidrBlock": "172.31.0.0/16",
"DhcpOptionsId": "dopt-068252d0ed40aeebd",
"State": "available",
"VpcId": "vpc-05c6d7be1dad725a5",
"OwnerId": "201554454372",
...
앞서 획득한 액세스키와 토큰을 완전히 분리된 로컬 환경에서 환경변수로 주입하고 사용해보았다.
export AWS_ACCESS_KEY_ID="~~~~"
export AWS_SECRET_ACCESS_KEY="~~~~"
export AWS_SESSION_TOKEN="~~~~"
isnt@DESKTOP-41KS4N1:~$ aws s3 ls
2023-03-12 00:59:12 ilikebigmac
isnt@DESKTOP-41KS4N1:~$ aws sts get-caller-identity
{
"UserId": "AROAS53MU45SAF7H2SAYG:i-0e25c5f531ae7802d",
"Account": "201554454372",
"Arn": "arn:aws:sts::201554454372:assumed-role/nodes.ilikebigmac.link/i-0e25c5f531ae7802d"
}
마치 본래의 워커노드로 사용했던 EC2인 것 처럼 권한 획득이 가능했다. 실제로 instance-id로 응답받은 i-0e25c5f531ae7802d
종료시키고 콘솔에서 확인해보았다.
isnt@DESKTOP-41KS4N1:~$ aws ec2 terminate-instances --instance-ids i-0e25c5f531ae7802d
{
"TerminatingInstances": [
{
"CurrentState": {
"Code": 32,
"Name": "shutting-down"
},
"InstanceId": "i-0e25c5f531ae7802d",
"PreviousState": {
"Code": 16,
"Name": "running"
}
}
]
}
실제로 해당 인스턴스가 종료됨을 확인하였다. 앞서 Administrator 권한을 롤에 부여했었는데, 만약 실제로 이러한 권한을 가진 인스턴스에서 탈취가 이루어지는 경우 AWS 위에 구현된 모든 환경이 영향 범위에 포함되므로 워커노드로 사용할 경우 외에도 각별히 주의해야 함을 느꼈다.
IRSA(Instance Role-Based Access Control for Service Accounts)
이에 대한 대응 방안으로 IRSA가 있다. EC2의 롤을 공유하거나, 파드 내에 사용자의 액세스키와 시크릿 키를 저장하는 것은 보안상 취약점으로 작용할 수 있다. IRSA는 IAM 롤을 쿠버네티스의 Service Account에 매핑하여 권한을 부여하고 해당 Service Account는 파드에 연결되어 각 파드별로 롤을 부여하고 권한을 제어할 수 있도록 하는 개념이다.
IRSA 사용을 위해서는 OIDC 가 전제된다. EKS는 클러스터 생성시에 함께 적용되지만, 실습환경인 kOps에서는 별도로 설정해 주어야 한다. 설정 방법은 kOps의 공식 문서를 참고하여 진행하였으며, kOps addon으로 클러스터 명세의 spec 필드 하위에 추가해주면 OIDC는 자동 생성 및 적용된다.
#spec: #spec 필드 하위에 추가
serviceAccountIssuerDiscovery:
discoveryStore: s3://ilikebigmac
enableAWSOIDCProvider: true
podIdentityWebhook
enabled: true
...
# iam: #spec.iam 필드 하위에 추가
useServiceAccountExternalPermissions: true
serviceAccountExternalPermissions:
- name: test-sa
namespace: default
aws:
inlinePolicy: |-
[
{
"Effect": "Allow",
"Action": "s3:ListAllMyBuckets",
"Resource": "*"
}
]
certManager:
enabled: true #기본 설정되어있음
공식 문서에서는 certManager 필드도 함께 예시에 포함되어 있는데, 실습 환경의 클러스터에는 이미 적용되어 있어 오류가 발생하므로 제외하고, iam 필드 이하의 내용들도 iam 필드가 이미 존재하므로 하위의 내용만 추가하였다.
변경한 옵션들은 다음과 같다.
- serviceAccountIssuerDiscovery : kOps가 OIDC 정보를 S3 버킷에 저장하도록 지정하고 OIDC Provider를 활성화
- certManager, podIdentityWebhook : Service Account가 지정된 IAM Role을 수임하도록 설정
- iam : 적용할 IAM Policy와 SA 설정
이후에 kops update cluster --yes
명령으로 클러스터를 업데이트하면, 추가로 생성되는 리소스들을 확인할 수 있다.(IAMOIDCProvider, IAMRole, IAMRolePolicy..)
- s3 버킷 내 OIDC 데이터 추가 확인
- 자격 증명 공급자 생성 확인
이제 SA를 생성하여 Role을 연결하고, 해당 SA를 Pod와 매핑해준다.
SA
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: irsa-test
namespace: default
EOF
Deployment
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: irsa-test
spec:
replicas: 2
selector:
matchLabels:
app: irsa
template:
metadata:
labels:
app: irsa
spec:
containers:
- name: irsa-test
image: amazon/aws-cli
command: ["tail"]
args: ["-f", "/dev/null"]
serviceAccountName: "test-sa"
EOF
$ kubectl exec -it irsa-test-859dbbfc6c-4p5cf -- bash
bash-4.2# aws s3 ls
2023-03-12 00:59:12 ilikebigmac
bash-4.2# aws ec2 describe-instances
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
최초 부여한 권한대로 s3 버킷에 대한 조회가 가능하며, 그 외의 동작은 불가능함을 확인할 수 있다.
해당 파드에 연결된 SA를 통해 부여받은 권한으로만 동작하기 때문에 행여 누군가 쉘을 획득하더라도 그 영향범위가 한정적이므로 피해를 최소화 힐 수 있으며, 관리자 입장에서도 인스턴스의 권한을 끌어서 쓸 때보다 세밀하게 특정지어 조치할 수 있게 된다.
'Kubernetes > PKOS' 카테고리의 다른 글
[5주차-1] 쿠버네티스 클러스터에 Polaris 적용하기 (0) | 2023.04.08 |
---|---|
[보완] 쿠버네티스 스토리지와 CSI Driver (0) | 2023.04.08 |
[보완] kubernetes 워커노드를 spot instance로 구성하기(Node Termination Handler) (1) | 2023.04.02 |
[4주차] 쿠버네티스 모니터링 (0) | 2023.04.02 |
[3주차] GitOps와 ArgoCD (0) | 2023.03.26 |