[CloudNet@] 쿠버네티스 실무 실습 스터디 5주차 - 보안

s3ich4n·2023년 4월 9일
0
post-thumbnail

이 내용은 CloudNet@ 에서 진행하는 쿠버네티스 실무 실습 스터디에 대한 연재글입니다.

스터디에서 사용하는 교재는 24단계 실습으로 정복하는 쿠버네티스 입니다.

본 3주차에는 교재의 제 5부 내용을 살펴보고 있습니다. 전체 컨텍스트를 이해하시려면 교재를 참고하시기를 추천드립니다.

Prerequisites

쿠버네티스를 클라우드 환경에서 사용하고자 하는 것 자체가 이미 하기 사항들에 대한 기본적인 이해와 준비사항을 요합니다.

이번 연재글에서는 개념에 대한 상세한 추가설명은 가급적 없이 작성하려 합니다. 다만 제가 공부하며 기본적으로 알아야겠다 싶은 연재글에 대해 대신 소개드립니다.

들어가며

사전 준비사항 (1)

  1. AWS Free Tier 계정(비용문제로 인해 필요합니다!)
  2. IAM User 생성 후 권한 부여
    1. 학습을 위해 자신의 작업환경 IP에서만 접근할 수 있도록 하고, 관리자 권한을 주는 식으로 해결해도 좋습니다.
  3. Route 53 퍼블릭 호스팅 영역
    1. 혹은 도메인 구매사이트에서 도메인 구매 후, Route 53 설정 지정하기

사전 준비사항 (2)

주의! 비용이 많이 발생할 수 있으니, 빠르게 실습 후 종료하시기를 권장드립니다.

  1. kOps 커맨드를 수행할 인스턴스 t3.small
  2. 마스터 노드, 워커 노드 t3.xlarge (vCPU 4, 16GiB)

아래 커맨드를 통해 배포하여 주십시오.

설치과정 (1) 클러스터 배포

  • ClusterBaseName 은 사용중인 도메인을 이용해야합니다. AWS Route 53의 퍼블릭 호스팅 영역(도메인) 구입 후 진행해주십시오.
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/kops-oneclick-f1.yaml

# CloudFormation 스택 배포 : 노드 인스턴스 타입 변경
#   MasterNodeInstanceType=t3.medium
#   WorkerNodeInstanceType=c5d.large
aws cloudformation deploy \
    --template-file kops-oneclick-f1.yaml \
    --stack-name mykops \
    --parameter-overrides \
        KeyName=<개인별 키 이름> \
        SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 \
        MyIamUserAccessKeyID=<IAM 액세스 키> \
        MyIamUserSecretAccessKey=<IAM 시크릿 액세스 키> \
        ClusterBaseName=<도메인 명> \
        S3StateStore=<s3 버킷명> \
        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

# 약 10분 후 작업 SSH 접속 (13분 전후로 기다리세요!)
ssh \
    -i /path/to/<본인의 키 이름>.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

설치과정 (2) 보안요소 제거 (테스트용!)

#
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

들어가며: 보안의 중요성

쇠사슬은 가장 약한 고리만큼 강할 뿐이다.

A chain is only as strong as its weakest link.

보안에 대해 공부할 때 자주 듣던 이러한 격언이 기억났습니다. 커져가는 시스템일 수록 보안을 신경써야합니다. 시스템이 커지고 자동화되기 시작하는 것들이 많아지면 쉽게 놓치게 됩니다.

공격 시나리오와 EC2 IAM Role, 그리고 메타데이터

접속 키의 부주의한 관리로 인해 IAM 권한이 탈취된 상황을 가정해봅시다.

사실 이러한 상황은 실 환경에서 이루어질 것이라고 보진 않습니다. 구조화된 환경이라면 필히 주어진 권한을 임시요청(AssumeRole)하는 등, 임시권한을 통한 리소스 확보를 얻어가는 것이 일반적일 것입니다(AWS STS 등이 기반이 되겠지요).

시나리오 1: 파드에서 EC2 메타데이터 탈취

파드에서 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

# RESULT)
(s3ich4n:N/A) [root@kops-ec2 ~]# kubectl get pods -o wide
NAME                            READY   STATUS    RESTARTS   AGE   IP              NODE                  NOMINATED NODE   READINESS GATES
netshoot-pod-7757d5dd99-2xwnc   1/1     Running   0          13s   172.30.93.0     i-07f358a55d8154978   <none>           <none>
netshoot-pod-7757d5dd99-44m79   1/1     Running   0          13s   172.30.54.224   i-0105868c724fdffbc   <none>           <none>


# 파드 이름 변수 지정
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

# RESULT) 저는 PODNAME2 에서만 결과가 나오네요!

# 파드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

# RESULT
#   이후 출력되는 메타데이터를 잘 기록해둡시다.
#   액세스키, 시크릿액세스키, 토큰을 참고해주세요.
(s3ich4n:N/A) [root@kops-ec2 ~]# kubectl exec -it $PODNAME2 -- curl 169.254.169.254/latest ;echo
dynamic
meta-data
user-data
(s3ich4n:N/A) [root@kops-ec2 ~]# kubectl exec -it $PODNAME2 -- curl 169.254.169.254/latest/meta-data/iam/security-credentials/ ;echo
nodes.s3ich4n.me
(s3ich4n:N/A) [root@kops-ec2 ~]# kubectl exec -it $PODNAME2 -- curl 169.254.169.254/latest/meta-data/iam/security-credentials/nodes.$KOPS_CLUSTER_NAME | jq
{
  "Code": "Success",
  "LastUpdated": "2023-04-09T18:16:04Z",
  "Type": "AWS-HMAC",
  "AccessKeyId": "REDACTED!!!!",
  "SecretAccessKey": "REDACTED!!!!",
  "Token": "REDACTED!!!!",
  "Expiration": "2023-04-10T00:50:45Z"
}

시나리오 2: 토큰 정보로 AWS 서비스 강제 사용

# 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})

# 파드1에서 boto3 사용 -> credentials이 없으니 안되겠지요.
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
------------
# RESULT)
(s3ich4n:N/A) [root@kops-ec2 ~]# kubectl exec -it $PODNAME2 -- sh
~/dev # ls
~/dev # ls
~/dev # cat <<EOF> ec2.py
> import boto3
>
> ec2 = boto3.client('ec2', region_name = 'ap-northeast-2')
> response = ec2.describe_instances()
> print(response)
> EOF
~/dev # python ec2.py
{u'Reservations': [{u'OwnerId': '240962124292', u'ReservationId': 'r-0ae3714333461e9df', u'Groups': [], u'RequesterId': '722737459838', u'Instances': [{u'Monitoring': {u'State': 'disabled'}, u'PublicDnsName': 'ec2-3-34-181-55.ap-northeast-2.compute.amazonaws.com', u'State': {u'Code': 16, u'Name': 'running'}, u'EbsOptimized': False, u'LaunchTime': datetime.datetime(2023, 4, 9, 18, 6, 17, tzinfo=tzlocal()), u'PublicIpAddress': '3.34.181.55', u'PrivateIpAddress': '172.30.84.250', u'ProductCodes': [], u'VpcId': 'vpc-09764afee4a0312ec', u'StateTransit
(생략)

# 실습 완료 후 삭제
kubectl delete deploy boto3-pod

만일 귀찮아서 관리자권한을 모두 주었다면, boto3 SDK 를 이용해서 공격자가 무엇이든 할 수 있었을 것입니다. 토큰 기간이 있기야 하지만, 공격자는 미리 공격코드를 준비한 채로 침투했겠죠. 만일, 공격자가 제 클러스터에 ELB 를 구축한다면 아마 이런 코드를 사용할 수 있을 것입니다:

import pprint
from boto3_helper import *

# 80, TCP 를 열어둔 보안그룹을 찾습니다.
security_groups = ec2_get_security_group_list()
for security_group in security_groups:
    security_group_ip_perms = security_group['IpPermissions']
    for security_group_ip_perm in security_group_ip_perms:
        if security_group_ip_perm['IpProtocol'] == 'tcp' and security_group_ip_perm['FromPort'] == 80:
            vpc_id = security_group['VpcId']
            security_group_id = security_group['GroupId']
            break

# 상기 보안그룹과 연관된 VPC 값과 서브넷값을 찾습니다.
subnet_list = ec2_get_subnet_list()
subnet_id_list = []
for subnet in subnet_list:
    if subnet['VpcId'] == vpc_id:
        subnet_id_list.append(subnet['SubnetId'])

print (subnet_id_list, vpc_id, security_group_id)

# 타겟 보안그룹을 생성합니다.
target_group = elb_create_target_group(
    's3ich4n-target-group',
    vpc_id,
)
target_group_arn = target_group['TargetGroups'][0]['TargetGroupArn']

session = init_aws_session()
elb = session.client('elbv2')

# 로드밸런서를 생성합니다.
response = elb.create_load_balancer(
    Name='s3ich4nLoadBalancer',
    Subnets = subnet_id_list,
    SecurityGroups=[
        security_group_id,
    ],
    Scheme='internal',
    Type='application',
    IpAddressType='ipv4',
)
pprint.pprint(response)

Mitigation?

이를 대응하기 위해, EC2 메타데이터에 대한 접속제한을 수행하거나, AWS의 IRSA(IAM Roles for Service Accounts)을 사용하는 것이 필요합니다. 이를 통해 AWS 리소스를 접근하는 일을 차단해야겠지요.

IRSA는 파드별로 IAM Role을 부여하는 것으로, 각각 개별 IAM 롤을 가지도록 분리하는 것을 의미합니다. 내부과정에서 AWS의 OIDC(OpenID Connect) 프로바이더를 통해 진두지휘 됩니다. 아는대로 살펴보자면 IdP(STS, IAM이겠지요) 및 쿠버네티스 클러스터(EKS 나 kOps로 배포한 클러스터겠지요)가 위치하고 있을것이며, OIDC Flow를 따를 것으로 보입니다. 이 때 토큰은 JWT 토큰을 사용할 것입니다.

출처: https://ssup2.github.io/theory_analysis/AWS_EKS_Service_Account_IAM_Role/

출처: https://tech.devsisters.com/posts/pod-iam-role/

kubescape: 권고사항 기반의 취약점 점검

그렇다면, 담당자라면 이런 사항을 자동으로 해주는 솔루션이나 오픈소스를 찾게 될 것입니다. 다행히도 CNCF 재단의 샌드박스 프로젝트에서 오픈소스로 개발중인 Kubescape 라는 도구가 있습니다.

Kubescape는 IDE, CI/CD 파이프라인, 클러스터에 사용할 수 있는 오픈소스 k8s 보안 플랫폼입니다. 이를 이용해 보안 취약점은 물론, 컴플라이언스 및 헐거운 설정[^1]을 찾아서 개선책을 권고해줍니다.

YAML 파일, 헬름차트, 클러스터를 스캔해서 헐거운 설정을 찾아냅니다. 이에 대한 근거는 NSA-CISA, MITRE ATT&CK, CIS Benchmark 입니다(쟁쟁하군요).

설치

그렇다면, 한번 직접 깔아서 구동해봅시다.

# 설치
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/
cat ~/.kubescape/attack-tracks.json | jq

# RESULT)
#   MITRE ATT&CK 으로 봤더니, 장난아니게 많이 나오네요!
(s3ich4n:N/A) [root@kops-ec2 ~]# 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
(s3ich4n:N/A) [root@kops-ec2 ~]# cat ~/.kubescape/mitre.json | jq
{
  "guid": "",
  "name": "MITRE",
  "attributes": {
    "armoBuiltin": true
  },
  "creationTime": "",
  "description": "Testing MITRE for Kubernetes as suggested by microsoft in https://www.microsoft.com/security/blog/wp-content/uploads/2020/04/k8s-matrix.png",
(생략)


# 제공하는 보안 프레임워크 확인
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

# 모니터링
watch kubectl get pod -A

# 클러스터 스캔
# Deploy Kubescape host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data.
# Required to collect valuable data from cluster nodes for certain controls.
# Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml
kubescape scan --help
kubescape scan --enable-host-scan --verbose

# RESULT)
#   진짜 결과가 한참 나옵니다! 이걸 터미널로 다 볼 수는 없겠습니다. 일부 생략합니다.
+----------+-------------------------------------------------------+------------------+---------------+--------------------+
| SEVERITY |                     CONTROL NAME                      | FAILED RESOURCES | ALL RESOURCES |    % RISK-SCORE    |
+----------+-------------------------------------------------------+------------------+---------------+--------------------+
| Critical | API server insecure port is enabled                   |        0         |       1       |         0%         |
| Critical | Disable anonymous access to Kubelet service           |        0         |       3       |         0%         |
| Critical | Enforce Kubelet client TLS authentication             |        0         |       6       |         0%         |
| Critical | CVE-2022-39328-grafana-auth-bypass                    |        0         |       0       |         0%         |
| Low      | Naked PODs                                            |        0         |      35       |         0%         |
| Low      | Image pull policy on latest tag                       |        0         |      24       |         0%         |
| Low      | Label usage for resources                             |        6         |      24       |        26%         |
| Low      | K8s common labels usage                               |        4         |      24       |        19%         |
+----------+-------------------------------------------------------+------------------+---------------+--------------------+
|          |                   RESOURCE SUMMARY                    |        53        |      229      |       9.70%        |
+----------+-------------------------------------------------------+------------------+---------------+--------------------+
FRAMEWORKS: AllControls (risk: 10.09), NSA (risk: 13.18), MITRE (risk: 6.63)

Prometheus Exporter와 Grafana를 통한 시각화

조력자님의 책에도 적혀있듯, 시간은 가장 소중한 자산입니다. 이런 비가역적인 자산을 아끼기 위해 우리는 터미널로 모든 결과를 바라보고있을 수는 없죠. 그렇다면 이를 자동으로 export 해주는 방안을 살펴봅시다. 바로 지난주에 배웠던 Prometheus Exporter를 이용하는 방안입니다.

Prometheus exporter 설치

# 1. kube-prometheus-stack을 설치합시다.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
kubectl create namespace prometheus
helm install -n prometheus kube-prometheus-stack prometheus-community/kube-prometheus-stack --set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues=false,prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false

# 2. ARMO를 설치합시다.
helm repo add armo https://kubescape.github.io/helm-charts/

# 3. 헬름차트를 설치합시다.
helm upgrade \
  --install kubescape kubescape/kubescape-cloud-operator \
  -n kubescape \
  --create-namespace \
  --set clusterName=`kubectl config current-context` \
  --set kubescape.serviceMonitor.enabled=true \
  --set kubescape.submit=false \
  --set kubescape.enableHostScan=false \
  --set kubescape.downloadArtifacts=false

3번 과정의 값을 설명하겠습니다:

  • kubescape.serviceMonitor.enabled=true # Prometheus serviceMonitor 를 생성합니다.

Prometheus만 뎐동하고 다른값을 kubescape 클라우드 플랫폼(kubescape 사에서 운영하는 서비스입니다)과 연동하지 않으려면 아래 값을 사용합시다.

  • kubescape.submit=false # Do not submit scan results
  • kubescape.enableHostScan=false # Do not install the Host Scanner
  • kubescape.downloadArtifacts=false # Do not download artifacts every scan

Grafana Dashboard

정상적으로 설치되었다면, 그라파나 대시보드로 시각화를 해봅시다. 이 링크 를 통해 대시보드를 import 합시다.

(작업완료 스크린샷은 추후 추가예정입니다.)

파드/컨테이너 보안 컨텍스트

쿠버네티스 완벽 가이드 도서를 참조합니다.

컨테이너 보안 컨텍스트: SecurityContext

SecuriyContext에 대한 참고링크는 여기를 클릭하여 확인해주십시오.

각 컨테이너에 대한 보안설정을 수행하여, 침해사고 발생 시 권한을 최소한으로 축소합니다. 이를 통해 해당 사고에 대한 확대를 방지합니다. 그렇다면 어떤 종류의 보안 컨텍스트가 있는지 살펴봅시다.

종류개요
privileged특수 권한을 가진 컨테이너로 실행
capabilitiesCapabilities 의 추가와 삭제
allowPrivilegeEscalation컨테이너 실행 시 상위 프로세스보다 많은 권한을 부여할지 여부
readOnlyRootFilesystemroot 파일 시스템을 읽기 전용으로 할지 여부
runAsUser실행 사용자
runAsGroup실행 그룹
runAsNonRootroot 에서 실행을 거부
seLinuxOptionsSELinux 옵션

보안 컨텍스트를 확인

# 보안 컨텍스트 확인
(s3ich4n:N/A) [root@kops-ec2 ~]# 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"
    ]
  }
}

Linux Capabilities

이는 root의 힘을 여러 작은 조각으로 나눕니다. 각 조각은 per-thread 속성입니다. 그렇다면, 그 권한은 무진장 많을 것입니다. 마치 윈도우즈 운영체제의 시스템에 있는 수많은 권한처럼 말이지요...

권한을 최대한으로 나눕시다!

# Linux Capabilities 확인 : 현재 38개 (저도 동일하게 확인하였습니다!)
**capsh --print**
...
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**

# proc 에서 확인 : bit 별 Capabilities - [링크](https://github.com/torvalds/linux/blob/master/include/uapi/linux/capability.h)
**cat /proc/1/status | egrep 'CapPrm|CapEff'**
CapPrm:	0000003fffffffff
CapEff:	0000003fffffffff

# 마스터 노드 Linux Capabilities 확인 : 아래 굵은색은 파드 기본 Linux Capabilities(14개)
ssh -i ~/.ssh/id_rsa ubuntu@api.$KOPS_CLUSTER_NAME **capsh --print**
cap_chown                   파일이나 디렉토리의 소유자를 변경할 수 있는 권한
cap_dac_override            파일이나 디렉토리의 접근 권한을 무시하고 파일이나 디렉토리에 대한 접근을 수행할 수 있는 권한 (DAC의 약자는 Discretionary access control이다)
cap_dac_read_search         파일이나 디렉토리를 읽거나 검색할 수 있는 권한
cap_fowner                  파일이나 디렉토리의 소유자를 변경할 수 있는 권한
cap_fsetid                  일이나 디렉토리의 Set-User-ID (SUID) 또는 Set-Group-ID (SGID) 비트를 설정할 수 있는 권한
cap_kill                    다른 프로세스를 종료할 수 있는 권한
cap_setgid                  프로세스가 그룹 ID를 변경할 수 있는 권한
cap_setuid                  프로세스가 사용자 ID를 변경할 수 있는 권한
cap_setpcap                 프로세스가 자신의 프로세스 권한을 변경할 수 있는 권한
cap_linux_immutable         파일의 immutability(불변성) 속성을 변경할 수 있는 권한을 제공
cap_net_bind_service        프로그램이 특정 포트에 바인딩(bind)하여 소켓을 개방할 수 있는 권한
cap_net_broadcast           프로세스가 네트워크 브로드캐스트 메시지를 보낼 수 있는 권한
cap_net_admin               네트워크 인터페이스나 소켓 설정을 변경할 수 있는 권한
cap_net_raw                 네트워크 패킷을 송수신하거나 조작할 수 있는 권한
cap_ipc_lock                메모리 영역을 잠금(lock)하고 언락(unlock)할 수 있는 권한
cap_ipc_owner               IPC 리소스(Inter-Process Communication Resources)를 소유하고, 권한을 변경할 수 있는 권한
cap_sys_module              커널 모듈을 로드하거나 언로드할 수 있는 권한
cap_sys_rawio               입출력(I/O) 포트와 같은 하드웨어 리소스를 직접 접근할 수 있는 권한
cap_sys_chroot              프로세스가 chroot() 시스템 콜을 호출하여 프로세스의 루트 디렉토리를 변경할 수 있는 권한
cap_sys_ptrace              다른 프로세스를 추적(trace)하거나 디버깅할 수 있는 권한
cap_sys_pacct               프로세스 회계(process accounting)를 위한 파일에 접근할 수 있는 권한
cap_sys_admin               시스템 관리자 권한을 제공하는 권한
cap_sys_boot                시스템 부팅과 관련된 작업을 수행할 수 있는 권한
cap_sys_nice                프로세스의 우선순위를 변경할 수 있는 권한
cap_sys_resource            자원 제한(resource limit)과 관련된 작업을 수행할 수 있는 권한
cap_sys_time                시스템 시간을 변경하거나, 시간 관련 시스템 콜을 사용할 수 있는 권한
cap_sys_tty_config          터미널 설정을 변경할 수 있는 권한
cap_mknod                   mknod() 시스템 콜을 사용하여 파일 시스템에 특수 파일을 생성할 수 있는 권한
cap_lease                   파일의 잠금과 관련된 작업을 수행할 수 있는 권한
cap_audit_write             시스템 감사(audit) 로그에 대한 쓰기 권한
cap_audit_control           시스템 감사(audit) 설정과 관련된 작업을 수행할 수 있는 권한
cap_setfcap                 파일 시스템 캡러빌리티(file system capability)을 설정할 수 있는 권한
cap_mac_override            SELinux 또는 AppArmor과 같은 MAC(Mandatory Access Control) 시스템을 우회하고 자신의 프로세스가 접근 가능한 파일, 디바이스, 네트워크 등을 제한 없이 접근할 수 있는 권한
cap_mac_admin               SELinux 또는 AppArmor과 같은 MAC(Mandatory Access Control) 시스템을 관리하고 수정할 수 있는 권한
cap_syslog                  시스템 로그를 읽거나, 쓸 수 있는 권한
cap_wake_alarm              시스템의 RTC(Real-Time Clock)를 사용하여 장치를 깨우거나 슬립 모드를 해제할 수 있는 권한
cap_block_suspend           시스템의 전원 관리 기능 중 하나인 Suspend(절전 모드)를 방지하는 권한
cap_audit_read              시스템 감사(audit) 로그를 읽을 수 있는 권한

이를 통해 파드별로 특수한 권한을 주거나, 빼거나 하는 등의 제어가 가능합니다.

polaris

Polaris는 오픈소스 보안점검 도구입니다. YAML 파일을 읽고 최적의 예시(best-practices)를 토대로 점검한 후 리포트를 제공해주는 방식의 서비스를 제공합니다.

설치

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

(s3ich4n:N/A) [root@kops-ec2 ~]# helm install polaris fairwinds-stable/polaris --namespace polaris
NAME: polaris
LAST DEPLOYED: Mon Apr 10 04:21:18 2023
NAMESPACE: polaris
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **

Enjoy Polaris and smooth sailing!
To view the dashboard execute this command:

kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80

Then open http://localhost:8080 in your browser.
(s3ich4n:N/A) [root@kops-ec2 ~]# kubectl annotate service polaris-dashboard "external-dns.alpha.kubernetes.io/hostname=polaris.$KOPS_CLUSTER_NAME" -n polaris
service/polaris-dashboard annotated
(s3ich4n:N/A) [root@kops-ec2 ~]# echo -e "Polaris Web URL = http://polaris.$KOPS_CLUSTER_NAME"
Polaris Web URL = http://polaris.s3ich4n.me

취약점 점검

  • Vulnerabilities 뿐 아니라 Weaknesses를 함께 조치하는 것으로 보입니다. replica가 1개 일 때와 버전명시 미조치(latest 를 사용하여 supply-chain 등의 공격에 취약)등에 대한 상황을 살펴봅시다.

쿠버네티스 인증/인가 및 RBAC

(교재의 제 1장에 포함된 내용이 일부 있습니다.)

서비스가 커지고 서비스에 접근하는 사람이 많아진다면 저절로 권한을 분리해야합니다. 작을 수록 간단하게 "시작" 해서 점점더 세분화하고 복잡하게 하면 좋을 것입니다. 쿠버네티스 입문 영상 모음글에도 소개되었듯, API 서버를 통해 쿠버네티스에 대한 접근을 수행할 수 있지요. 그렇다면 이를 접근하고 사용하기 위한 과정은 어떨까요?

출처: https://learnk8s.io/kubernetes-custom-authentication

쿠버네티스 접근에 대해

쿠버네티스는 서비스 어카운트를 통해 접근할 수 있습니다.
인증 -> 인가 -> Admission Control을 거치고 비로소 권한검증을 마친 후 사용합니다.

인증?

Authentication, 즉 당신이 누구인지? 를 묻는 과정입니다.

  • X.509 Client Certs : kubeconfigCA crt(발급 기관 인증서) , Client crt(클라이언트 인증서) , Client key(클라이언트 개인키) 를 통해 인증이 가능합니다.
  • kubectl : 여러 클러스터(kubeconfig)를 관리 가능 - contexts 에 클러스터와 유저 및 인증서/ 를 참고하여 처리합니다.
  • Service Account : 기본 서비스 어카운트(default) - 시크릿(CA crt 와 token)을 활용합니다.

인가?

Authorization, 즉 당신이 무엇을 할 수 있는가? 를 묻는 과정입니다.

  • 인가 방식 : RBAC(Role, RoleBinding), ABAC, Webhook, Node Authorization이 있습니다.
  • RBAC : 말 그대로! 역할 기반의 권한 관리, 사용자와 역할을 별개로 선언 후 두가지를 조합(binding)해서 사용자에게 권한을 부여하여 kubectl 혹은 API로 관리 가능합니다.
    • Namespace/Cluster - Role/ClusterRole, RoleBinding/ClusterRoleBinding, Service Account
    • Role(롤) - (RoleBinding 롤 바인딩) - Service Account(서비스 어카운트) : 롤 바인딩은 롤과 서비스 어카운트를 연결합니다.
    • Role(네임스페이스내 자원의 권한) vs ClusterRole(클러스터 수준의 자원의 권한) 의 차이가 있습니다.

.kube/config 파일은?

  • 원격지 접속정보, 유저정보, 컨텍스트(인증/인가를 통해 클러스터에 접근한다) 를 포함합니다.
  • 이를 통해 사용자(쿠버네티스 클러스터에 접근하려는 사람)가 이를 토대로 클러스터에 접근할 수 있습니다.

권한분리

  • 네임스페이스, 서비스 어카운트를 지정하여 파드를 생성하고 관리할 수 있습니다. 파드 기동 시 서비스 어카운트 한 개가 할당되며, 서비스 어카운트 기반 인증/인가를 합니다. 미지정 시 기본 서비스 어카운트가 할당됩니다. (이 때, Bearer token으로 쿠버네티스에서는 JWT 토큰을 사용합니다)
  • 네임스페이스에 규칙(Role)을 생성한 후, 서비스 어카운트를 바인딩합니다.
  • 서비스 어카운트를 지정하여 파드별로 권한을 분리하여 운영합니다.

자원 삭제

실습을 완료 후 아래의 명령을 통해 kOps 클러스터와 AWS CloudFormation 스택을 삭제합시다.

kOps 클러스터 삭제 및 AWS CloudFormation 스택 삭제

실습 마무리를 수행합니다.

kops delete cluster --yes && aws cloudformation delete-stack --stack-name mykops

마무리

제 5장에서는 쿠버네티스 보안과 관련한 사항을 살펴보았습니다. 클러스터 운영 시 파드, 컨테이너 단위부터 클러스터에 이르기까지 살펴볼 수 있는 보안 점검도구인 kubesparypolaris 를 살펴보았습니다. 또한 규모있는 클러스터의 필수요소인 RBAC(Role-based Access Control) 방안에 대해 살펴보았습니다.

이번 장에서는 아래 내용을 반드시 기억하셨으면 좋겠습니다.

  1. 보안 약점(Weakness)와 취약점(Vulnerabilities)는 안전한 서비스 운영에 필수적인 요소입니다. 기본을 지키고있는지 항상 점검합시다.
    1. 점점 복잡해지는 환경을 사람이 일일이 관리할 수는 없습니다. 좋은 도구와 정책의 힘을 토대로 슬기롭게 해결해야 합니다.
    2. 자동화된 보안요소 수집과 시각화, 알람기능을 통해 보안사항도 관리할 수 있는 시스템을 구축합시다.
  2. 적절한 권한 분리를 통해 필요에 따른 서비스 운영을 위임할 수도 있고, 통제된 관리의 장점을 모두 가져갈 수 있습니다.

이것으로 제 5장 및 스터디 요약을 마칩니다. 긴 글 읽어주셔서 감사합니다.

[^1]: misconfiguration 이란 말을 뭐라 번역할까 하다가, 보안 설정을 나사 쪼이듯 제대로 하지 않고 헐겁게 해두었다 라고 표현하기로 했습니다.

profile
백엔드 프로그래머

0개의 댓글