이번 포스팅은 AEWS 학습에서 Bottlerocket Security Features on Amazon EKS 워크숍을 그대로 따라하면서 정리한 내용이다.해당 워크숍은 EKS 노드그룹으로 Bottlerocket OS를 사용하는 내용이다. 노드그룹의 OS 업데이트를 API 방식을 통해서 진행하려고 한다.
EKS Bottlerocket 노드그룹 생성
해당 스크립트를 통해서 노드 그룹을 생성하자. 2개를 생성하는데 한 개는 일반적인 bottlerocket이고 다음 것은 그래도 어느정도 설정을 스크립트 상에서 실시한 것이다.
cat << EOF > ng-br.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: myeks
region: ap-northeast-2
version: "1.31"
managedNodeGroups:
- name: ng-bottlerocket
instanceType: m5.large
amiFamily: Bottlerocket
desiredCapacity: 1
maxSize: 1
minSize: 1
labels:
alpha.eksctl.io/cluster-name: myeks
alpha.eksctl.io/nodegroup-name: ng-bottlerocket
ami: bottlerocket
subnets:
- $PubSubnet1
- $PubSubnet2
- $PubSubnet3
tags:
alpha.eksctl.io/nodegroup-name: ng-bottlerocket
alpha.eksctl.io/nodegroup-type: managed
- name: ng-bottlerocket-ssh
instanceType: m5.large
amiFamily: Bottlerocket
bottlerocket:
enableAdminContainer: true
settings:
motd: "Hello, eksctl!"
desiredCapacity: 1
maxSize: 1
minSize: 1
ssh:
allow: true
publicKeyName: $SSHKEYNAME
labels:
alpha.eksctl.io/cluster-name: myeks
alpha.eksctl.io/nodegroup-name: ng-bottlerocket-ssh
ami: bottlerocket
subnets:
- $PubSubnet1
- $PubSubnet2
- $PubSubnet3
tags:
alpha.eksctl.io/nodegroup-name: ng-bottlerocket-ssh
alpha.eksctl.io/nodegroup-type: managed
EOF
해당 스크립트에서 주의 깊게 봐야할 부분이라면 다음과 같다.
- amiFamily 및 ami는 bottlerocket을 사용해 지정한다.
- bottlerocket 키값에 enableAdminContainer를 true로 설정하면 생성시 바로 admin container가 활성화된다.
- bottlerocket setting 중에서 다양한 값들이 존재하는데(마지막에 배운다) 이 설정을 미리 할 수 있다. 아래 스크립트에서는 motd값을 설정한 것을확인 할 수 있다.
- ssh 등 다양한 설정이 가능하다. 참고로 bottlerocket은 ssh를 권장하지 않는다.
- name: ng-bottlerocket
instanceType: m5.large
amiFamily: Bottlerocket
bottlerocket:
enableAdminContainer: true
settings:
motd: "Hello, eksctl!"
ssh:
allow: true
publicKeyName: $SSHKEYNAME
labels:
alpha.eksctl.io/cluster-name: myeks
alpha.eksctl.io/nodegroup-name: ng-bottlerocket
ami: bottlerocket
노드 그룹 확인하기
아래 명령어를 통해서 bottlerocket을 사용하는 노드그룹을 미리 확인할 수 있는데, bottlerocker이미지를 사용하는지 판단하면 된다.
eksctl get cluster -n $EKS_CLUSTER -r $AWS_REGION -o json | jq -M ".[] | {Name,Version,Status,CreatedAt}"
eksctl get nodegroups -c $EKS_CLUSTER -r $AWS_REGION -o json | jq -M ".[] | {Cluster,Name,Status,ImageID,Type}"
eksctl get nodegroup -c $EKS_CLUSTER -n $BR_MNG_NAME -r $AWS_REGION -o json | jq -M ".[] | {Cluster,Name,Status,MaxSize,MinSize,DesiredCapacity,InstanceType,ImageID}"

참고) 만약 Bottlerocket을 사용하는 이미지의 노드그룹이 없다면 아래 명령을 통해서 바로 scale up 할 수 있다.
if [ `kubectl get nodes -l eks.amazonaws.com/nodegroup=$BR_MNG_NAME -o json | jq -r '.items | length'` -gt 0 ]; then
echo -e "\nBottlerocket Managed Node Group has nodes. No need to scale.\n\n"
else
echo -e "\nBottlerocket Managed Node Group has no nodes. Scaling to one node.\n\n"
eksctl scale nodegroup -c $EKS_CLUSTER -n $BR_MNG_NAME -r $AWS_REGION --nodes 1
fi
scale up 했을 경우 Ready 상태를 기다리기 위해 다음 명령어를 사용한다.
while [ "`kubectl get nodes -l eks.amazonaws.com/nodegroup=$BR_MNG_NAME | grep -v STATUS | awk '{print $2}'`" != "Ready" ]
do
echo -e "`date` - Waiting for the Bottlerocket Node to be ready. Please wait...\n"
sleep 15
done
echo -e "\nBottlerocket Node is ready. Please proceed with the next steps.\n"
Bottlerocket 호스트 엑세스
잘보면 SSM을 통해서 bottlerocket EC2에 접속이 가능하다. 아마 AWS에서 가장 권장하는 방법이 아닐까 싶다.
기본적으로 Bottlerocket EC2에 접속하면 다음과 같은 메시지가 나온다. 내용을 한번 살펴보자.
aws ssm start-session --target $INSTANCE_ID

일단 우리가 처음 접속하면 Bottlerocket의 컨트롤 컨테이너에 접속된다.
접속자의 세션 ID
Starting session with SessionId: james-y79kzlpced59i6bzagujb4ieta
접속된 세션 ID를 해당 콘솔에 그대로 표시해준다.
“Welcome to Bottlerocket’s control container!”
우리가 Bottlerocket control 컨테이너에 접속했음을 알린다.
또한 이 컨테이너를 통해 Bottlerocket API에 접속할 수 있고, 시스템 구성이나 검사가 가능하다고 알려준다.
예를 들어 여기서 알려주는 apiclient라는 명령을 통해서 설정 값을 조회하는 것을 해보자.
apiclient -u /settings
여기서 json으로 정보가 엄청 나게 나열되는데, json parser를 통해서 나오는 정보를 트리구조로 보았다.

우리가 여기서 확인해볼 의미있는 정보는 커널이나 host-containers 정보 정도가 될 것 같다.
개념에서 살펴본 것과 같이 host-container는 admin과 control 컨테이너로 나뉘고 있다. 기본적으로 admin 컨테이너는 비활성화 상태지만 스크립트 상 활성화 시켰기 때문에 활성화상태이다.

구체적인 정보를 조회하고 싶다면 apiclient get을 통해서 직접 조회가 가능하다.
추후에 만약 해당 정보를 가지고 업그레이드 여부를 판단해야한다면 바로 apiclient를 통해서 해당 정보를 추적하는 것이 좋겠다고 생각이 들었다.
apiclient get settings.host-containers

Bottlerocket 이미지 구성
여기서 잠깐 개념설명을 하고 가자면 Bottlerocket 이미지는 다음과 같이 admin과 control 호스트 컨테이너가 존재한다. 이는 잘보면 호스트 서버 내에서도 containerd 런타임을 통해서 또 한번 분리되어 시스템 영향을 최소화 시키는 것을 확인할 수 있다. 우리가 애플리케이션을 돌리는 부분은 오른쪽 kubelet을 통해 pod를 구동시키는 것을 확인할 수 있다.

이곳에서 정말 자세하게 다루고 있으니 확인해보면 좋을것이다.
참고로 ECS는 다음과 같은 구조이다.

차이라고 한다면 ECS를 위한 ecs-agent가 있다는 점이다.
admin container 활성화
다시 원래대로 SSM으로 돌아와서 admin container를 활성화 시켜보자. 기본적으로는 비활성화 상태이므로 다음 명령어를 통해서 실행해줘야한다. admin 컨테이너는 시스템 디버깅이나 루트의 작업을 요하는 경우 혹은 추가적인 네트워크 경로 등을 필요로 하는 경우에 액세스를 제공한다.
enable-admin-container

이 명령어를 통해서 admin container로 들어갈 수 있다.
enter-admin-container

참고로 활성화된 admin container를 비활성화 시키려면 간단하게 disable 시키면 된다.
disable-admin-container
Bottlerocket 호스트 업그레이드
updates 정보 확인
apiclient get settings.updates

한번 nslookup으로 해당 주소를 찍어봤는데 CDN이 나오는 것으로 보아 S3에 해당 파일을 관리하고 있는 것 같다.

seed : 업데이트 시드 값으로 이 값을 통해 업데이트 버전이나 배포 상태를 추적하는데 사용된다.
targets-base-url : 업데이트 타겟 파일을 다운로드할 위치를 가리킨다. 이 위치에서 파일을 다운로드해 사용한다.
version-lock : 업데이트 버전을 지정할 수 있다. lastest의 경우 가장 최신 버전으로 자동 업그레이드되는 것을 의미한다. 만약 특정 버전을 고정하고 싶다면 해당 버전을 명시하면된다. 예를들어 1.27을 명시하면 1.27 버전으로 고정된다.
사용 가능한 OS 버전 확인
다음 명령어를 통해서 사용 가능한 OS 버전을 확인할 수 있다.
apiclient update check

version-lock 설정에 따라 최신버전으로 설정되게 되는데 현재 available_updates가 1.32.0이고, 현재 버전 역시 1.32.0으로 최신버전인 상태이다.
결과에서 사용 가능한 업데이트가 없다면 chosen_update : null, update_state: Idle이 표시된다.
업데이트 실행
해당 명령어를 통해서 업데이트를 진행할 수 있다.
apiclient update apply
만약 업데이트가 성공적이라면 아래와 같은 결과가 나오면서 업데이트 된다.
21:11:32 [INFO] Downloading and applying update to disk...
21:11:37 [INFO] Still waiting for updated status, will wait up to 594.5s longer...
21:11:41 [INFO] Setting the update active so it will apply on the next reboot...
21:11:42 [INFO] Update has been applied and will take effect on next reboot.
하지만 지금과 같이 최신상태에서는 다음과 같이 실패 메시지를 볼 수 있다.
15:46:06 [INFO] Downloading and applying update to disk...
Failed to apply update: Failed to prepare update. This could mean that we don't have a list of updates yet or that an update is already applied.

업데이트가 되고 난 후에는 bottlerocket의 전체 파일 시스템 이미지를 다운로드하고 재부팅하는 과정을 거친다.
apiclient update check
해당 명령어에서 active_partition은 현재 버전, staging_partition은 새로운 버전으로 표시되고 update_state: Ready로 표시될 것이다.
reboot를 통해서 재실행하게 되면 결국 staging_partition이 active_partition으로 변경되게 된다.
apiclient reboot
exit
Bootstrap(부트스트랩) 컨테이너 탐색
Bootstrap Containers
Setting up the host with containers that start during boot.
bottlerocket.dev
부트스트랩 컨테이너는 Bottlerocket 운영 체제가 부팅될 때 최초로 실행되는 컨테이너 이다. 주로 하는 역할은 시스템 초기화와 환경설정 작업이다. 이 컨테이너는 Bottlerocket 운영 체제에서 초기화 과정이 끝난 후 종료되고 다른 애플리케이션 컨테이너가 실행될 수 있는 환경을 만들어 준다.
Bootstrap container의 역할
- 시스템 초기화 : Bottlerocket 운영체제 부팅시 가장 먼저 실행되는 init container의 역할을 한다. 따라서 시스템의 기본 설정을 담당한다.
- 설정 관리 : 네트워크 및 보안 설정을 초기화하고 Bottlerocket API와 통신하면서 구성 정보를 가져온다.
- 보안 및 설정 : SELinux 기능 등을 활성화하고 시스템 필요한 보안 설정을 수행한다.
수명주기(라이프사이클)
부트 스트랩 컨테이너는 systemd에 의해서 관리되는데 다음과 같은 과정을 거친다.
- systemd가 모든 부트스트랩 컨테이너를 동시에 실행한다. (병렬처리)
systemd는 모든 부트스트랩 컨테이너가 실행하고 종료될 때까지 (1사이클이 끝날때까지) 다음 타겟(systemd.target)으로 넘어가지 않는다. (즉 시스템 초기화 과정에서 모든 준비가 마치고 다른 작업을 시작하겠다는 의미)

- mode=once라고 설정된 부트스트랩 컨테이너는 완료되면 mode=off로 변환된다.
(즉 1번만 실행하는 컨테이너는 mode=once로 표기하여 실행 후 mode=off로 자동 변환되고 재실행되지 않는다.)

- essential=true로 설정된 부트스트랩 컨테이너가 비정상 종료되면 부팅 과정이 중단된다.
주로 핵심적인 부트스트랩 컨테이너에 설정되며, 컨테이너가 비정상 종료(exit0이 아닌 값으로 종료)되면 부팅과정이 중단된다.

- 중단 없이 계속 진행된다면 부트스트랩 컨테이너가 종료되고 오케스트레이터 에이전트인 kubelet이나 ECS agent를 로드한다.
부트스트랩 컨테이너의 기능
- 루트 파일 시스템 엑세스가 가능
Bottlerocket 운영 체제의 루트 파일 시스템 (/.bottlerocket/rootfs)을 포함하는 /.bottlerocket/*에 접근할 수 있다. 이 루트 파일 시스템은 시스템의 중요한 기본 파일과 디렉터리를 포함한다. 따라서 부트스트랩 컨테이너는 기본 파일 시스템을 사용할 수 있다는 말이된다. 다만 루트 파일 시스템은 변경할 수 없다.
- 컨테이너 권한은 CAP_SYS_ADMIN을 할당받음
부트스트랩 컨테이너는 CAP_SYS_ADMIN이라는 특별한 권한을 가지고 실행한다. CAP_SYS_ADMIN 권한은 시스템에서 관리자와 같은 권한으로 파일, 디렉터리, 마운트 등 시스템 자원을 관리할 수 있다. 다만, 이는 루트 파일 시스템 자체의 변경을 의미하지는 않는다.
- /.bottlerocket/rootfs/mnt에 바인드 마운트를 사용함
이 경로에 바인드 마운트가 설정되어 이 경로를 통해 호스트 시스템과 파일을 공유하거나 호스트 파일 시스템에 접근할 수 있도록 해준다.
부트스트랩 컨테이너 생성
먼저 컨테이너 이미지를 생성하고 이를 보관하기 위해 ECR을 사용한다. 다음 명령어를 통해서 ECR을 생성하자.
복잡하게 써져있지만 br-bootstrap이라는 ECR이 없으면 만들라는 내용이 전부다.
AWS_ACCOUNT_ID=your-account-id
AWS_REGION=ap-northeast-2
export ECR_REPO="br-bootstrap"
echo "export ECR_REPO=$ECR_REPO" | tee -a ~/.bash_profile
export ECR_REGISTRY="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
export ECR_REPO_URI=$(aws ecr describe-repositories --repository-name ${ECR_REPO} --region ${AWS_REGION} | \
jq -r '.repositories[0].repositoryUri')
if [ -z "$ECR_REPO_URI" ]
then
echo "${ECR_REGISTRY}/${ECR_REPO} does not exist. So creating it..."
ECR_REPO_URI=$(aws ecr create-repository \
--repository-name $ECR_REPO \
--region $AWS_REGION \
--image-tag-mutability IMMUTABLE \
--query 'repository.repositoryUri' \
--output text)
echo "export ECR_REPO_URI=$ECR_REPO_URI" | tee -a ~/.bash_profile
echo -e "\n${ECR_REPO_URI} repo created..."
else
echo "${ECR_REPO_URI} already exists..."
echo "export ECR_REPO_URI=$ECR_REPO_URI" | tee -a ~/.bash_profile
fi

다음은 dockerfile을 생성한다. 이 예제에서는 eks-workshop 폴더를 /.bottlerocket/rootfs/mnt에 바인드 마운트 시킨다. 즉 eks-workshop 폴더를 만들고 /.bottlerocket/rootfs/mnt에 생성할 것이다. 이미지를 빌드하고 ECR에 푸쉬하는 작업을 거쳐보자.
cat << EoF > Dockerfile
FROM alpine
ADD test-bootstrap ./
RUN chmod +x ./test-bootstrap
ENTRYPOINT ["sh", "test-bootstrap"]
EoF
cat << EoF > test-bootstrap
#!/usr/bin/env bash
set -ex
mkdir -p /.bottlerocket/rootfs/mnt/eks-workshop
EoF
docker build -t $ECR_REPO:v1 .
docker tag $ECR_REPO:v1 $ECR_REPO_URI:v1
docker push $ECR_REPO_URI:v1
참고로 해당 작업 전에 ECR 로그인을 해주는게 좋다.
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 160889332534.dkr.ecr.ap-northeast-2.amazonaws.com
이제 admin container로 접속해 봅시다.
enter-admin-container
일단 /.bottlerocket/rootfs/mnt에서 루트 파일시스템의 바인드 마운트가
df -h /.bottlerocket/rootfs/mnt/
if [ -d "/.bottlerocket/rootfs/mnt/eks-workshop" ]; then
echo -e "\nDirectory /.bottlerocket/rootfs/mnt/eks-workshop exists"
else
echo -e "\nDirectory /.bottlerocket/rootfs/mnt/eks-workshop does not exist"
fi
exit


apiclient reboot; exit


이렇게 노드가 처음 부트스트랩 하는 단계에서 해당 컨테이너 이미지를 사용해 사용환경을 세팅할 수 있다는 것을 확인했다.
제한된 파일 시스템 탐색
일반적으로 호스트 파일 시스템에 접근할 일은 별로 없지만 일부 로그나 설정파일 등 호스트 파일 시스템에 접근해야할 경우가 생긴다면 제한적으로 이용할 수 있도록 하는 방법이다. 워크숍에서는 개념 설명이 부족해서 다음 공식문서의 내용을 요약해보았다.
기본적으로 Bottlerocket에서는 다음 소개하는 3가지 방식으로 파일시스템의 접근을 관리하고 있다.
Immutable은 dm-verity라는 기술을 통해서, mutable은 SELinux의 기능을 통해서, 그리고 일부 변경가능한 임시 저장소의 경우는 임시라는 특징을 통해 재부팅 후 변경 사항이 소멸되는 방식으로 보안성을 높이고 있다. 세부적으로 알아보자.
변경 불가능한 파일 시스템(Immutable Filesystem)
Bottlerocket의 루트 파일 시스템은 dm-verity라는 기술로 읽기 전용으로 설정된다. 따라서 루트 파일 시스템을 사용자 프로세스가 수정할 수 없도록 하여 안정성을 보장한다.
dm-verity는 블록 수준의 보호를 제공하는 이 기술은 해시 트리를 사용해 파일 시스템의 무결성을 검증한다.
각 물리적인 블록에 대해 암호화된 해시 값을 계산하고, 이 해시 값들의 결합으로 루트 블록의 해시를 계산한다.
이후 모든 쓰기 작업에서 새로운 루트 블록 해시와 기존의 루트 블록 해시를 비교해 변경사항을 감지한다.

아래 그림처럼 물리적 블록이 변경되면(Changed block) 이후 각 계층에서 다른 다이제스트가 생성되면서 다른 루트 블록 다이제스트가 생성된다.

결국 루트 파일 시스템이 변경되면 해시 값이 달라지므로 Changed root block이 생기면서 dm-verity는 시스템 변경사항을 알리고 커널은 시스템을 재부팅한다. 따라서 루트 파일 시스템을 보호하고 루트킷 등 악성 공격으로 부터 유지한다.
변경 가능한 파일 시스템 (Mutable Filesystem)
기본적으로 루트 파일 시스템에 대해 가변적인 것은 실용적이지도 보안적이지도 않기 때문에 잘 사용되지 않는다. 다만 컨테이너 로그를 기록하거나 설정 파일을 변경해할 경우 사용된다고 볼 수 있다. Bottlerocket은 SELinux를 활용해 보안성을 갖춘다. 특히 파일 및 디렉터리에 대해 세부적인 보안 정책을 설정하고, 각 자원에 대한 액세스 권한을 세밀하게 조정할 수 있다.
SELinux 보안 정책에 대해 조금 더 살펴보자.
- SELinux는 각 파일이나 리소스에 대해 라벨을 설정해 보호하고, 특정 리소스를 읽기/쓰기 할 수 있도록 권한 설정을 할 수 있다.
예를 들면 kubelet이 /var/log/containers에 로그를 기록하거나 bottlerocket API 서버가 /etc/motd 파일을 변경할 수 있도록 할 수 ㅣ있다.
- SELinux는 기본적으로 enforcing 모드이다. 이 모드는 로그를 기록하거나 불법적인 변경을 방지하는 기능이 있다.
일시적인 저장소 (Ephemeral Storage)
변경가능한 파일 시스템에서 일부는 일시적 저장소로 존재한다. 예를 들면 /etc 디렉터리는 재부팅 시 변경 사항이 유지되지 않는다.
장점으로는 구성 변경 경로의 신뢰성을 높여줄 수 있다. API 또는 CNI, CSI와 같은 특별한 컨테이너 명세를 통해 구성을 변경할 수 있다.
공격자가 변경 사항을 영구적으로 남기기 어렵게 한다. 재부팅 후 일시적으로 변경한 내용은 초기화 되기 때문에 악성 변경을 차단할 수 있다.
각 설정 확인하기
일단 admin 컨테이너에서 루트 파일 시스템이 읽기 전용 볼륨으로 마운트 되었는지 확인해보자.
다음 명령어를 통해서 루트 파일 시스템의 options에 ro(읽기 전용)으로 마운트 됨을 확인할 수 있다.
findmnt /.bottlerocket/rootfs/

기본적으로 읽기 전용으로 파일 시스템이 관리되어야 보안성이 높은건 당연할 것이다.
이제 admin 컨테이너에서 sheltie 스크립트를 실행하여 Bottlerocket 호스트의 루트 파일시스템에 접근 가능한 권한을 사용할 수 있도록 해보자.
sudo sheltie
이와 같이 bash로 넘어온 것을 볼 수 있다.

sheltie로 접근한 경우 루트 쉘을 사용하게 되며 dm_verity_hash에 있는 블록을 덮어쓰기해보도록 한다. 변경사항을 가하는 행위이다.
기본적으로 Bottlerocket 호스트에서는 커널에서 dm-verity라는 기술을 이용해 루트 파일시스템이 의도되지 않은 변경사항에 대해 변경되는 것을 차단한다. 이 방식은 hash 트리를 사용하여 체킹하는 방식이고 아까 공식문서에 확인했었다.
따라서 우리는 아래 명령어를 통해서 일부러 해시를 손상시켜보자. 그러면 호스트는 해시가 손상된 것을 체크하고 작동을 멈출 것이다.
dd if=/dev/zero of=`blkid -t TYPE=DM_verity_hash | cut -d":" -f1` bs=1M count=1

잠시만 기다려보면 호스트가 작동을 멈추는 것을 확인할 수 있다.
dd 명령어(파일 복사 및 변환 수행)를 실행해보자.
dd

아무리 시도해도 호스트는 상태는 구동되고 있지만 아무 생산적인 동작은 수행하지 못한다.

이제 다시 reboot을 통해 복구 여부를 확인해보자.
reboot
reboot을 해도 여전히 NotReady 상태임을 확인할 수 있으며, 더 이상 사용이 불가능합니다. 이미 정합성이 깨졌기 때문에 보안을 위해서 서버가 정상적인 기능을 하지 못한다.

호스트가 재 부팅하는 동안에는 READY 상태일 수 있는데 이는 시스템이 부팅될때마다 dm-verity는 루트 파일 시스템의 무결성 검사를 하기 때문에 해당 검사를 마치고 나서 운영체제 부팅을 거부하므로 일시적인 현상이다.
작동하지 않는 호스트는 종료하고 새로운 인스턴스를 생성해서 사용하자.
# 기존 bottlerocket 인스턴스 제거
kubectl get nodes -l eks.amazonaws.com/nodegroup=$BR_MNG_NAME | grep NotReady | awk '{print $1}' | xargs kubectl delete node
kubectl get nodes -l eks.amazonaws.com/nodegroup=$BR_MNG_NAME
aws ec2 terminate-instances --instance-ids $INSTANCE_ID --region $AWS_REGION
# 새로운 노드그룹 추가
while [ "`kubectl get nodes -l eks.amazonaws.com/nodegroup=$BR_MNG_NAME | grep -v STATUS | awk '{print $2}'`" != "Ready" ]
do
echo -e "`date` - Waiting for the Bottlerocket Node to be ready. Please wait...\n"
sleep 15
done
kubectl get nodes -l eks.amazonaws.com/nodegroup=$BR_MNG_NAME
echo -e "\nBottlerocket Node is ready."
export INSTANCE_IP=$(kubectl get nodes -l eks.amazonaws.com/nodegroup=$BR_MNG_NAME -o json | jq -r '.items[0].metadata.annotations."alpha.kubernetes.io/provided-node-ip"')
export INSTANCE_ID=$(aws ec2 describe-instances --filters Name=private-ip-address,Values=$INSTANCE_IP | jq -r .[][].Instances[].InstanceId)
echo "export INSTANCE_ID=$INSTANCE_ID" | tee -a ~/.bash_profile
aws ssm start-session --target $INSTANCE_ID
자 이번에는 다시 새로운 노드를 생성해서 로그인 한 뒤 변경 가능한 mutable의 예시를 살펴보자.
다시 한 번 관리형 컨테이너에 로그인 한 뒤 sheltie 명령어를 통해 root shell에 진입한다.
enter-admin-container;exit
sudo sheltie
Bottlerocket은 기본적으로 SELinux가 enforcing 모드로 실행되고, 커널에서는 이를 비활성화하는 것이 방지되도록 컴파일 되어있다.

따라서 /etc 디렉터리 내에 수정을 해볼 예정이다. 기본적으로 bottlerocket에서는 /etc 디렉터리는 일시적인 저장소이므로 재부팅시 변경사항이 초기화 된다. 따라서 다음 내용을 /motd에 기록해보고 재부팅해보자.
cat /etc/motd
echo "Attempting local modifications to /etc/motd" > /etc/motd
cat /etc/motd
reboot
일단 기본적으로 motd에는 Welcome to Bottlerocket! 이라는 내용의 파일이 존재했는데 Attempting local modifications to /etc/motd라는 내용으로 변경한걸 확인할 수 있다.

자 이제 다시 들어가보자. 다시 들어가면 Welcom to Bottlerocket으로 다시 초기화 된것을 확인할 수 있다.
aws ssm start-session --target $INSTANCE_ID
enter-admin-container
sudo sheltie; exit
cat /etc/motd

Bottlerocket 설정
일단 bottlerocket에서 설정할 수 있는 정---말로 많은 내용이 있으므로 반드시 여기를 참고하기 바란다.
너무 많아서 극히 일부만 캡처했다.

apiclient get settings.kubernetes.authentication-mode
설정은 apiclient를 통해서 control 컨테이너에서 진행한다. 특히 쿠버네티스(EKS)는 kubelet을 통해서 API 서버와 통신하는데 이 통신은 매우 안전해야 하므로 인증서 서명 API를 통해 서로 안전성을 지키고 있다. 이처럼 Bottlerocket에서도 aws-iam-authenticator를 사용해 인증토큰을 생성하고 EKS의 API 서버에 인증서 서명 요청(CSR)을 제출한다.
https://bottlerocket.dev/en/os/1.30.x/api/settings/kubernetes/#authentication-mode
kubernetes
Settings related to Kubernetes (`settings.kubernetes.*`)
bottlerocket.dev
apiclient get settings.kubernetes.authentication-mode

커널 잠금기능은 커널의 보안 강화를 위해 필요하다. 기본적으로 Integrity Mode를 사용하는데 이 모드는 커널 코드나 메모리 변경을 차단해 공격자가 시스템을 침해하는 것을 막아준다. 이 모드는 커널의 무결성을 보장해주므로 특히 메모리를 덮어쓰거나 수정하는 행위를 방지한다.
apiclient get settings.kernel.lockdown

모든 리소스 삭제
실습을 위해서 생성한 EKS 노드 그룹 및 ECR을 삭제한다. 비용조심하자!!
eksctl scale nodegroup -c $EKS_CLUSTER -n $BR_MNG_NAME -r $AWS_REGION --nodes 0
docker rmi $ECR_REPO:v1
docker rmi $ECR_REPO_URI:v1
aws ecr delete-repository \
--repository-name $ECR_REPO \
--region $AWS_REGION \
--force
이상으로 실습을 마치겠다.