관리 메뉴

근묵자흑

[쿠버네티스] 서비스와 시크릿의 보안 취약점 및 대응 전략 본문

k8s

[쿠버네티스] 서비스와 시크릿의 보안 취약점 및 대응 전략

Luuuuu 2025. 4. 13. 14:51

쿠버네티스(Kubernetes)는 현대 클라우드 네이티브 환경에서 컨테이너 오케스트레이션의 표준으로 자리잡았지만, 복잡한 아키텍처와 다양한 구성 요소로 인해 보안 취약점에 노출될 가능성이 있습니다. 특히 서비스(Service)시크릿(Secret) 리소스는 클러스터 운영에 필수적이지만, 잘못 구성되면 심각한 보안 위협을 초래할 수 있습니다.

 

1. 서비스(Service) 관련 취약점

1.1 서비스 계정 토큰 탈취

공격 방법:

  • 공격자가 Pod 내부에 접근하면 자동으로 마운트되는 /var/run/secrets/kubernetes.io/serviceaccount/token 파일에서 서비스 계정 토큰을 추출할 수 있습니다.
  • 이 토큰은 기본적으로 만료 시간이 없어, 탈취 시 장기간 악용될 수 있습니다.
  • 추출된 토큰으로 Kubernetes API 서버에 인증하여 권한 상승 공격을 수행할 수 있습니다.

실제 공격 예시:

# 공격자가 침투한 Pod 내부에서 서비스 계정 토큰 추출
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

# API 서버에 인증하여 클러스터 리소스에 접근
curl -k -H "Authorization: Bearer $TOKEN" \
  https://kubernetes.default/api/v1/namespaces/default/pods

# 토큰의 권한 확인
curl -k -H "Authorization: Bearer $TOKEN" \
  https://kubernetes.default/api/v1/namespaces/default/secrets

대응 방법:

  1. 서비스 계정 토큰 자동 마운트 비활성화:
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  automountServiceAccountToken: false
  containers:
  - name: app
    image: app:1.0
  1. Kubernetes 1.21+ 버전에서는 BoundServiceAccountTokenVolume 기능을 활성화하여 토큰에 만료 시간 설정:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: short-lived-token-account
  annotations:
    kubernetes.io/enforce-mountable-secrets: "true"
  1. 서비스 계정의 RBAC 권한 최소화:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
  # secrets, configmaps 등 민감한 리소스에 대한 접근 제한

1.2 MITM(Man-in-the-Middle) 공격 (CVE-2020-8554)

취약점:

  • 공격자가 ClusterIP 서비스를 생성하고 spec.externalIP를 조작하여 트래픽을 가로챌 수 있습니다.
  • 이 취약점은 낮은 권한으로도 클러스터 내 통신을 감청할 수 있게 합니다.
  • 주요 위험은 Pod 간 통신이 공격자의 제어 하에 있는 노드로 리다이렉션되어 민감한 데이터가 유출될 수 있다는 점입니다.

취약한 서비스 예시:

apiVersion: v1
kind: Service
metadata:
  name: vulnerable-service
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: my-app
  # 공격자가 자신의 제어 하에 있는 IP로 설정
  externalIPs:
  - 192.168.1.100

대응 방안:

  1. externalIP 필드 사용을 제한하는 Admission Webhook 구현:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: restrict-external-ips
webhooks:
- name: restrict-external-ips.example.com
  rules:
  - apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["services"]
    operations: ["CREATE", "UPDATE"]
  clientConfig:
    service:
      namespace: webhook-system
      name: validation-webhook
      path: "/validate-external-ip"
  admissionReviewVersions: ["v1"]
  sideEffects: None
  timeoutSeconds: 5
  1. OPA(Open Policy Agent) Gatekeeper 정책 설정:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoExternalIPs
metadata:
  name: no-external-ips
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Service"]
  1. 네트워크 정책을 사용하여 Pod 간 통신 제한:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-pod-communications
spec:
  podSelector:
    matchLabels:
      app: secure-app
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: frontend
  egress:
  - to:
    - podSelector:
        matchLabels:
          role: database

1.3 노출된 API 서버 공격

공격 방법:

  • 인증 없이 Kubernetes API 서버가 외부에 노출된 경우, 공격자가 직접 API를 조작할 수 있습니다.
  • 클라우드 환경에서 IAM 권한이 과도하게 설정된 경우, 클라우드 자격 증명을 통해 API 서버에 접근할 수 있습니다.
  • 노출된 API 서버를 통해 공격자는 서비스를 생성, 수정, 삭제하고 클러스터를 완전히 장악할 수 있습니다.

취약점 확인 방법:

# 외부에서 API 서버 접근 가능 여부 확인
nmap -p 443,6443,8443 <cluster-ip>

# 인증 없이 API 접근 시도
curl -k https://<cluster-ip>:6443/api/v1/namespaces

대응 방안:

  1. API 서버에 대한 인증 강화:
# kube-apiserver 설정
--anonymous-auth=false
--enable-bootstrap-token-auth=false
--insecure-port=0  # 비보안 포트 비활성화
--secure-port=6443
--tls-cert-file=/etc/kubernetes/pki/apiserver.crt
--tls-private-key-file=/etc/kubernetes/pki/apiserver.key
  1. API 서버 앞에 프록시/방화벽 설정:
# API 서버 접근 제한을 위한 방화벽 규칙
iptables -A INPUT -p tcp -s 10.0.0.0/24 --dport 6443 -j ACCEPT
iptables -A INPUT -p tcp --dport 6443 -j DROP
  1. 클라우드 IAM 권한 최소화 및 정기적인 감사:
// AWS IAM 정책 예시 - 최소 권한
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "eks:DescribeCluster",
        "eks:ListClusters"
      ],
      "Resource": "*"
    }
  ]
}

1.4 NodePort 및 LoadBalancer 취약점

취약점:

  • NodePort와 LoadBalancer 타입의 서비스는 클러스터 외부에 직접 노출되므로 보안 위험이 있습니다.
  • 특히 NodePort 서비스는 모든 노드의 IP 주소를 통해 접근 가능하여 공격 표면이 넓어집니다.
  • 인증 및 권한 부여가 제대로 구현되지 않은 경우, 무단 접근 위험이 있습니다.

취약한 서비스 구성:

apiVersion: v1
kind: Service
metadata:
  name: vulnerable-nodeport
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30007  # 모든 노드의 30007 포트로 접근 가능
  selector:
    app: web

대응 방안:

  1. 인그레스 컨트롤러를 사용하여 NodePort 대신 중앙화된 접근 제어:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: secure-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
spec:
  tls:
  - hosts:
    - secure-app.example.com
    secretName: tls-secret
  rules:
  - host: secure-app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80
  1. LoadBalancer 서비스에 소스 IP 제한 설정:
apiVersion: v1
kind: Service
metadata:
  name: restricted-lb
  annotations:
    # AWS Load Balancer의 경우
    service.beta.kubernetes.io/aws-load-balancer-source-ranges: "203.0.113.0/24,198.51.100.0/24"
spec:
  type: LoadBalancer
  ports:
  - port: 443
    targetPort: 8443
  selector:
    app: secure-app
  1. 서비스 메시(예: Istio)를 사용하여 mTLS(상호 TLS) 구현:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

1.5 DNS 스푸핑 공격

취약점:

  • 쿠버네티스는 클러스터 내 서비스 검색을 위해 DNS를 사용합니다.
  • 공격자가 DNS 서비스(CoreDNS)에 접근하거나 조작할 경우, 서비스 간 통신을 가로채 스푸핑 공격을 수행할 수 있습니다.
  • DNS 응답 위조를 통해 애플리케이션을 악성 서비스로 리다이렉션할 수 있습니다.

공격 시나리오:

  1. 공격자가 취약한 Pod에 접근하여 권한 상승
  2. CoreDNS ConfigMap 또는 DNS Pod에 대한 접근 권한 획득
  3. DNS 응답을 조작하여 database-service에 대한 요청을 공격자가 제어하는 서비스로 리다이렉션

대응 방안:

  1. CoreDNS RBAC 권한 강화:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: kube-system
  name: coredns-config-restricted
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["coredns"]
  verbs: ["get"]
  1. DNS 보안 정책 적용:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-access
  namespace: kube-system
spec:
  podSelector:
    matchLabels:
      k8s-app: kube-dns
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53
  egress:
  - {}  # 필요한 경출 규칙 추가
  1. DNS 캐싱 및 검증 구현:
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        cache 30
        loop
        reload
        loadbalance
        forward . /etc/resolv.conf
        pprof
        prometheus :9153
        }
  1. mTLS를 통한 서비스 간 통신 암호화:
# Istio 서비스 메시 구성
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "default"
  namespace: "istio-system"
spec:
  mtls:
    mode: STRICT

2. 시크릿(Secret) 관련 취약점

2.1 기본 암호화 미적용

문제점:

  • 쿠버네티스 시크릿은 기본적으로 Base64 인코딩만 적용되며, etcd에 평문으로 저장됩니다.
  • etcd 데이터베이스에 접근할 수 있는 공격자는 모든 시크릿을 쉽게 복호화할 수 있습니다.
  • 기본 구성의 쿠버네티스에서는 시크릿이 노드의 tmpfs에 평문으로 저장됩니다.

취약한 시크릿 예시:

apiVersion: v1
kind: Secret
metadata:
  name: unencrypted-secret
type: Opaque
data:
  username: YWRtaW4=  # "admin" (Base64 인코딩)
  password: UEAkJHcwcmQ=  # "P@$$w0rd" (Base64 인코딩)

복호화 공격 예시:

# 시크릿 데이터 추출
kubectl get secret unencrypted-secret -o jsonpath='{.data.password}' | base64 -d
# 결과: P@$$w0rd

# etcd에서 직접 데이터 추출 (etcd 접근 권한이 있는 경우)
ETCDCTL_API=3 etcdctl --cacert /etc/kubernetes/pki/etcd/ca.crt \
  --cert /etc/kubernetes/pki/etcd/server.crt \
  --key /etc/kubernetes/pki/etcd/server.key \
  get /registry/secrets/default/unencrypted-secret

대응 방안:

  1. etcd 암호화 설정:
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <32바이트 base64 인코딩 키>
      - identity: {}
  1. API 서버 설정:
# kube-apiserver 설정에 암호화 구성 파일 추가
--encryption-provider-config=/etc/kubernetes/encryption-config.yaml
  1. 기존 시크릿 재암호화:
# 모든 시크릿을 재암호화
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
  1. 정기적인 암호화 키 순환:
# 새 키로 암호화 구성 업데이트
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key2  # 새 키
              secret: <새 32바이트 base64 인코딩 키>
            - name: key1  # 이전 키 (해독용)
              secret: <이전 32바이트 base64 인코딩 키>
      - identity: {}

2.2 RBAC 미스컨피그

공격 방법:

  • 시크릿에 대한 과도한 읽기 권한이 부여된 경우, 공격자는 권한 있는 서비스 계정을 통해 모든 시크릿에 접근할 수 있습니다.
  • kubectl get secrets -A -o yaml 명령어로 모든 네임스페이스의 시크릿 데이터를 추출할 수 있습니다.
  • 특히 클러스터 관리자 권한이나 시크릿 조회 권한을 가진 서비스 계정이 노출될 경우 위험합니다.

취약한 RBAC 구성:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: global-secret-reader
subjects:
- kind: ServiceAccount
  name: app-sa
  namespace: default
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

대응 방안:

  1. 최소 권한 원칙 적용:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role  # ClusterRole 대신 네임스페이스 범위 Role 사용
metadata:
  namespace: app-namespace
  name: limited-secret-access
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get"]
  resourceNames: ["app-specific-secret"]  # 특정 시크릿으로 제한
  1. 네임스페이스별 시크릿 분리:
# 각 애플리케이션/팀별 네임스페이스 생성
apiVersion: v1
kind: Namespace
metadata:
  name: team-a
---
# 네임스페이스 내 시크릿 생성
apiVersion: v1
kind: Secret
metadata:
  name: team-a-db-credentials
  namespace: team-a
type: Opaque
data:
  username: dGVhbS1hLXVzZXI=
  password: c2VjdXJlLXBhc3N3b3Jk
  1. RBAC 감사 및 모니터링:
# RBAC 권한 감사 스크립트
#!/bin/bash
echo "서비스 계정별 시크릿 접근 권한 조회:"
kubectl get serviceaccounts --all-namespaces -o json | jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name)"' | while read sa; do
  ns=$(echo $sa | cut -d/ -f1)
  name=$(echo $sa | cut -d/ -f2)
  echo "SA: $sa"
  kubectl auth can-i --as=system:serviceaccount:$ns:$name get secrets --all-namespaces
  kubectl auth can-i --as=system:serviceaccount:$ns:$name list secrets --all-namespaces
  echo ""
done

2.3 장기간 유효한 토큰

위험성:

  • 쿠버네티스 1.21 이전 버전에서는 서비스 계정 토큰이 만료 없이 영구적으로 유효했습니다.
  • 토큰이 유출될 경우, 공격자는 제한 없이 오랜 시간 동안 API 접근이 가능합니다.
  • 토큰 순환 메커니즘이 없으면 침해된 토큰을 무효화하기 어렵습니다.

문제 예시:

# 전통적인 서비스 계정 방식
apiVersion: v1
kind: ServiceAccount
metadata:
  name: long-lived-token-sa
---
apiVersion: v1
kind: Secret
metadata:
  name: long-lived-token-sa-token-abcde
  annotations:
    kubernetes.io/service-account.name: long-lived-token-sa
type: kubernetes.io/service-account-token

대응 방안:

  1. 토큰 만료 시간 설정 (Kubernetes 1.21+):
apiVersion: v1
kind: ServiceAccount
metadata:
  name: short-lived-token-sa
---
# TokenRequest API를 사용하여 제한된 수명의 토큰 발급
apiVersion: v1
kind: Secret
metadata:
  name: short-lived-token
  annotations:
    kubernetes.io/service-account.name: short-lived-token-sa
    # 12시간 후 만료
    kubernetes.io/service-account.expiration: "43200"
type: kubernetes.io/service-account-token
  1. 프로젝션된 서비스 계정 토큰 사용:
apiVersion: v1
kind: Pod
metadata:
  name: short-lived-token-pod
spec:
  containers:
  - name: app
    image: app:1.0
    volumeMounts:
    - name: token-volume
      mountPath: /var/run/secrets/tokens
  volumes:
  - name: token-volume
    projected:
      sources:
      - serviceAccountToken:
          path: token
          expirationSeconds: 3600  # 1시간 만료
          audience: api-audience
  1. 정기적인 토큰 순환 구현:
#!/bin/bash
# 정기적인 서비스 계정 토큰 순환 스크립트

# 서비스 계정 토큰 시크릿 삭제 (자동으로 재생성됨)
kubectl delete secret $(kubectl get secrets | grep service-account-token | awk '{print $1}')

# TokenRequest API를 통한 토큰 순환
NEW_TOKEN=$(kubectl create token service-account-name --duration=24h)
# 새 토큰을 애플리케이션에 전달하는 로직 구현

2.4 시크릿 버전 관리 문제

문제점:

  • 쿠버네티스는 기본적으로 시크릿 버전 관리 기능을 제공하지 않습니다.
  • 시크릿 업데이트 시 이전 버전으로 롤백하기 어렵습니다.
  • 시크릿 변경이 애플리케이션에 즉시 영향을 주어 서비스 중단을 초래할 수 있습니다.

시크릿 업데이트 예시:

# 시크릿 업데이트
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=newpassword123 \
  -o yaml --dry-run=client | kubectl apply -f -

# 이 업데이트는 이전 버전을 덮어씁니다

대응 방안:

  1. 버전이 지정된 시크릿 이름 사용:
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials-v2  # 버전 정보를 이름에 포함
type: Opaque
data:
  username: YWRtaW4=
  password: bmV3cGFzc3dvcmQxMjM=
  1. 시크릿에 레이블과 주석 추가:
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  labels:
    version: "2"
  annotations:
    description: "데이터베이스 인증 정보 v2"
    updated-at: "2023-04-15T10:00:00Z"
type: Opaque
data:
  username: YWRtaW4=
  password: bmV3cGFzc3dvcmQxMjM=
  1. GitOps 접근 방식으로 시크릿 관리:
# Sealed Secrets를 사용한 버전 관리 예시
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
  namespace: default
  annotations:
    sealedsecrets.bitnami.com/version: "1.2.3"
spec:
  encryptedData:
    username: AgBy8hCL8...
    password: AgAskjl2H...
  1. 외부 시크릿 관리 도구 사용 (HashiCorp Vault):
# Vault와 통합하는 예시
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: db-credentials
spec:
  vaultAuthRef: my-auth
  mount: secret
  path: db-credentials
  version: 2  # 시크릿 버전 지정
  destination:
    name: db-credentials
    create: true

2.5 CI/CD 파이프라인에서의 시크릿 노출

취약점:

  • CI/CD 파이프라인에서 시크릿이 평문으로 저장되거나 로그에 노출될 수 있습니다.
  • Git 저장소에 시크릿이 실수로 커밋될 위험이 있습니다.
  • 배포 과정에서 시크릿 배포 중 보안 문제가 발생할 수 있습니다.

위험한 CI/CD 구성 예시:

# 위험한 Jenkins 파이프라인 예시
pipeline {
  agent any
  environment {
    DB_PASSWORD = 'super-secret-password'  # 하드코딩된 암호
  }
  stages {
    stage('Deploy') {
      steps {
        sh 'kubectl create secret generic db-credentials \
             --from-literal=username=admin \
             --from-literal=password=$DB_PASSWORD'
        // 로그에 암호가 노출될 수 있음
      }
    }
  }
}

대응 방안:

  1. CI/CD 시스템의 보안 시크릿 저장소 사용:
# GitLab CI/CD 보안 변수 사용 예시
deploy:
  stage: deploy
  script:
    - kubectl create secret generic db-credentials
        --from-literal=username=$DB_USERNAME
        --from-literal=password=$DB_PASSWORD
  variables:
    # 보호된 변수로 설정
    DB_USERNAME: $CI_DB_USERNAME
    DB_PASSWORD: $CI_DB_PASSWORD
  1. Sealed Secrets 또는 SOPS 사용:
# SOPS를 사용한 암호화된 시크릿 생성
cat <<EOF > secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  username: $(echo -n "admin" | base64)
  password: $(echo -n "secure-password" | base64)
EOF

# 시크릿 암호화
sops --encrypt --pgp <GPG_KEY_ID> secret.yaml > secret.enc.yaml

# CI/CD에서 복호화 및 적용
sops --decrypt secret.enc.yaml | kubectl apply -f -
  1. 외부 시크릿 관리 서비스와 통합:
# AWS Secrets Manager 통합 예시
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: aws-secrets
spec:
  provider: aws
  parameters:
    objects: |
      - objectName: "db-credentials"
        objectType: "secretsmanager"
        objectVersionLabel: "AWSCURRENT"
  secretObjects:
  - secretName: db-credentials
    type: Opaque
    data:
    - objectName: db-credentials
      key: username
    - objectName: db-credentials
      key: password

3. 방어 전략

3.1 서비스 보안 강화

1. API 서버 접근 제한:

# kube-apiserver 설정
apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    command:
    - kube-apiserver
    - --anonymous-auth=false  # 익명 인증 비활성화
    - --enable-admission-plugins=NodeRestriction,PodSecurityPolicy
    - --audit-log-path=/var/log/kubernetes/audit.log
    - --audit-log-maxage=30
    - --audit-log-maxbackup=10
    - --audit-policy-file=/etc/kubernetes/audit-policy.yaml
    - --authorization-mode=Node,RBAC
    - --tls-min-version=VersionTLS12

2. NetworkPolicy 적용:

# 기본 거부 정책
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

# 세부 허용 정책
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-specific-traffic
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 80
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: db
    ports:
    - protocol: TCP
      port: 3306

3. 서비스 메시 구현:

# Istio 서비스 메시 설정
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

# 서비스 간 통신 정책
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: service-authz
  namespace: default
spec:
  selector:
    matchLabels:
      app: backend
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/frontend-sa"]
    to:
    - operation:
        methods: ["GET"]
        paths: ["/api/v1/*"]

4. 클러스터 수준 보안 정책:

# Pod Security Standards (PSS) 적용
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
  configuration:
    apiVersion: pod-security.admission.config.k8s.io/v1
    kind: PodSecurityConfiguration
    defaults:
      enforce: "restricted"
      enforce-version: "latest"
      audit: "restricted"
      audit-version: "latest"
      warn: "restricted"
      warn-version: "latest"
    exemptions:
      usernames: []
      runtimeClasses: []
      namespaces: ["kube-system"]

3.2 시크릿 관리 개선

1. etcd 암호화 활성화:

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <32바이트 base64 인코딩 키>
      - identity: {}

2. 외부 시크릿 관리 도구 사용:

AWS Secrets Manager 통합:

# AWS Secrets Manager CSI Driver 설정
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: aws-secrets
spec:
  provider: aws
  secretObjects:
  - secretName: db-credentials
    type: Opaque
    data:
    - objectName: db-username
      key: username
    - objectName: db-password
      key: password
  parameters:
    objects: |
      - objectName: "prod/db"
        objectType: "secretsmanager"

3. Sealed Secrets 사용:

# 암호화된 시크릿 정의
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: mysecret
  namespace: default
spec:
  encryptedData:
    password: AgBUqHkgMYP...
    username: AgCym7j...

4. 시크릿 순환 자동화:

# Kubernetes CronJob을 사용한 시크릿 순환
apiVersion: batch/v1
kind: CronJob
metadata:
  name: secret-rotation
spec:
  schedule: "0 0 * * 0"  # 매주 일요일 00:00
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: secret-rotator
          containers:
          - name: rotator
            image: secret-rotator:v1
            command: ["./rotate-secrets.sh"]
          restartPolicy: OnFailure

3.3 모니터링 및 감사 활성화

1. API 서버 감사 로그 설정:

# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# 시크릿 관련 작업 기록
- level: Metadata
  resources:
  - group: ""
    resources: ["secrets"]
  verbs: ["get", "list", "create", "update", "patch", "delete"]

# 권한 변경 작업 기록
- level: RequestResponse
  resources:
  - group: "rbac.authorization.k8s.io"
    resources: ["roles", "clusterroles", "rolebindings", "clusterrolebindings"]
  verbs: ["create", "update", "patch", "delete"]

# 서비스 변경 작업 기록
- level: Request
  resources:
  - group: ""
    resources: ["services"]
  verbs: ["create", "update", "patch", "delete"]

# 그 외 작업은 메타데이터만 기록
- level: Metadata

2. Falco 보안 모니터링 구성:

# Falco 룰 설정
apiVersion: v1
kind: ConfigMap
metadata:
  name: falco-rules
  namespace: security
data:
  falco_rules.yaml: |-
    - rule: Secret Access
      desc: Detect accessing Kubernetes secret objects
      condition: >
        k8s.resource.type="secrets" and
        ka.verb in (create, get, list, update, delete)
      output: >
        K8s secret accessed (user=%ka.user.name ns=%ka.target.namespace
        secret=%ka.target.name verb=%ka.verb)
      priority: WARNING

    - rule: Pod With Sensitive Mount
      desc: Detect a pod mounting a secret or configmap as volume
      condition: >
        k8s.pod.create and
        (k8s.pod.volumes.secret.name != "" or
         k8s.pod.volumes.configmap.name != "")
      output: >
        Pod created with sensitive mount (user=%ka.user.name pod=%ka.resp.name
        ns=%ka.target.namespace vols=%jevt.value[/spec/volumes])
      priority: INFO

3. 침입 탐지 및 대응 시스템 구축:

# Falco 알림 설정
apiVersion: v1
kind: ConfigMap
metadata:
  name: falco-config
  namespace: security
data:
  falco.yaml: |-
    program_output:
      enabled: true
      keep_alive: false
      program: "curl -s -H 'Content-Type: application/json' -d @- http://alert-service:8080/falco"

3.4 네트워크 정책 구현

1. 기본 거부 정책 설정:

# 모든 네임스페이스에 기본 거부 정책 적용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: default  # 각 네임스페이스에 적용
spec:
  podSelector: {}  # 모든 Pod 선택
  policyTypes:
  - Ingress
  - Egress

2. 서비스별 통신 허용 정책:

# 프론트엔드 -> 백엔드 -> 데이터베이스 통신 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
  - Egress
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432

3. 네임스페이스 간 통신 정책:

# 서로 다른 네임스페이스 간 특정 통신만 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: cross-namespace-policy
  namespace: app
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          purpose: monitoring
      podSelector:
        matchLabels:
          app: prometheus
    ports:
    - protocol: TCP
      port: 9090

4. DNS와 외부 통신 허용 정책:

# DNS 및 특정 외부 통신 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-and-external
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
  - Egress
  egress:
  # DNS 허용
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53
  # 특정 외부 API 접근 허용
  - to:
    - ipBlock:
        cidr: 203.0.113.0/24
    ports:
    - protocol: TCP
      port: 443

4. 실전 보안 시나리오

4.1 제로 트러스트 아키텍처 구현

1. 제로 트러스트 원칙:

  • 내부 네트워크의 모든 통신도 기본적으로 신뢰하지 않습니다.
  • 최소 권한 원칙을 모든 구성 요소에 적용합니다.
  • 명시적으로 허용된 통신만 허용합니다.

2. mTLS 구현:

# Istio 서비스 메시를 통한 mTLS 적용
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

3. 애플리케이션 수준 인증 및 권한 부여:

# Istio RequestAuthentication 및 AuthorizationPolicy
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: jwt-auth
  namespace: default
spec:
  selector:
    matchLabels:
      app: backend
  jwtRules:
  - issuer: "issuer.example.com"
    jwksUri: "https://issuer.example.com/.well-known/jwks.json"
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: backend-policy
  namespace: default
spec:
  selector:
    matchLabels:
      app: backend
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/frontend-sa"]
    to:
    - operation:
        methods: ["GET"]
        paths: ["/api/public/*"]
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/admin-sa"]
    to:
    - operation:
        methods: ["*"]
        paths: ["/api/*"]

4. 네트워크 세그먼테이션:

# 네임스페이스 분리 및 네트워크 정책
apiVersion: v1
kind: Namespace
metadata:
  name: frontend
---
apiVersion: v1
kind: Namespace
metadata:
  name: backend
---
apiVersion: v1
kind: Namespace
metadata:
  name: database
---
# 네임스페이스 간 네트워크 정책
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-to-backend
  namespace: backend
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: frontend

참고 자료

  1. Kubernetes 서비스 계정 토큰 보안
  2. CVE-2020-8554 MITM 공격 분석
  3. 시크릿 관리 모범 사례
  4. Kubernetes RBAC 보안 가이드
  5. 쿠버네티스 네트워크 정책 모범 사례
  6. 쿠버네티스 etcd 암호화 설정
  7. HashiCorp Vault로 Kubernetes 시크릿 관리
  8. Falco 보안 모니터링 도구
  9. Kubernetes Pod Security Standards
  10. Istio 서비스 메시 보안