[CKS] Kubernetes 노드 보안 강화
Kubernetes 노드 메타데이터 보호 전략으로 RBAC, 네트워크 정책, 감사 로그를 활용하여 민감한 시스템 정보 노출을 방지하고 워크로드 무결성을 보장합니다.
개요
CKS 시험을 대비하여 kubernetes에서 노드 메타데이터를 보호하는 방법을 살펴봅니다.
노드 메타데이터 구성
노드 메타데이터의 주요 구성과 사용 용도는 다음과 같습니다.
- 노드 이름/고유 ID: 각 노드의 고유 식별자
- Labels(레이블): 노드를 그룹화하는 데 사용되는 키-값 쌍
- Annotations(주석): 디버깅, 로깅 또는 모니터링에 사용되는 추가 데이터
- 아키텍처: 하드웨어 아키텍처에 대한 정보(예: x86-64).
- 시스템 정보: Machine ID, System UUID, Boot ID, 커널 버전, 운영 체제 세부 정보, 컨테이너 런타임 버전 및 Kubernetes 구성 요소 버전을 포함한 시스템 데이터
- 주소: 내부 및 외부 IP 주소 목록
- 기타 주요 구성 요소: 노드 상태(예: Ready, OutOfDisk), 리소스 용량, taints와 tolerations, CIDR, kubelet 버전 및 클라우드 공급자별 ID.
Labels(레이블): 노드를 그룹화하는 데 사용되는 키-값 쌍
Labels:
beta.kubernetes.io/arch: amd64
beta.kubernetes.io/os: linux
kubernetes.io/arch: amd64
kubernetes.io/hostname: node01
kubernetes.io/os: linux
region: us-east-1
- Annotations(주석): 디버깅, 로깅 또는 모니터링에 사용되는 추가 데이터
- Flannel은 구성을 위해 어노테이션을 사용합니다.
Annotations:
flannel.alpha.coreos.com/backend-data: '{"VNI":1,"VtepMAC":"a2:bd:8e:41:63:65"}'
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: "true"
flannel.alpha.coreos.com/public-ip: 192.168.87.255
kubeadm.alpha.kubernetes.io/cri-socket: unix:///var/run/containerd/containerd.sock
node.alpha.kubernetes.io/ttl: "0"
volumes.kubernetes.io/controller-managed-attach-detach: "true"
- 아키텍처: 하드웨어 아키텍처에 대한 정보(예: x86-64).
- 시스템 정보: Machine ID, System UUID, Boot ID, 커널 버전, 운영 체제 세부 정보, 컨테이너 런타임 버전 및 Kubernetes 구성 요소 버전을 포함한 시스템 데이터
System Info:
Machine ID: 69ee5c89434f4d5baea262a6ecc698fe
System UUID: 8ab83d3f-465d-36a9-6ec2-b7e9e7ad6a45
Boot ID: 8059e764-a637-45f0-abd9-36e9a366e719
Kernel Version: 5.15.0-1065-gcp
OS Image: Ubuntu 22.04.4 LTS
Operating System: linux
Architecture: amd64
Container Runtime Version: containerd://1.6.26
Kubelet Version: v1.30.0
Kube-Proxy Version: v1.30.0
- 버전 정보는 공격자의 취약점, 익스플로잇같은 커스텀한 공격에 취약할 수 있습니다.
- 횡적 이동(lateral movement)의 취약합니다.
노드 메타데이터는 클러스터 관리에 필수적이지만, 민감한 정보가 많아서 보안이 중요합니다.
노드 메타데이터를 보호해야하는 이유
노드 메타데이터는 무결성과 보안을 유지하는데 필수적입니다.
특정한 파드는 민감한 워크로드를 나타내며 엄격한 보안 기준을 충족해야하는 경우가 존재합니다. 만약 노드의 메타데이터가 손상되거나 변조될 수 있습니다.
만약 노드 메타데이터가 변조되면 민감한 워크로드가 보안 취약한 노드에 배치될 수 있습니다.
노드 메타데이터의 위험성
<1. 부적절한 워크로드 스케줄링>
잘못된 메타데이터로 중요하지 않은 파드가 격리되고 중요하지 않은 파드가 취약한 네트워크 등에 노출될 수 있습니다. 대표적으로 프로덕션 노드에서 중요한 Taint 설정이 제거되면 비프로덕션 워크로드가 해당 노드에 스케줄링 될 수 있습니다.
이로 인해 리소스 경합이나 임시적인 서비스 중단이 발생할 수 있습니다.
kubectl taint nodes node-1 key=value:NoSchedule-
# node/node-1 untainted
<2. 부적절한 데이터 노출>
권한 없는 사용자가 Kubernetes API와 Kubelet에 접근하여 버전, 시스템 정보같은 민감한 정보를 탈취할 수 있습니다.
kubectl get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}'
<3. 네트워크 매핑 및 공격>
IP 주소를 통해 DDoS 공격과 같은 네트워크 기반 공격에 취약해질 수 있습니다.
또한 내부 네트워크 구조를 파악하여 정보 탈취 등을 수행할 수 있습니다.
kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}'
<4. 규정 위반>
커널 버전과 같은 노드 세부 정보에 대한 무단 접근은 GDPR 또는 HIPAA 등의 규제를 적용받을 수 있습니다.
kubectl get nodes -o jsonpath='{.items[*].status.nodeInfo.kernelVersion}'
# 5.4.0-1041-aws 4.15.0-142-generic 5.8.0-53-generic
심각한 취약점에 노출되지 않기 위해서 엄격한 접근 제어와 무단 변경 사항이 있는지 메타데이트를 반드시 정기적으로 감사해야합니다.
보안 전략
- RBAC(Role-Based Access Control)
- kubernetes에는 역할 기반 접근 제어를 설정할 수 있습니다.
- Node Isolation(노드 격리)
- 특정 워크로드에 맞는 노드를 예약하고 노드를 네트워크 상의 격리된 공간에 배치할 수 있습니다.
- 시스템은 중요하지 않거나 승인되지 않은 애플리케이션이 중요한 노드에 할당 되는 것을 방지합니다.
- Network Policies(네트워크 정책)
- 네트워크 정책을 통해 노드와 파드 간의 통신을 제어할 수 있습니다.
- Audit Logs(감사 로그)
- 노드 메타데이터에 접근 및 수정 사항을 추적하여 상세 기록을 관리합니다.
- Updates and Patches
- 노드에 체계적인 업데이트와 패치를 제공합니다.
이로써 쿠버네티스의 안정성과 워크로드 무결성을 보장할 수 있습니다.
Auditing
Auditing은 클러스터 내 모든 활동을 기록하고 추적하여 상세한 이벤트 이력을 생성하는 과정입니다.
보안 강화, 규정 준수, 트러블 슈팅 과정에서 매우 중요한 설정입니다. 관리자는 리소스 접근을 모니터링하고, 잠재적인 보안 침해를 감지하며, 모든 작업이 조직 정책을 준수하는지 확인할 수 있습니다.
다음은 Nginx 배포를 생성하는 감사 이벤트 예시입니다. 이벤트 세부 정보로 사용자 ID, 영향 받는 리소스와 요청 수신 및 처리 시간을 표시하는 타임스탬프가 있습니다.
API 버전, 작업 유형, 요청 사용자, 타임스탬프와 같은 주요 세부 정보가 기록됩니다.
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "Metadata",
"auditID": "f4d9a5c1-5f4d-48cb-bd27-2f66c9d9c7c6",
"stage": "ResponseComplete",
"requestURI": "/apis/apps/v1/namespaces/default/deployments",
"verb": "create",
"user": {
"username": "admin",
"groups": [
"system:masters",
"system:authenticated"
]
},
"sourceIPs": ["192.168.1.10"],
"userAgent": "kubectl/v1.20.0 (linux/amd64) kubernetes/af46c47",
"objectRef": {
"resource": "deployments",
"namespace": "default",
"name": "nginx-deployment",
"apiVersion": "apps/v1"
},
"responseStatus": {
"metadata": {},
"code": 201
},
"requestReceivedTimestamp": "2024-07-29T07:50:04.123456Z",
"stageTimestamp": "2024-07-29T07:50:04.223456Z",
"annotations": {
"authorization.k8s.io/decision": "allow",
"authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding \"cluster-admin\" of ClusterRole \"cluster-admin\" to User \"admin\""
}
}
Auditing이 필요한 이유
이러한 감사 로그는 규정 준수 감사(audit)를 받을 때, 감독 기관이나 외부 감사자에게 제출하거나 리소스가 조직 정책에 따라 관리되도록 보장하는데 사용됩니다. 감사 기능을 통해 관리자는 리소스 및 구성 변경 사항을 모니터링할 수 있으므로 문제의 근본 원인을 파악하여 문제를 더 쉽게 관리하고 해결할 수 있습니다.
감사 정책
- None (없음)
- Metadata (메타데이터): 로그는 요청/응답 본문 없이 메타데이터(사용자, 타임스탬프, 리소스, 행위)만 요청
- Request (요청): 이벤트 메타데이터와 요청 본문을 모두 기록하지만 응답 본문은 제외합니다.
- RequestResponse (요청응답): 이벤트 메타데이터, 요청 본문 및 응답 본문을 기록합니다.
Policy에서 level로 관리됩니다.
메타데이터 감사 정책
사용자 이름, 타임스탬프 및 리소스 정보와 같은 세부 정보가 캡처됩니다
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
verbs: ["get", "list", "create", "delete", "update"]
resources:
- group: ""
resources: ["pods"]
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "Metadata",
"timestamp": "2024-10-11T12:34:56Z",
"user": {
"username": "system:serviceaccount:kube-system:default",
"groups": ["system:serviceaccounts", "system:serviceaccounts:kube-system"]
},
"verb": "get",
"namespace": "default",
"resource": "pods",
"stage": "ResponseComplete",
"objectRef": {
"resource": "pods",
"namespace": "default",
"name": "example-pod"
}
}
요청 감사 정책
요청 객체에 대한 전체 세부 정보가 포함됩니다.
requestObject
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Request
verbs: ["create", "delete", "update"]
resources:
- group: ""
resources: ["pods"]
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "Request",
"timestamp": "2024-10-11T12:34:56Z",
"user": {
"username": "system:serviceaccount:kube-system:default",
"groups": ["system:serviceaccounts", "system:serviceaccounts:kube-system"]
},
"verb": "create",
"namespace": "default",
"resource": "pods",
"stage": "ResponseComplete",
"objectRef": {
"resource": "pods",
"namespace": "default",
"name": "example-pod"
},
"requestObject": {
"metadata": {
"name": "example-pod",
"namespace": "default"
},
"spec": {
"containers": [
{
"name": "example-container",
"image": "nginx"
}
]
}
}
}
요청응답 감사 정책
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
verbs: ["create", "delete", "update"]
resources:
- group: ""
resources: ["pods"]
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "RequestResponse",
"timestamp": "2024-10-11T12:34:56Z",
"user": {
"username": "system:serviceaccount:kube-system:default",
"groups": ["system:serviceaccounts", "system:serviceaccounts:kube-system"]
},
"verb": "create",
"namespace": "default",
"resource": "pods",
"stage": "ResponseComplete",
"objectRef": {
"resource": "pods",
"namespace": "default",
"name": "example-pod"
},
"requestObject": {
"metadata": {
"name": "example-pod",
"namespace": "default"
},
"spec": {
"containers": [
{
"name": "example-container",
"image": "nginx"
}
]
}
},
"responseObject": {
"metadata": {
"name": "example-pod",
"namespace": "default",
"uid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"creationTimestamp": "2024-10-11T12:34:56Z"
},
"spec": {
"containers": [
{
"name": "example-container",
"image": "nginx"
}
],
"nodeName": "worker-node-01"
},
"status": {
"phase": "Pending"
}
}
}
결론은 Audit은 클러스터 보안 및 관리에 필수적입니다.
모든 엑세스 요청 및 변경 사항은 포괄적인 정보를 포함하기에 규정 준수, 보안 사고 감지, 문제 해결에 용이합니다.
관리자는 적절한 정책을 이해하고 구현할 수 있어야합니다.
실습 1(Implementing Node Metadata Protection)
kubectl exec app -- curl -s http://192.168.121.74:9999
{"accountId": "123456789012", "architecture": "x86_64", "availabilityZone": "us-east-1a", "billingProducts": null, "devpayProductCodes": null, "marketplaceProductCodes": null, "imageId": "ami-0abcdef1234567890", "instanceId": "i-0abcdef1234567890", "instanceType": "t2.micro", "kernelId": null, "pendingTime": "2024-09-26T14:18:43Z", "privateIp": "192.168.1.1", "ramdiskId": null, "region": "us-east-1", "version": "2017-09-30", "availabilityZoneId": "use1-az2", "instanceLifecycle": "on-demand", "localHostname": "ip-192-168-1-1.ec2.internal", "localIpv4": "192.168.1.1", "mac": "02:7b:64:0e:59:7a", "networkInterfaces": [{"mac": "02:7b:64:0e:59:7a", "ownerId": "123456789012", "deviceNumber": 0, "interfaceId": "eni-1234abcd", "subnetId": "subnet-1234abcd", "vpcId": "vpc-1234abcd", "securityGroups": [{"groupId": "sg-1234abcd", "groupName": "default"}], "ipv4Addresses": ["192.168.1.1"]}], "iam": {"info": {"instanceProfileArn": "arn:aws:iam::123456789012:instance-profile/my-instance-profile", "instanceProfileId": "AIPAJOIG4WLEF42WPZ5DG"}, "securityCredentials": {"roleName": {"AccessKeyId": "ASIA...", "SecretAccessKey": "Secret...", "Token": "Token...", "Expiration": "2024-09-26T18:18:43Z"}}}, "tags": {"Name": "MyInstance", "Environment": "Development"}}
Network Policy를 등록합니다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-metadata
namespace: default
spec:
podSelector:
matchLabels:
app: app
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 192.168.121.74/32 # Control Plane으로만 못 나가게 차단함.
차단 된 것을 확인할 수 있습니다.
CONTROLPLANE_IP=$(kubectl get node controlplane -o jsonpath="{.status.addresses[0].address}")
kubectl exec app -- curl -s http://$CONTROLPLANE_IP:9999
실습 2(Setting Access Controls for Node Metadata)
Role-Based Access Control (RBAC)에 대해 실습했습니다.
Cluster Role binding
- 클러스터 전체
Role binding
- 특정 네임스페이스만 적용
k create clusterrole node-viewer --verb get,list,watch --resource nodes
k create clusterrolebinding node-viewer-binding --clusterrole node-viwer --serviceaccount default:node-viewer-service
Pod에 Service Account를 바인딩 합니다.
apiVersion: v1
kind: Pod
metadata:
labels:
run: pod
name: api-test-pod
spec:
serviceAccountName: node-viewer-service
containers:
- args:
- api-test-pod
image: nginx
name: api-test
command: ["sleep", "infinity"]
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Never
status: {}