티스토리 뷰
안녕하세요. CloudNet@ K8S Study를 진행하며 해당 내용을 이해하고 공유하기 위해 작성한 글입니다. DevOps 이정훈님의 도서 ‘24단계 실습으로 정복하는 쿠버네티스’ 의 내용을 바탕으로 스터디를 하며 어떻게 k8s에 적용할지 고민하며 정리해봤습니다.
사전 환경
# YAML 파일 다운로드
$ curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/kops-oneclick-f1.yaml
# CloudFormation 스택 배포 : 노드 인스턴스 타입 변경 - MasterNodeInstanceType=t3.xlarge WorkerNodeInstanceType=t3.large
$ aws cloudformation deploy - template-file kops-oneclick-f1.yaml - stack-name mykops - parameter-overrides KeyName=kp-hayley3 SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 MyIamUserAccessKeyID=AKIA5… MyIamUserSecretAccessKey='CVNa2…' ClusterBaseName='wellbeconnected.com' S3StateStore='hayley-bucket-test' MasterNodeInstanceType=t3.xlarge WorkerNodeInstanceType=t3.xlarge - region ap-northeast-2
# CloudFormation 스택 배포 완료 후 kOps EC2 IP 출력
$ aws cloudformation describe-stacks - stack-name mykops - query 'Stacks[*].Outputs[0].OutputValue' - output text
# 13분 후 작업 SSH 접속
$ ssh -i ~/.ssh/kp-gasida.pem ec2-user@$(aws cloudformation describe-stacks - stack-name mykops - query 'Stacks[*].Outputs[0].OutputValue' - output text)
# EC2 instance profiles 에 IAM Policy 추가(attach) : 처음 입력 시 적용이 잘 안될 경우 다시 한번 더 입력 하자! - IAM Role에서 새로고침 먼저 확인!
$ aws iam attach-role-policy - policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy - role-name masters.$KOPS_CLUSTER_NAME
$ aws iam attach-role-policy - policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy - role-name nodes.$KOPS_CLUSTER_NAME
사용자 데이터와 메타데이터
- 사용자 데이터 : 인스턴스 생성과 함께 전달되는 최대 16KB 크기의 텍스트 데이터
- 메타 데이터 : 실행 중인 인스턴스를 구성 또는 관리하는 데 사용될 수 있는 인스턴스 관련 데이터. 예를 들어 호스트 이름, 이벤트 및 보안 그룹과 같은 범주로 분류
다음 방법 중 하나를 사용하여 실행 중인 인스턴스에서 인스턴스 메타데이터에 액세스할 수 있습니다.
- 인스턴스 메타데이터 서비스 버전 1(IMDSv1) — 요청/응답 방법
- 인스턴스 메타데이터 서비스 버전 2(IMDSv2) — 세션 지향 방법
로컬 코드 또는 사용자가 IMDSv2를 사용해야 하도록 각 인스턴스에서 인스턴스 메타데이터 서비스를 구성할 수 있습니다.
인스턴스 메타데이터 서비스 버전 2 작동 방식
- IMDSv2는 세션 지향 요청을 사용합니다. 세션 지향 요청을 사용하여 세션 기간을 정의하는 세션 토큰을 생성합니다. 세션 기간은 최소 1초에서 최대 6시간일 수 있습니다.
- 지정된 기간 중에는 후속 요청에 동일한 세션 토큰을 사용할 수 있습니다. 지정된 기간이 만료된 후에는 향후 요청에 사용할 새로운 세션 토큰을 생성할 수 있습니다.
워커 노드 대 EC2 메타데이터(IMDSv2) 보안 제거
#
$ kops edit ig nodes-ap-northeast-2a
- -
# 아래 3줄 제거
spec:
instanceMetadata:
httpPutResponseHopLimit: 1
httpTokens: required
- -
# 업데이트 적용 : 노드1대 롤링업데이트
$ kops update cluster - yes && echo && sleep 3 && kops rolling-update cluster - yes
EC IAM Role & 메타데이터
파드에서 EC2 메타데이터 사용 가능 확인
# netshoot-pod 생성
$ cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: netshoot-pod
spec:
replicas: 2
selector:
matchLabels:
app: netshoot-pod
template:
metadata:
labels:
app: netshoot-pod
spec:
containers:
- name: netshoot-pod
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 파드 이름 변수 지정
$ PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].metadata.name})
$ PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].metadata.name})
# EC2 메타데이터 정보 확인
$ kubectl exec -it $PODNAME1 - curl 169.254.169.254 ;echo
$ kubectl exec -it $PODNAME2 - curl 169.254.169.254 ;echo
# 파드1에서 EC2 메타데이터 정보 확인
$ kubectl exec -it $PODNAME1 - curl 169.254.169.254/latest ;echo
$ kubectl exec -it $PODNAME1 - curl 169.254.169.254/latest/meta-data/iam/security-credentials/ ;echo
$ kubectl exec -it $PODNAME1 - curl 169.254.169.254/latest/meta-data/iam/security-credentials/nodes.$KOPS_CLUSTER_NAME | jq
# 파드2에서 EC2 메타데이터 정보 확인
$ kubectl exec -it $PODNAME2 - curl 169.254.169.254/latest ;echo
$ kubectl exec -it $PODNAME2 - curl 169.254.169.254/latest/meta-data/iam/security-credentials/ ;echo
$ kubectl exec -it $PODNAME2 - curl 169.254.169.254/latest/meta-data/iam/security-credentials/nodes.$KOPS_CLUSTER_NAME | jq
# 메타데이터 메모해두기
{
"Code": "Success",
"LastUpdated": "2023–03–01T14:11:26Z",
"Type": "AWS-HMAC",
"AccessKeyId": "XXXXXXXXXXXXXXXXXXX",
"SecretAccessKey": "XXXXXXXXXXXXXXXXX",
"Token": "XXXXXXXXXXXXXXXXXXXXXXXX",
"Expiration": "2023–03–01T20:12:11Z"
}
파드(컨테이너)탈취 후 EC2 메타데이터의 IAM Role 토큰 정보를 활용해 python boto3 를 통해 SDK로 AWS 서비스 강제 사용 [참고-aws boto3]
- Boto3란 : Python용 AWS SDK
- 기본적으로 AWS 리소스에 접근하기 위해서는 필연적으로 AWS IAM(AWS Identity and Access Management) 으로부터 권한을 받아야합니다.
- Boto3에는 자격증명(credentials)(aws_access_key_id , aws_secret_access_key 및 aws_session_token 항목) 과 비자격증명(non-credentials)(사용할 리전 또는 Amazon S3에 사용할 주소 지정 스타일과 같은 항목)의 두 가지 구성 데이터 유형이 있습니다.
Boto3가 AWS의 자격증명(Credentials)을 찾는 메커니즘은 아래와 같습니다.
- boto.client()메서드 에서 자격 증명을 매개 변수로 전달
# 예시
import boto3
client = boto3.client(
's3',
aws_access_key_id=ACCESS_KEY,
aws_secret_access_key=SECRET_KEY,
aws_session_token=SESSION_TOKEN
)
2. Session개체를 만들 때 자격 증명을 매개 변수로 전달
# 예시
import boto3
session = boto3.Session(
aws_access_key_id=ACCESS_KEY,
aws_secret_access_key=SECRET_KEY,
aws_session_token=SESSION_TOKEN
)
3. 환경 변수
# 예시
AWS_ACCESS_KEY_ID- AWS 계정의 액세스 키.
AWS_SECRET_ACCESS_KEY- AWS 계정의 비밀 키.
AWS_SESSION_TOKEN- AWS 계정의 세션 키. 임시 자격 증명을 사용하는 경우에만 필요.
4. 공유 자격 증명 파일( ~/.aws/credentials)
# 예시
[default]
aws_access_key_id=foo
aws_secret_access_key=bar
[dev]
aws_access_key_id=foo2
aws_secret_access_key=bar2
[prod]
aws_access_key_id=foo3
aws_secret_access_key=bar3
5. AWS 구성 파일( ~/.aws/config)
# 예시
[default]
aws_access_key_id=foo
aws_secret_access_key=bar
[profile dev]
aws_access_key_id=foo2
aws_secret_access_key=bar2
[profile prod]
aws_access_key_id=foo3
aws_secret_access_key=bar3
6. 역할 공급자 가정
# 예시
# In ~/.aws/credentials:
[development]
aws_access_key_id=foo
aws_access_key_id=bar
# In ~/.aws/config
[profile crossaccount]
role_arn=arn:aws:iam:...
source_profile=development
7. Boto2 구성 파일( /etc/boto.cfg및 ~/.boto)
# Example ~/.boto file
[Credentials]
aws_access_key_id = foo
aws_secret_access_key = bar
8. IAM 역할이 구성된 Amazon EC2 인스턴스의 인스턴스 메타데이터 서비스
- Amazon EC2에서 실행 중이고 위의 공급자가 자격 증명을 찾지 못한 경우 Boto3는 인스턴스 메타데이터 서비스에서 자격 증명을 로드하려고 시도합니다. 이 기능을 활용하려면 EC2 인스턴스를 시작할 때 사용할 IAM 역할을 지정해야 합니다.
# 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
# 파드1에서 boto3 사용
$ kubectl exec -it $PODNAME1 - sh
- - - - - -
$ cat <<EOF> ec2.py
import boto3
ec2 = boto3.client('ec2', region_name = 'ap-northeast-2')
response = ec2.describe_instances()
print(response)
EOF
python ec2.py # aws ec2 describe-vpcs
exit
- - - - - -
# 파드2에서 boto3 사용
$ kubectl exec -it $PODNAME2 - sh
- - - - - -
$ cat <<EOF> ec2.py
import boto3
ec2 = boto3.client('ec2', region_name = 'ap-northeast-2')
response = ec2.describe_instances()
print(response)
EOF
python ec2.py # aws ec2 describe-vpcs
exit
- - - - - -
# 실습 완료 후 삭제
$ kubectl delete deploy boto3-pod
- 대응 방안 : EC2 메타데이터 접속 제한 or AWS IRSA(IAM Role for Service Account) 사용
AWS IRSA : POD별로 권한 분리를 하기 위한 방안(Service Account IAM Role을 설정하고 Service Account를 Pod에 할당) (또는 OIDC를 이용하여 권한 분리)
Kubescape
- 보안 권고 사항 기반 현재 쿠버네티스 클러스터의 취약점을 점검합니다.
# 설치
$ curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
# Download all artifacts and save them in the default path (~/.kubescape)
$ kubescape download artifacts
$ tree ~/.kubescape/
/root/.kubescape/
├── allcontrols.json
├── armobest.json
├── attack-tracks.json
├── cis-eks-t1.2.0.json
├── cis-v1.23-t1.0.1.json
├── controls-inputs.json
├── devopsbest.json
├── exceptions.json
├── mitre.json
└── nsa.json
0 directories, 10 files
$ cat ~/.kubescape/attack-tracks.json | jq
# 제공하는 보안 프레임워크 확인
$ kubescape list frameworks --format json | jq '.[]'
"AllControls"
"ArmoBest"
"DevOpsBest"
"MITRE"
"NSA"
"cis-eks-t1.2.0"
"cis-v1.23-t1.0.1"
# 제공하는 통제 정책 확인
$ kubescape list controls
파드/컨테이너 보안 컨텍스트
- 컨테이너 보안 컨텍스트 SecurityContext : 각 컨테이너에 대한 보안 설정을 통해 침해사고 발생 시 침해사고를 당한 권한을 최대한 축소하여 그 사고에 대한 확대를 방지합니다.
# SecurityContext 예시
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo-2
spec:
securityContext:
runAsUser: 1000
containers:
- name: sec-ctx-demo-2
image: gcr.io/google-samples/node-hello:1.0
securityContext:
runAsUser: 2000
allowPrivilegeEscalation: false
위의 예시에 표시된 SecurityContext 의 의미는 아래와 같습니다.
- runAsUser : 실행 사용자
- allowPrivilegeEscalation : 컨테이너 실행 시 상위 프로세스보다 많은 권한을 부여할지 여부
$ kubectl get pod -n kube-system -o jsonpath={.items[*].spec.containers[*].securityContext} | jq
{
"allowPrivilegeEscalation": false,
"readOnlyRootFilesystem": true,
"runAsNonRoot": true
}
{
"capabilities": {
"add": [
"NET_ADMIN",
"NET_RAW"
]
}
}
...
- readOnlyRootFilesystem : root 파일 시스템을 읽기 전용으로 사용
#
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: rootfile-readonly
spec:
containers:
- name: netshoot
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
readOnlyRootFilesystem: true
terminationGracePeriodSeconds: 0
EOF
# 파일 생성 시도
$ kubectl exec -it rootfile-readonly -- touch /tmp/text.txt
touch: /tmp/text.txt: Read-only file system
command terminated with exit code 1
- Linux Capabilities : Linux는 전통적으로 수퍼유저와 관련된 권한을 별개의 단위인 Capabilities으로 나눕니다. 즉, Capabilities는 thread별 속성입니다.
# capsh --print
Current: =ep
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Ambient set =
Current IAB:
Securebits: 00/0x0/1'b0 (no-new-privs=0)
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
secure-no-ambient-raise: no (unlocked)
uid=0(root) euid=0(root)
gid=0(root)
groups=0(root)
Guessed mode: UNCERTAIN (0)
# proc 에서 확인 : bit 별 Capabilities
# cat /proc/1/status | egrep 'CapPrm|CapEff'
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
# 마스터 노드 Linux Capabilities 확인
$ ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME capsh --print
WARNING: libcap needs an update (cap=40 should have a name).
Current: =
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,38,39,40
Ambient set =
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
secure-no-ambient-raise: no (unlocked)
uid=1000(ubuntu) euid=1000(ubuntu)
gid=1000(ubuntu)
groups=4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),118(netdev),119(lxd),1000(ubuntu)
Guessed mode: UNCERTAIN (0)
- 파드의 Linux Capabilities 기본 확인
# 샘플 파드 생성
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: sample-capabilities
spec:
containers:
- name: nginx-container
image: masayaaoyama/nginx:capsh
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 파드의 Linux Capabilities 기본 확인
$ kubectl exec -it sample-capabilities -- capsh --print | grep Current
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
# proc 에서 확인 : bit 별 Capabilities - 링크
$ kubectl exec -it sample-capabilities -- cat /proc/1/status | egrep 'CapPrm|CapEff'
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
# 파드에서 시간 변경 시도
$ kubectl exec -it sample-capabilities -- date
Thu Apr 6 16:21:22 UTC 2023
# 파드에서 시간 변경 시도
$ kubectl exec -it sample-capabilities -- date -s "12:00:00"
date: cannot set date: Operation not permitted
Thu Apr 6 12:00:00 UTC 2023
command terminated with exit code 1
$ kubectl exec -it sample-capabilities -- date
Thu Apr 6 16:22:52 UTC 2023
# 파드가 배포된 워커노드 확인
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
awscli-pod-5c87478747-2chct 1/1 Running 0 145m 172.30.62.48 i-0d8582550153273dc <none> <none>
awscli-pod-5c87478747-44jc2 1/1 Running 0 145m 172.30.85.5 i-02f69111c92e36490 <none> <none>
netshoot-pod-7757d5dd99-kvbhh 1/1 Running 0 157m 172.30.53.131 i-0d8582550153273dc <none> <none>
netshoot-pod-7757d5dd99-mz6tc 1/1 Running 0 157m 172.30.85.4 i-02f69111c92e36490 <none> <none>
rootfile-readonly 1/1 Running 0 45m 172.30.53.132 i-0d8582550153273dc <none> <none>
sample-capabilities 1/1 Running 0 23m 172.30.85.6 i-02f69111c92e36490 <none> <none>
# 파드가 배포된 워커노드 IP 확인
$ kubectl get node -o wide | grep i-02f69111c92e36490
i-02f69111c92e36490 Ready node 168m v1.24.11 172.30.80.213 52.79.52.36 Ubuntu 20.04.5 LTS 5.15.0-1031-aws containerd://1.6.18
# 워커 노드 외부 IP로 접근
$ ssh -i ~/.ssh/id_rsa ubuntu@52.79.52.36
# 워커 노드에 systemd-timesyncd 종료
root@i-02f69111c92e36490:/home/ubuntu# systemctl stop systemd-timesyncd
# sample-capabilities 파드 시간이 변경되지않음을 알 수 있다.
$ kubectl exec -it sample-capabilities -- date
Thu Apr 6 16:48:42 UTC 2023
$ kubectl exec -it sample-capabilities -- date -s "12:00:00"
date: cannot set date: Operation not permitted
Thu Apr 6 12:00:00 UTC 2023
command terminated with exit code 1
$ kubectl exec -it sample-capabilities -- date
Thu Apr 6 16:48:52 UTC 2023
# 파드에 Linux Capabilities 부여 및 삭제
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: sample-capabilities2
spec:
containers:
- name: nginx-container
image: masayaaoyama/nginx:capsh
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
capabilities:
add: ["NET_ADMIN", "SYS_TIME"]
drop: ["AUDIT_WRITE"]
terminationGracePeriodSeconds: 0
EOF
# 파드의 Linux Capabilities 기본 확인
$ kubectl exec -it sample-capabilities2 -- capsh --print | grep Current
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_admin,cap_net_raw,cap_sys_chroot,cap_sys_time,cap_mknod,cap_setfcap+ep
# 파드 상세 정보 확인
$ kubectl get pod sample-capabilities2 -o jsonpath={.spec.containers[0].securityContext} | jq
{
"capabilities": {
"add": [
"NET_ADMIN",
"SYS_TIME"
],
"drop": [
"AUDIT_WRITE"
]
}
}
$ kubectl exec -it sample-capabilities2 -- cat /proc/1/status | egrep 'CapPrm|CapEff'
CapPrm: 000000008a0435fb
CapEff: 000000008a0435fb
# 파드가 배포된 워커노드 확인
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
awscli-pod-5c87478747-2chct 1/1 Running 0 145m 172.30.62.48 i-0d8582550153273dc <none> <none>
awscli-pod-5c87478747-44jc2 1/1 Running 0 145m 172.30.85.5 i-02f69111c92e36490 <none> <none>
netshoot-pod-7757d5dd99-kvbhh 1/1 Running 0 157m 172.30.53.131 i-0d8582550153273dc <none> <none>
netshoot-pod-7757d5dd99-mz6tc 1/1 Running 0 157m 172.30.85.4 i-02f69111c92e36490 <none> <none>
rootfile-readonly 1/1 Running 0 45m 172.30.53.132 i-0d8582550153273dc <none> <none>
sample-capabilities 1/1 Running 0 23m 172.30.85.6 i-02f69111c92e36490 <none> <none>
sample-capabilities2 1/1 Running 0 11m 172.30.53.133 i-0d8582550153273dc <none> <none>
# 파드가 배포된 워커노드 IP 확인
$ kubectl get node -o wide | grep i-02f69111c92e36490
i-0d8582550153273dc Ready node 3h10m v1.24.11 172.30.59.106 3.39.6.121 Ubuntu 20.04.5 LTS 5.15.0-1031-aws containerd://1.6.18
# 워커 노드 외부 IP로 접근
$ ssh -i ~/.ssh/id_rsa ubuntu@3.39.6.121
# 워커 노드에 systemd-timesyncd 종료
root@i-0d8582550153273dc:/home/ubuntu# systemctl stop systemd-timesyncd
# sample-capabilities2 파드 시간이 변경됨을 알 수 있다.
$ kubectl exec -it sample-capabilities2 -- date -s "12:00:00"
Thu Apr 6 12:00:00 UTC 2023
$ kubectl exec -it sample-capabilities2 -- date
Thu Apr 6 12:00:06 UTC 2023
2. 파드 보안 컨텍스트 : 파드에 포함된 모든 컨테이너가 영향을 받습니다.
- runAsUser 변경 : runuser 파드는 실행 사용자를 nobody(UID:65534) 사용자로 실행, 실행권한에 서브그룹 1001/1002 추가
#
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: rundefault
spec:
containers:
- name: centos
image: centos:7
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
readOnlyRootFilesystem: true
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: runuser
spec:
securityContext:
runAsUser: 65534
runAsGroup: 65534
supplementalGroups:
- 1001
- 1002
containers:
- name: centos
image: centos:7
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
readOnlyRootFilesystem: true
terminationGracePeriodSeconds: 0
EOF
#
$ kubectl get pod rundefault -o jsonpath={.spec.securityContext} | jq
$ kubectl get pod runuser -o jsonpath={.spec.securityContext} | jq
{
"runAsGroup": 65534,
"runAsUser": 65534,
"supplementalGroups": [
1001,
1002
]
}
# 실행 사용자 정보 확인
$ kubectl exec -it rundefault -- id
uid=0(root) gid=0(root) groups=0(root)
$ kubectl exec -it rundefault -- id
uid=0(root) gid=0(root) groups=0(root)
$ kubectl exec -it runuser -- id
uid=65534 gid=65534 groups=65534,1001,1002
# 프로세스 정보 확인
$ kubectl exec -it rundefault -- ps -axo uid,user,gid,group,pid,comm
UID USER GID GROUP PID COMMAND
0 root 0 root 1 tail
0 root 0 root 19 ps
$ kubectl exec -it runuser -- ps -axo uid,user,gid,group,pid,comm
UID USER GID GROUP PID COMMAND
65534 65534 65534 65534 1 tail
65534 65534 65534 65534 12 ps
- runAsNonRoot 로 실행 제한 시 아래와 같은 에러 발생
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: nonroot
spec:
securityContext:
runAsNonRoot: true
containers:
- name: netshoot
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 이벤트 확인
$ kubectl events --for pod/nonroot
LAST SEEN TYPE REASON OBJECT MESSAGE
4h47m Normal Pulled Pod/nonroot Successfully pulled image "nicolaka/netshoot" in 1.32982942s
4h47m Warning Failed Pod/nonroot Error: container has runAsNonRoot and image has non-numeric user (root), cannot verify user is non-root (pod: "nonroot_default(9796696b-d387-42e8-9a14-0c5ad32526d1)", container: netshoot)
4h47m (x2 over 4h47m) Normal Pulling Pod/nonroot Pulling image "nicolaka/netshoot"
3s Normal Scheduled Pod/nonroot Successfully assigned default/nonroot to i-0d8582550153273dc
— Error: container has runAsNonRoot and image has non-numeric user (root), cannot verify user is non-root (pod: “nonroot_default(9796696b-d387–42e8–9a14–0c5ad32526d1)”, container: netshoot)
- fsGroup(파일시스템그룹) 지정 : 일반적으로 마운트한 볼륨의 소유자와 그룹은 root:root로 되어 있습니다. 실행 사용자를 변경한 경우에는 마운트한 볼륨에 권한이 없는 경우가 있습니다. 따라서 마운트하는 볼륨의 그룹을 변경할 수 있습니다.
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: fsgoup1
spec:
volumes:
- name: vol1
emptyDir: {}
containers:
- name: centos
image: centos:7
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: vol1
mountPath: /data/demo
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: fsgoup2
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
volumes:
- name: vol2
emptyDir: {}
containers:
- name: centos
image: centos:7
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: vol2
mountPath: /data/demo
terminationGracePeriodSeconds: 0
EOF
$ kubectl get pod fsgoup1 -o jsonpath={.spec.securityContext} | jq
$ kubectl get pod fsgoup2 -o jsonpath={.spec.securityContext} | jq
{
"fsGroup": 2000,
"runAsGroup": 3000,
"runAsUser": 1000
}
# 실행 사용자 정보 확인
$ kubectl exec -it fsgoup1 -- id
uid=0(root) gid=0(root) groups=0(root)
$ kubectl exec -it fsgoup2 -- id
uid=1000 gid=3000 groups=3000,2000
# 프로세스 정보 확인
$ kubectl exec -it fsgoup1 -- ps -axo uid,user,gid,group,pid,comm
UID USER GID GROUP PID COMMAND
0 root 0 root 1 sleep
0 root 0 root 19 ps
# 디렉터리 정보 확인 : fsgoup2파드의 마운트 볼륨 그룹의 GID가 2000 (fsGroup: 2000)
$ kubectl exec -it fsgoup2 -- ps -axo uid,user,gid,group,pid,comm
UID USER GID GROUP PID COMMAND
1000 1000 3000 3000 1 sleep
1000 1000 3000 3000 14 ps
$ kubectl exec -it fsgoup1 -- ls -l /data
total 4
drwxrwxrwx 2 root root 4096 Apr 6 16:59 demo
$ kubectl exec -it fsgoup2 -- ls -l /data
total 4
drwxrwsrwx 2 root 2000 4096 Apr 6 16:59 demo
# fsgoup2파드에서 파일 생성 및 확인
$kubectl exec -it fsgoup2 -- sh -c "echo write > /data/demo/sample.txt"
$# kubectl exec -it fsgoup2 -- cat /data/demo/sample.txt
write
$ kubectl exec -it fsgoup2 -- ls -l /data/demo/sample.txt
-rw-r--r-- 1 1000 2000 6 Apr 6 17:01 /data/demo/sample.txt
# fsgoup2파드에서 다른 디렉토리에 파일 생성 시도 - 실패
$ kubectl exec -it fsgoup2 -- sh -c "echo write > /data/sample.txt"
sh: /data/sample.txt: Permission denied
command terminated with exit code 1
- sysctl을 사용한 커널 파라미터 설정 : 커널 파라미터 변경 적용을 위해서는 컨테이너에서도 설정이 필요합니다. 네임스페이스 sysctl을 구성하기 위해서 파드 securityContext를 사용합니다. securityContext는 동일한 파드의 모든 컨테이너에 적용됩니다.
- sysctl은 safe sysctl과 unsafe sysctl로 구성되어 있습니다.
- 즉, 하나의 파드에 safe sysctl을 설정한다는 것은 다음을 의미합니다.
— 노드의 다른 파드에 영향을 미치지 않아야 한다
— 노드의 상태를 손상시키지 않아야 한다
— CPU 또는 메모리 리소스가 파드의 리소스 제한에 벗어나는 것을 허용하지 않아야 한다
다음 sysctl은 safe 명령을 지원합니다.
- kernel.shm_rmid_forced,
- net.ipv4.ip_local_port_range,
- net.ipv4.tcp_syncookies,
- net.ipv4.ping_group_range (쿠버네티스 1.18 이후),
- net.ipv4.ip_unprivileged_port_start (쿠버네티스 1.22 이후).
# unsafe 파라미터를 변경 시도
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: unsafe
spec:
securityContext:
sysctls:
- name: net.core.somaxconn
value: "12345"
containers:
- name: centos-container
image: centos:7
command: ["/bin/sleep", "3600"]
terminationGracePeriodSeconds: 0
EOF
$ kubectl events --for pod/unsafe
LAST SEEN TYPE REASON OBJECT MESSAGE
4h47m Warning SysctlForbidden Pod/unsafe forbidden sysctl: "net.core.somaxconn" not allowlisted
4s Normal Scheduled Pod/unsafe Successfully assigned default/unsafe to i-0d8582550153273dc
# safe 파라미터 수정 - sysctl2 파드가 배포된 노드의 net.ipv4.ip_local_port_range 값과 다를 경우
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: sysctl1
spec:
containers:
- name: centos-container
image: centos:7
command: ["/bin/sleep", "3600"]
terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
name: sysctl2
spec:
securityContext:
sysctls:
- name: net.ipv4.ip_local_port_range
value: "1025 61000"
containers:
- name: centos-container
image: centos:7
command: ["/bin/sleep", "3600"]
terminationGracePeriodSeconds: 0
EOF
#
$ kubectl get pod sysctl1 -o jsonpath={.spec.securityContext} | jq
$ kubectl get pod sysctl2 -o jsonpath={.spec.securityContext} | jq
{
"sysctls": [
{
"name": "net.ipv4.ip_local_port_range",
"value": "1025 61000"
}
]
}
$ kubectl exec -it sysctl1 -- sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999
$ kubectl exec -it sysctl2 -- sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 1025 61000
# initContainer 와 privileged 를 활용하여 unsafe 커널 파라미터를 강제로 변경
$ curl -s -O https://raw.githubusercontent.com/MasayaAoyama/kubernetes-perfect-guide/ko/2nd-edition/samples/chapter13/sample-sysctl-initcontainer.yaml
$ cat sample-sysctl-initcontainer.yaml| yh
apiVersion: v1
kind: Pod
metadata:
name: sample-sysctl-initcontainer
spec:
initContainers:
- name: initialize-sysctl
image: busybox:1.27
command:
- /bin/sh
- -c
- |
sysctl -w net.core.somaxconn=12345
securityContext:
privileged: true
containers:
- name: tools-container
image: amsy810/tools:v2.0
$ kubectl apply -f sample-sysctl-initcontainer.yaml
$ kubectl exec -it sample-sysctl-initcontainer -c tools-container -- sysctl net.core.somaxconn
net.core.somaxconn = 12345
polaris
- 오픈소스 보안 점검 도구입니다.
# 설치
$ kubectl create ns polaris
#
$ cat <<EOT > polaris-values.yaml
dashboard:
replicas: 1
service:
type: LoadBalancer
EOT
# 배포
$ helm repo add fairwinds-stable https://charts.fairwinds.com/stable
$ helm install polaris fairwinds-stable/polaris --namespace polaris --version 5.7.2 -f polaris-values.yaml
# CLB에 ExternanDNS 로 도메인 연결
$ kubectl annotate service polaris-dashboard "external-dns.alpha.kubernetes.io/hostname=polaris.$KOPS_CLUSTER_NAME" -n polaris
# 웹 접속 주소 확인 및 접속
$ echo -e "Polaris Web URL = http://polaris.$KOPS_CLUSTER_NAME"
K8S 인증/인가 & RBAC
- 서비스 어카운트(Service Account)
- API 서버 사용 : kubectl(config, 다수 클러스터 관리 가능), 서비스 어카운트, https(x.509 Client Certs)
- API 서버 접근 과정 : 인증 → 인가 → Admission Control(API 요청 검증, 필요 시 변형 — 예. ResourceQuota, LimitRange)
- X.509 Client Certs : kubeconfig 에 CA crt(발급 기관 인증서) , Client crt(클라이언트 인증서) , Client key(클라이언트 개인키) 를 통해 인증
- kubectl : 여러 클러스터(kubeconfig)를 관리 가능 — contexts 에 클러스터와 유저 및 인증서/키 참고
- Service Account : 기본 서비스 어카운트(default) — 시크릿(CA crt 와 token)
- 인가 방식 : RBAC(Role, RoleBinding), ABAC, Webhook, Node Authorization
- RBAC : 역할 기반의 권한 관리, 사용자와 역할을 별개로 선언 후 두가지를 조합(binding)해서 사용자에게 권한을 부여하여 kubectl or API로 관리 가능
- Namespace/Cluster — Role/ClusterRole, RoleBinding/ClusterRoleBinding, Service Account
- Role(롤) — (RoleBinding 롤 바인딩) — Service Account(서비스 어카운트) : 롤 바인딩은 롤과 서비스 어카운트를 연결
- Role(네임스페이스내 자원의 권한) vs ClusterRole(클러스터 수준의 자원의 권한)
written in 2023.4.6
https://medium.com/techblog-hayleyshim/k8s-security-11147c78ee28
'IT > Container&k8s' 카테고리의 다른 글
[k8s] Service Mesh (0) | 2023.10.29 |
---|---|
[k8s] cluster install - cri-o runtime error (0) | 2023.10.29 |
[k8s] Monitoring(Prometheus, Grafana, Loki) (0) | 2023.10.29 |
[k8s] GitOps(Image registy, Source repository, Continuous Delivery) (0) | 2023.10.29 |
[k8s] Storage (0) | 2023.10.29 |
- Total
- Today
- Yesterday
- security
- PYTHON
- 파이썬
- AWS
- cni
- k8s calico
- 혼공챌린지
- handson
- 혼공단
- controltower
- 혼공파
- terraform
- AI
- GKE
- GCP
- OS
- SDWAN
- S3
- EKS
- k8s cni
- k8s
- VPN
- 도서
- 국제 개발 협력
- NFT
- IaC
- NW
- cloud
- gcp serverless
- operator
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |