13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] 클러스터 보안

  • [보안] Kubernetes 보안 체크리스트: 컨테이너 이미지 스캔 자동화

    [보안] Kubernetes 보안 체크리스트: 컨테이너 이미지 스캔 자동화

    본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다.

    Kubernetes 보안 체크리스트: 컨테이너 이미지 스캔 자동화

    Kubernetes 보안 체크리스트를 운영하다 보면 병목이 꽤 자주 이미지 스캔에서 드러납니다. 배포 파이프라인은 분 단위로 빨라졌는데 보안 판정이 여전히 사람 손에 걸려 있으면, 속도와 통제가 같이 무너지더라고요. 저도 홈랩이든 사내 테스트 클러스터든 처음에는 릴리스 직전 일회성 스캔으로 시작했는데, 운영으로 넘어가면 금방 한계가 보였습니다. 이미지 변경 속도, 베이스 이미지 교체 주기, 레지스트리 정책, 클러스터 반입 기준이 따로 놀면 자동화는 이름만 자동화가 되거든요.

    실무에서는 스캐너를 한 번 붙이는 것으로 끝나지 않습니다. 빌드 시점, 레지스트리 저장 시점, 클러스터 반입 시점, 운영 중 재평가 시점을 나눠서 봐야 합니다. 이유는 단순합니다. 배포 당시엔 통과한 이미지라도, 며칠 뒤 취약점 데이터베이스가 갱신되면 같은 다이제스트가 다른 판정을 받을 수 있습니다. 이 차이를 놓치면 팀은 바로 “어제는 괜찮았는데 왜 오늘 막히지?”라는 혼란에 빠집니다.

    CI, 레지스트리, Kubernetes 클러스터, 보고 체계를 한눈에 보여주는 전체 구성도입니다.

    Kubernetes 보안 체크리스트에서 이미지 스캔이 중요한 이유

    현장에서 필요한 건 CVE 숫자 나열이 아닙니다. 무엇을 기준으로 배포를 막을지, 무엇은 추적만 하고 무엇은 즉시 조치할지, 그 판정을 누가 재현할 수 있는지가 핵심입니다. 실제 스캔 결과를 보면 문제는 대체로 세 부류로 갈립니다. 베이스 이미지 교체로 끝나는 항목, 애플리케이션 의존성 업데이트가 필요한 항목, 이미지가 아니라 배포 매니페스트나 실행 권한 설정을 손봐야 하는 항목입니다. 같은 High라도 대응 경로가 완전히 다릅니다.

    그래서 체크리스트를 만들 때 제가 먼저 적는 건 도구 이름이 아니라 다음 네 가지입니다. 첫째, 어떤 시점에 검사할지. 둘째, 어떤 대상을 검사할지. 셋째, 어떤 심각도와 조건에서 실패로 볼지. 넷째, 예외를 누구 승인으로 얼마나 오래 둘지. 이 네 줄이 빠지면 결과는 늘 많고, 책임은 늘 흐려집니다.

    Kubernetes 보안 체크리스트로 정리한 컨테이너 이미지 스캔 자동화 체크리스트

    1. 이미지 태그보다 다이제스트를 기준으로 기록: repo:tag만 저장하지 말고 repo@sha256:...를 남겨야 스캔 결과와 배포 대상을 정확히 연결할 수 있습니다.
    2. 빌드 직후 스캔: CI에서 이미지 생성 직후 취약점과 시크릿 혼입 여부를 확인합니다.
    3. 실패 기준을 코드로 고정: CRITICAL 즉시 차단, HIGH는 팀 정책에 따라 차단 또는 예외 관리 등으로 명시합니다.
    4. 데이터베이스 갱신 시점 기록: 로컬, CI, 클러스터의 스캔 결과가 다른 흔한 원인입니다.
    5. 레지스트리 인증 분리: 빌드용 자격 증명과 스캔용 읽기 전용 자격 증명을 분리해 두면 사고 범위를 줄이기 좋습니다.
    6. 클러스터 내 재스캔: 이미 떠 있는 워크로드를 주기적으로 다시 평가해야 운영 중 신규 공개 취약점을 따라잡을 수 있습니다.
    7. 예외 처리 만료일 강제: 예외는 허가가 아니라 부채입니다. 사유, 승인자, 만료일이 없으면 결국 영구 면제가 됩니다.
    8. 결과 저장 위치 통일: CI 로그만 믿지 말고 리포트 시스템이나 Kubernetes 리소스로 남겨 반복 조회 가능하게 해야 합니다.
    9. 로컬과 CI 판정 일치: 같은 옵션, 같은 대상, 가능하면 같은 DB 갱신 흐름을 써야 팀이 결과를 신뢰합니다.
    10. 최소 이미지 우선: Distroless나 slim 계열 검토는 보안 때문이기도 하지만, 스캔 노이즈와 패치 범위를 줄이는 데도 효과가 큽니다.
    11. Admission 단계 연계 여부 결정: 규정이 엄격하면 배포 허가 단계에서 차단하고, 초기 도입기라면 관찰 모드부터 들어가는 편이 덜 부서집니다.
    12. 성능 예산 확보: 대형 이미지 재스캔은 노드 자원과 레지스트리 호출을 먹습니다. 운영 클러스터에서 무작정 스캔 빈도를 높이면 다른 워크로드가 피해를 볼 수 있습니다.

    도구 선택 전에 먼저 정할 기준

    Trivy, Grype처럼 널리 쓰이는 스캐너는 출발점으로 충분히 좋습니다. 저는 빠르게 붙일 때는 Trivy를 자주 쓰는 편입니다. 로컬 점검, CI, Kubernetes 연계까지 흐름을 만들기 편해서 이거 진짜 실무에서 손이 덜 가더라고요. 다만 도구보다 먼저 정해야 할 것이 있습니다. 결과 해석 단위가 이미지인지, 레포지토리인지, 실제 클러스터 워크로드인지부터 정해야 합니다. 이 기준이 흔들리면 스캐너를 바꿔도 운영은 그대로 혼란스럽습니다.

    선택 포인트 A를 택할 때 B를 택할 때 제가 실제로 권하는 기준
    스캔 시점 CI만 스캔: 배포 속도와 단순성이 우선일 때 CI + 클러스터 재스캔: 운영 중 노출 추적이 중요할 때 운영 클러스터가 있으면 병행이 맞습니다. CI만으로는 신규 공개 취약점을 놓치기 쉽습니다.
    식별 방식 태그 기준: 초기에 단순하게 시작할 때 다이제스트 기준: 결과 재현성과 감사 추적이 필요할 때 실서비스는 다이제스트 기준으로 가는 편이 안전합니다. 태그는 너무 쉽게 바뀝니다.
    실패 정책 Critical만 차단: 도입 초기에 반발을 줄여야 할 때 Critical + High 차단: 규정과 배포 품질 기준이 이미 성숙했을 때 처음부터 전부 막기보다 Critical 차단, High는 만료일 있는 예외 관리가 현실적입니다.
    스캔 대상 OS 패키지 위주: 베이스 이미지 관리가 주 이슈일 때 OS + 애플리케이션 의존성 + 시크릿: 공급망 전체를 봐야 할 때 실무에서는 최소한 취약점과 시크릿은 같이 보는 편이 낫습니다.
    운영 방식 관찰 모드: 현재 상태 파악이 먼저일 때 차단 모드: 배포 기준을 강제해야 할 때 새로 도입하는 팀은 1~2주 정도 관찰 모드로 데이터부터 모으는 편이 덜 아픕니다.

    실전 구현 1: Kubernetes 보안 체크리스트 기준으로 로컬과 CI 맞추기

    가장 먼저 할 일은 개발자 로컬과 CI가 가능한 한 같은 판정을 내도록 맞추는 것입니다. 로컬에선 통과했는데 CI에서만 실패하면 팀은 금방 스캐너보다 파이프라인을 더 싫어하게 됩니다. 그래서 이 단계에서는 “옵션을 많이 주는 것”보다 “같은 옵션을 고정하는 것”이 더 중요합니다. 이 기준만 맞춰도 운영 피로도가 꽤 줄어듭니다.

    1. 이미지 빌드 후 Trivy로 즉시 검사

    아래 예시는 로컬에서 이미지 빌드 직후 취약점과 시크릿을 같이 보는 가장 단순한 흐름입니다. 팀 공통 기준을 잡을 때는 이런 명령부터 통일하는 게 훨씬 편합니다.

    docker build -t myapp:scan-test .
    trivy image \
      --severity HIGH,CRITICAL \
      --ignore-unfixed \
      --scanners vuln,secret \
      --format table \
      myapp:scan-test

    여기서 중요한 건 옵션 의미보다 운영상의 함정입니다. --ignore-unfixed는 노이즈를 줄이는 데 유용하지만, “아직 수정본이 없으니 위험이 없다”는 뜻은 아닙니다. 패치가 없어도 우회책이나 완화 조치가 필요한 경우가 있습니다. 그래서 저는 이 옵션을 쓰더라도 예외 문서에는 “미조치”보다 “공급사 수정 대기”처럼 상태를 분리해 적는 편입니다.

    --scanners vuln,secret 조합도 실무 효율이 좋습니다. 이미지 레이어에 테스트 토큰, 오래된 인증서, 샘플 키가 남아 있는 경우가 의외로 자주 나옵니다. 이건 CVE보다 발견 즉시 우선순위를 높게 잡아야 하는 유형입니다. 공격 난도가 낮고 노출 즉시 영향이 커질 수 있어서 그렇습니다.

    2. CI에서 실패 기준을 강제하고 결과를 아티팩트로 남기기

    CI에서는 사람 눈으로만 보는 표 형식과, 후처리용 JSON 결과를 같이 남겨 두는 편이 좋습니다. 나중에 추세 비교나 예외 검토할 때 이 차이가 꽤 크게 느껴집니다.

    IMAGE_REF="registry.example.com/team/myapp:${GIT_COMMIT}"
    
    trivy image \
      --severity CRITICAL,HIGH \
      --exit-code 1 \
      --ignore-unfixed \
      --scanners vuln,secret \
      --format json \
      --output trivy-report.json \
      "$IMAGE_REF"
    
    trivy image \
      --severity CRITICAL,HIGH \
      --ignore-unfixed \
      --scanners vuln,secret \
      --format table \
      "$IMAGE_REF"

    --exit-code 1이 없으면 자동화는 반쪽입니다. 로그에 경고가 많아도 배포가 계속되면 운영팀은 곧 “보안 경고는 참고용”으로 받아들이게 됩니다. 그 순간부터 정책은 문서에만 남습니다. 그리고 JSON 결과를 별도 파일로 남기는 이유도 분명합니다. 사람이 읽기 쉬운 표와 기계가 다시 처리하기 쉬운 형식을 분리해야 나중에 재검증이 편해집니다.

    초기에 자주 보는 실패 모드는 “로컬은 어제 DB로 검사했고, CI는 오늘 DB로 검사했다”는 불일치입니다. 겉으로는 같은 이미지, 같은 명령처럼 보여도 결과가 달라집니다. 그래서 운영 문서에는 최소한 이미지 다이제스트, 스캔 시각, 스캐너 버전, DB 갱신 시각 네 가지를 같이 남겨 두는 게 좋습니다.

    컨테이너 이미지 스캔이 포함된 Kubernetes 보안 체크리스트 CI 흐름

    빌드, 스캔, 실패 기준 적용, 배포 차단으로 이어지는 파이프라인 흐름 예시입니다.

    실전 구현 2: Kubernetes 클러스터 안에서 재스캔 자동화

    CI 스캔은 배포 직전의 사진 한 장에 가깝습니다. 운영은 동영상에 더 가깝고요. 이미지 자체는 그대로인데 취약점 데이터베이스가 갱신되거나, 오래된 워크로드가 남아 있거나, 수동 롤백으로 이전 태그가 다시 떠 있을 수 있습니다. 그래서 운영 클러스터에서는 주기 재평가가 꼭 필요합니다.

    1. Helm으로 Trivy Operator 설치

    Trivy Operator는 Kubernetes 안에서 보안 리포트를 CRD 형태로 남겨 주기 때문에, 클러스터 관점 추적이 필요한 팀에는 꽤 잘 맞습니다. 별도 콘솔 없이도 kubectl로 상태를 확인할 수 있다는 점도 편합니다.

    helm repo add aqua https://aquasecurity.github.io/helm-charts/
    helm repo update
    helm upgrade --install trivy-operator aqua/trivy-operator \
      --namespace trivy-system \
      --create-namespace

    이 방식의 장점은 결과가 Kubernetes 리소스로 남는다는 점입니다. CI 로그는 흩어지기 쉽지만 리소스는 조회 경로가 고정됩니다. 운영팀과 플랫폼팀이 같은 화면을 본다는 점도 생각보다 큽니다. 인수인계할 때 특히 편하더라고요.

    2. 스캔 결과 조회

    설치 후에는 먼저 실제로 어떤 리포트가 생성되는지 확인하는 게 좋습니다. 환경과 활성화된 기능에 따라 보이는 리포트 종류가 조금씩 다를 수 있습니다.

    kubectl get vulnerabilityreports -A
    kubectl describe vulnerabilityreport -n default <report-name>

    결과를 읽을 때는 숫자보다 맥락을 먼저 봅니다. 저는 보통 세 가지를 바로 확인합니다. 첫째, 어떤 워크로드의 어떤 컨테이너인지. 둘째, 문제가 베이스 이미지 계층인지 애플리케이션 의존성인지. 셋째, 지금 당장 수정 가능한지입니다. 같은 High라도 베이스 이미지 교체로 끝나는 항목과 코드 호환성 검토가 필요한 라이브러리 문제는 대응 일정이 다릅니다.

    3. 실제 배포 중인 이미지 목록부터 확인

    보고서가 아니라 실제 실행 대상을 먼저 보는 습관이 중요합니다. 선언상 최신 이미지가 적혀 있어도, 운영 클러스터에는 예전 이미지가 남아 있는 경우가 꽤 있습니다.

    kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.image}{" "}{end}{"\n"}{end}'

    이 명령은 단순해 보여도 중요합니다. Git 저장소 기준으로는 최신 태그를 본다고 해도, 실제 클러스터에선 예전 태그나 수동 롤백된 이미지가 남아 있을 수 있습니다. 스캔 체계가 배포 선언만 보고 실제 실행 대상을 안 보면, 보고서는 깨끗한데 운영은 더러운 상태가 됩니다.

    4. 사설 레지스트리 인증 문제가 있는지 먼저 분리 진단

    리포트가 안 생길 때는 스캐너 자체를 의심하기 전에 인증 흐름부터 확인하는 편이 빠릅니다. 여기서 시간 많이 쓰는 팀이 정말 많습니다.

    kubectl get events -A --sort-by=.lastTimestamp
    kubectl get secrets -n trivy-system
    kubectl describe serviceaccount -n trivy-system trivy-operator

    클러스터 내 스캐너가 리포트를 못 만드는 가장 흔한 이유는 인증 연결 누락입니다. 이미지 풀은 애플리케이션 서비스 계정이 잘하는데, 스캐너는 다른 서비스 계정이라 접근을 못 하는 경우가 잦습니다. 표면 증상은 “리포트가 안 생긴다”인데 근본 원인은 권한 경계가 다른 데 있습니다. 이건 설정 몇 줄 더 넣는다고 해결되지 않고, 누가 어느 레지스트리를 어떤 자격 증명으로 읽는지 모델을 분리해서 봐야 합니다.

    재현 가능한 시나리오로 보는 운영 포인트

    이런 시나리오는 실제 운영에서 자주 나옵니다. 내부 API 서비스가 registry.example.com/team/api:2026-09-01로 배포됐고, 배포 당시 CI 스캔은 통과했습니다. 며칠 뒤 베이스 이미지에 대한 신규 취약점 정보가 스캐너 DB에 반영됩니다. 코드도, 이미지 다이제스트도 바뀌지 않았는데 클러스터 리포트에는 새로운 Critical이 생길 수 있습니다. CI만 보는 팀은 “왜 갑자기 숫자가 늘었지?”에서 멈추고, 클러스터 재스캔이 있는 팀은 “지금 떠 있는 워크로드 중 어느 네임스페이스가 영향권인지”까지 바로 확인합니다.

    이 지점에서 중요한 것은 억울함을 줄이는 기록입니다. 결과가 바뀌었는데 이미지가 안 바뀌었을 때 스캐너 오작동으로 몰아가는 경우를 여러 번 봤습니다. 실제로는 취약점 DB 갱신이나 메타데이터 해석 차이인 경우가 많았습니다. 그래서 운영 기준에는 반드시 스캔 시각, DB 갱신 시각, 대상 다이제스트를 함께 남겨 두는 게 좋습니다.

    실제로 많이 막히는 문제와 해결법

    Private Registry 인증이 안 붙는 경우

    증상은 단순합니다. 리포트가 생성되지 않거나 이벤트에 인증 실패가 남습니다. 그런데 근본 원인은 보통 “앱 배포에 쓰는 이미지 풀 자격 증명”과 “스캐너가 읽는 자격 증명”을 같은 것으로 착각한 데 있습니다. 해결은 권한을 더 크게 주는 것이 아니라, 스캐너 전용 읽기 권한이 어디에 연결돼 있는지 확인하는 것입니다.

    취약점이 너무 많이 떠서 운영이 멈추는 경우

    초기 도입기에는 거의 반드시 겪습니다. 이때 모든 심각도를 한 번에 차단하면 실제로는 보안 수준이 올라가는 게 아니라 파이프라인이 멈춥니다. 저는 보통 Critical 즉시 차단, High는 예외 문서와 만료일 부여, Medium 이하는 추세 관찰로 시작합니다. 운영이 감당할 수 있는 속도로 올라가야 자동화도 습관으로 남습니다.

    태그만 보고 이미지를 추적하는 경우

    latest나 재사용되는 릴리스 태그는 감사 추적에 불리합니다. 스캔 결과가 어느 바이너리 집합을 가리키는지 불명확해집니다. 실제 배포 검증이나 사고 대응까지 생각하면 다이제스트 기준 저장이 맞습니다. 태그는 사람이 보기 좋고, 다이제스트는 시스템이 책임지기 좋습니다.

    운영 판단 없이 숫자만 보는 경우

    스캔 수치는 우선순위의 단서일 뿐, 우선순위 그 자체는 아닙니다. 외부에 노출된 서비스의 런타임 라이브러리 문제와 내부 배치 컨테이너의 개발 도구 패키지 문제는 같은 High여도 대응 순서가 달라집니다. 저는 아래 네 항목을 같이 봅니다.

    • 노출 면적: Ingress 뒤 서비스인지, 내부 잡인지 구분합니다.
    • 권한 수준: root 실행 여부, 추가 capability, privileged 여부를 확인합니다.
    • 수정 가능성: 베이스 이미지 교체로 끝나는지, 코드 수정과 검증이 필요한지 나눕니다.
    • 예외 만료: 지금 못 고치는 항목은 다시 볼 날짜가 없으면 영구 미해결이 됩니다.

    스캔이 클러스터 자원을 갉아먹는 경우

    운영 규모가 커지면 이것도 무시하기 어렵습니다. 큰 이미지가 많고 네임스페이스가 많으면 스캔 자체가 CPU, 메모리, 레지스트리 호출량을 소모합니다. 증상은 오퍼레이터가 느려지거나 리포트 생성이 밀리는 식으로 나타납니다. 재스캔은 많이 돌린다고 무조건 좋은 게 아니라, 업데이트 빈도, 클러스터 자원, 조치 가능한 인력에 맞춰야 합니다.

    클러스터 보안 관점의 Kubernetes 보안 체크리스트 리포트 구성도

    오퍼레이터가 워크로드를 스캔하고 리포트를 남기는 클러스터 내부 흐름 예시입니다.

    검증: 자동화가 제대로 작동했는지 확인하는 방법

    자동화 검증은 명령 성공 여부만 보면 부족합니다. 실패해야 할 때 진짜 실패하는지, 운영 중 판정 변화가 반영되는지, 팀원이 바뀌어도 같은 경로로 확인 가능한지가 핵심입니다. 제가 점검할 때는 다음 순서로 봅니다.

    1. 테스트용 이미지를 하나 빌드하고 로컬 결과를 저장합니다.
    2. 같은 이미지를 CI에서 같은 심각도 기준으로 스캔해 동일하게 실패 또는 통과하는지 확인합니다.
    3. 가능하면 배포 시점의 이미지 다이제스트를 기록해 로컬 결과와 CI 결과를 같은 대상으로 묶습니다.
    4. 클러스터에 배포한 뒤 vulnerabilityreports 리소스가 생성되는지 확인합니다.
    5. 사설 레지스트리 이미지는 인증 실패 없이 조회되는지 이벤트와 서비스 계정 기준으로 검증합니다.
    6. 예외 처리한 항목이 문서, CI 정책, 클러스터 결과에서 같은 의미로 반영되는지 비교합니다.

    환경에 따라 추가 리포트가 생성될 수 있으니, 아래처럼 어떤 CRD가 실제로 보이는지 함께 확인하면 좋습니다. 다만 활성화된 기능과 Trivy Operator 설정에 따라 결과는 달라질 수 있습니다.

    kubectl get vulnerabilityreports -A
    kubectl get configauditreports -A
    kubectl get clustercompliancereports -A

    중요한 것은 결과를 반복 조회 가능한 형태로 남기는 것입니다. 사람이 한 번 보고 지나가는 로그는 운영 자산이 아닙니다. 조회 경로가 고정되고 같은 명령으로 다시 확인할 수 있어야 팀 지식이 됩니다. 그리고 Pod Security Standards, RBAC, NetworkPolicy 관련 글도 함께 보면 Kubernetes 보안 체크리스트를 더 입체적으로 잡는 데 도움이 됩니다.

    Kubernetes 보안 체크리스트 결과 검증용 이미지 스캔 대시보드

    배포된 워크로드별 취약점 현황과 확인 포인트를 보여주는 결과 화면 예시입니다.

    FAQ: 현장에서 자주 받는 질문

    Q. 스캔 결과가 바뀌었는데 이미지는 안 바뀌었습니다.

    A. 충분히 가능한 일입니다. 가장 흔한 원인은 취약점 데이터베이스 갱신입니다. 그래서 스캔 시각, DB 갱신 시각, 이미지 다이제스트를 같이 기록해야 나중에 원인 추적이 쉬워집니다.

    Q. 모든 High를 바로 차단해야 하나요?

    A. 팀 상태에 따라 다릅니다. 초기 도입기라면 Critical 우선 차단, High는 예외와 만료일 관리가 현실적입니다. 이미 배포 품질 게이트가 정착된 조직이라면 High까지 차단해도 됩니다. 중요한 건 기준을 문서가 아니라 파이프라인에 넣는 것입니다.

    Q. Kubernetes 보안 체크리스트에서 이미지 스캔만 하면 충분한가요?

    A. 부족합니다. 이미지 스캔은 공급망과 배포 전 검증의 한 축일 뿐입니다. Pod Security Standards, RBAC, NetworkPolicy, Secret 관리, 감사 로그까지 함께 봐야 운영 리스크가 줄어듭니다. 다만 시작점으로는 이미지 스캔이 체감 효과가 빠른 편입니다.

    운영 기준은 이렇게 잡으시면 됩니다

    제가 오래 운영을 보면서 내린 기준은 단순합니다. 팀이 아직 수동 검토에 기대고 있다면 Trivy CLI로 CI 차단부터 붙이세요. 이 단계에서는 로컬과 CI 판정 일치, 다이제스트 기록, 예외 만료일 관리가 핵심입니다. 운영 클러스터가 여러 개이거나 롤백이 잦다면 클러스터 재스캔도 같이 가져가세요. 배포 당시엔 멀쩡했던 이미지가 나중에 문제로 바뀌는 순간을 잡아내려면 이 경로가 필요합니다.

    반대로 아직 팀이 결과 해석도 못 따라가는데 Admission 단계에서 전부 막는 것은 추천하지 않습니다. 그건 통제가 아니라 마찰이 됩니다. 이럴 땐 관찰 모드로 현재 상태 파악, 그다음 Critical 차단, 마지막으로 High까지 확대 순서가 덜 부서집니다. 규정이 엄격하고 변경 이력 감사가 중요하다면, 그때 배포 허가 단계 연계까지 가면 됩니다.

    제 권고를 한 줄로 줄이면 이렇습니다. 작게 시작하는 팀은 CI 스캔 + 실패 코드 적용, 운영 가시성이 필요한 팀은 클러스터 재스캔 추가, 강제 통제가 필요한 조직은 Admission 정책 연계. 이 순서가 실무에서 가장 덜 아프고, 결과가 남고, 다음 담당자도 이해하기 쉽습니다.

    본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다.

    CI 스캔, 클러스터 재스캔, 예외 관리, 정책 연계를 한 장으로 정리한 요약 이미지입니다.

  • [Kubernetes] Kubernetes PodSecurity 도입 전후 비교: 보안 강화 사례 연구

    [Kubernetes] Kubernetes PodSecurity 도입 전후 비교: 보안 강화 사례 연구

    [Kubernetes] Kubernetes PodSecurity 도입 전후 비교: 보안 강화 사례 연구

    Kubernetes PodSecurity 도입을 고민하시는 분들은 아마 한 번쯤 이런 순간이 있으셨을 겁니다. 워크로드는 잘 돌아가는데, 막상 보안 점검을 해보면 privileged(특권 모드) 컨테이너가 섞여 있거나, hostPath(호스트 경로 마운트) 같은 위험한 설정이 아무 제어 없이 배포되는 경우 말이죠. 저도 처음엔 “네임스페이스만 잘 나누면 되지 않을까?” 싶었는데, 실제로 운영 환경과 홈랩에서 계속 만져보니 결국 배포 시점의 기본 보안 가드레일이 있어야 하더라고요. 그래서 이번 글에서는 Kubernetes PodSecurity 도입 전후를 어떻게 비교했는지, 어떤 문제가 줄었는지, 그리고 적용하면서 어디서 삽질했는지 경험 기반으로 정리해보겠습니다.

    특히 이 글은 Kubernetes 보안, Pod Security Standards(파드 보안 표준), 보안 정책을 실제 운영에 녹이는 관점으로 읽으시면 도움이 됩니다. 이론만 설명하는 글이 아니라, 제가 직접 해보니 어디서 걸리고 어떤 기준으로 정리해야 덜 힘든지 그 흐름을 보여드릴게요.

    PodSecurity 도입 전에는 제어가 느슨하고, 도입 후에는 네임스페이스 정책과 허용 범위가 명확해진 구조를 한눈에 보여주는 개요 이미지입니다.

    Kubernetes PodSecurity 도입이 왜 중요한가요

    쉽게 말해 PodSecurity는 “이 파드(Pod)는 어디까지 위험한 설정을 써도 되는가”를 네임스페이스 단위로 관리하는 방식입니다. 예전에는 팀마다 매니페스트를 제각각 작성하다 보니, 어떤 워크로드는 runAsNonRoot가 잘 들어가 있고, 어떤 건 아예 빠져 있고, 또 어떤 건 디버깅한다고 privileged: true를 넣어둔 채 남아 있기도 했습니다. 이게 한두 개면 눈으로 보겠는데, 서비스가 늘어나면 사람의 기억으로 통제하는 건 불가능하더라고요.

    여기서 중요한 포인트! 보안은 좋은 설정을 권장하는 것만으로는 부족합니다. 실제로는 잘못된 설정이 들어왔을 때 막아줘야 합니다. 제가 현장에서 느낀 것도 그거였습니다. 리뷰에서 빠지고, 급한 장애 대응 중엔 원칙이 밀리고, 결국 가장 약한 워크로드가 전체 클러스터 위험도를 끌어올리거든요.

    Pod Security Standards(파드 보안 표준)를 먼저 이해해야 합니다

    Pod Security Standards는 파드 보안 수준을 세 단계로 나눠서 생각하게 해줍니다. 저도 처음엔 이름만 보고 좀 딱딱하다고 느꼈는데, 실제로 써보니까 기준점이 분명해서 오히려 팀 합의가 빨라졌습니다.

    수준 설명 적합한 상황
    Privileged 제한이 거의 없는 수준입니다. 특수한 시스템 워크로드, 아주 제한적인 운영 도구
    Baseline 명백히 위험한 설정을 막는 기본선입니다. 일반적인 애플리케이션 네임스페이스의 첫 단계
    Restricted 가장 엄격한 수준으로, 안전한 기본값을 강하게 요구합니다. 보안 민감 서비스, 표준화가 잘 된 앱 워크로드

    제가 보통 권하는 방식은 이렇습니다.

    • 처음부터 전부 Restricted로 밀어붙이지 않습니다.
    • 기존 레거시 워크로드가 많다면 먼저 Baseline으로 현재 상태를 정리합니다.
    • 새로 만드는 네임스페이스나 표준화된 앱은 Restricted를 기본으로 잡습니다.

    이 접근이 중요한 이유는, 보안 정책은 강하면 좋은 게 아니라 운영에 정착해야 좋은 것이기 때문입니다. 너무 급하게 올리면 예외만 늘어나고 결국 정책이 무력화되더라고요.

    도입 전 환경에서 실제로 보였던 문제들

    제가 PodSecurity를 넣기 전에 먼저 한 일은 “무슨 설정이 돌아다니고 있는지”를 보는 것이었습니다. 막연히 위험할 것 같다가 아니라, 실제 매니페스트와 실행 중인 파드를 기준으로 봐야 하거든요. 그때 자주 보였던 문제는 아래와 같았습니다.

    • securityContext가 아예 없는 배포가 생각보다 많았습니다.
    • allowPrivilegeEscalation 설정이 빠져 있었습니다.
    • 루트(root) 권한 기반 이미지가 관성적으로 사용되고 있었습니다.
    • 운영 중 잠깐 쓰려고 만든 디버그용 파드가 그대로 남아 있었습니다.
    • 예외가 문서화되지 않아, 왜 허용했는지 아무도 설명 못 하는 설정이 있었습니다.

    이런 상태에서 보안 점검이 들어오면 굉장히 피곤해집니다. “왜 이 컨테이너는 루트로 뜨나요?” 같은 질문이 나오면, 실제 이유가 있어서가 아니라 그냥 기본값이라서 그런 경우가 많거든요. 사실 제일 무서운 건 악의적인 공격보다도 무심코 허용된 기본값입니다.

    Kubernetes PodSecurity 도입 절차: 제가 실제로 밟은 순서

    여기서는 복잡하게 가지 않고, 운영에서 비교적 덜 아픈 순서로 설명해볼게요. 핵심은 한 번에 차단(enforce)하지 말고 먼저 관찰부터 하는 겁니다.

    1. 보호할 네임스페이스를 분류합니다.
    2. audit(감사)와 warn(경고) 중심으로 먼저 적용합니다.
    3. 실패하는 워크로드를 수정합니다.
    4. 문제가 없는 네임스페이스부터 enforce(강제)로 전환합니다.
    5. 예외 워크로드는 별도 네임스페이스로 분리합니다.

    예를 들어 애플리케이션용 네임스페이스에 먼저 Baseline 경고와 감사 정책을 걸어볼 수 있습니다.

    kubectl label namespace app-team \
      pod-security.kubernetes.io/audit=baseline \
      pod-security.kubernetes.io/warn=baseline --overwrite

    이 상태에서는 배포가 바로 막히지는 않지만, 어떤 설정이 기준을 벗어나는지 확인하기 좋아요. 저는 처음부터 차단했다가 CI/CD 파이프라인이 우르르 깨지는 바람에 한 번 크게 삽질했었습니다. 그 뒤로는 무조건 경고와 감사부터 봅니다.

    조금 정리됐다 싶으면 강제로 올립니다.

    kubectl label namespace app-team \
      pod-security.kubernetes.io/enforce=baseline \
      pod-security.kubernetes.io/enforce-version=latest --overwrite

    운영에서는 latest 대신 조직에서 검증한 기준 버전을 명시적으로 관리하는 팀도 많습니다. 저는 팀 표준이 아직 흔들릴 때는 버전 기준을 문서화해두는 쪽이 더 낫다고 봅니다. 기준이 자주 바뀌면 개발팀이 체감상 더 힘들어하거든요.

    Kubernetes PodSecurity 도입 시 네임스페이스 정책 적용 흐름 이미지

    네임스페이스에 audit, warn, enforce 라벨을 붙이면서 단계적으로 정책을 강화하는 흐름을 설명하는 이미지입니다.

    Restricted(제한) 수준을 목표로 할 때 매니페스트 예시

    다음은 비교적 안전한 기본 형태의 예시입니다. 모든 앱이 이대로 되진 않겠지만, 팀 표준의 시작점으로 잡기 좋습니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web-app
      namespace: app-team
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: web-app
      template:
        metadata:
          labels:
            app: web-app
        spec:
          securityContext:
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
          containers:
            - name: web
              image: nginx:stable
              ports:
                - containerPort: 80
              securityContext:
                allowPrivilegeEscalation: false
                capabilities:
                  drop:
                    - ALL

    여기서 자주 보는 포인트는 아래입니다.

    • runAsNonRoot: true로 비루트 실행을 강제합니다.
    • allowPrivilegeEscalation: false로 권한 상승 가능성을 줄입니다.
    • capabilities.drop: [ALL]로 불필요한 리눅스 capability(리눅스 커널 권한 집합)를 제거합니다.
    • seccompProfile.type: RuntimeDefault로 기본 시스템 호출 필터를 사용합니다.

    도입 전후 비교: 무엇이 달라졌나

    정량 수치를 억지로 붙이기보다, 운영 체감 기준으로 말씀드리는 게 더 정확할 것 같습니다. Kubernetes PodSecurity 도입 전에는 “문제 있는 매니페스트가 배포된 뒤 나중에 발견되는 구조”였다면, 도입 후에는 “기준에 맞지 않으면 배포 단계에서 바로 드러나는 구조”로 바뀌었습니다. 이 차이가 생각보다 큽니다.

    비교 항목 도입 전 도입 후
    보안 기준 문서나 리뷰에 의존 네임스페이스 정책으로 강제
    위험 설정 발견 시점 배포 후 또는 점검 시 배포 직전 또는 배포 중
    팀 간 일관성 사람마다 다름 정책 기준으로 통일
    예외 관리 구두 합의가 많음 별도 네임스페이스와 문서로 관리

    제가 직접 해보니 제일 편했던 건, 리뷰에서 감정 소모가 줄어든다는 점이었습니다. 예전엔 “이 설정 위험하지 않나요?” 같은 대화가 사람 대 사람의 의견 충돌처럼 번질 때가 있었는데, 정책을 두고 나면 “이건 Baseline 기준에서 걸립니다”처럼 훨씬 명확해집니다. 이거 진짜 편하더라고요.

    ⚠️ 적용하면서 많이 막혔던 문제와 해결 방법

    이 섹션은 꼭 넣고 싶었습니다. 문서만 보면 간단해 보이는데, 실제로는 여기서 다들 한 번씩 막히거든요.

    1. 기존 이미지가 non-root 실행을 전제로 만들어지지 않은 경우

    Restricted 수준으로 올리면 가장 먼저 터지는 부분 중 하나입니다. 애플리케이션은 문제 없어 보여도, 이미지 내부 디렉터리 권한이나 엔트리포인트 스크립트가 루트 권한을 전제하는 경우가 있습니다. 저도 처음엔 “분명 앱은 잘 뜨는데 왜 이게 안 되지?” 싶었는데, 알고 보니 컨테이너 시작 시 쓰기 권한이 필요한 경로가 있었어요.

    이럴 때는 아래 순서로 봤습니다.

    1. 이미지가 비루트 실행을 지원하는지 확인합니다.
    2. 쓰기 경로가 꼭 필요한지 줄입니다.
    3. 필요하면 Dockerfile 단계에서 권한을 정리합니다.
    4. 정말 예외가 필요한 워크로드만 별도 분리합니다.

    2. 운영 도구와 일반 앱을 같은 기준으로 묶은 경우

    로그 수집기, CNI 관련 컴포넌트, 노드 접근이 필요한 에이전트는 일반 앱과 요구 조건이 다를 수 있습니다. 그런데 이걸 한 네임스페이스 정책으로 퉁치면 꼭 어딘가서 충돌이 납니다. 저는 한동안 “왜 어떤 건 꼭 예외가 생기지?” 하다가, 결국 애플리케이션 네임스페이스와 인프라 네임스페이스를 분리하면서 정리가 되더라고요.

    3. warn만 보고 끝내는 경우

    이건 진짜 자주 봅니다. 경고는 보이는데 바빠서 미루다 보면, 3개월 뒤에도 같은 경고가 그대로 쌓여 있어요. 그래서 저는 warn → audit → enforce 전환 일정을 미리 잡아두는 편입니다. 일정이 없으면 정책은 거의 장식품이 됩니다.

    Kubernetes PodSecurity 도입 후 경고와 배포 실패 분석 이미지

    경고 단계에서 어떤 항목이 걸리는지 확인하고, enforce 전환 전에 수정 포인트를 찾는 과정을 보여주는 이미지입니다.

    검증은 이렇게 했습니다

    보안 정책은 넣는 것보다 검증이 더 중요합니다. 정책이 존재해도 실제로 막히지 않으면 의미가 없으니까요. 제가 보통 하는 검증은 두 가지입니다. 하나는 정상 매니페스트가 잘 배포되는지, 다른 하나는 의도적으로 위험한 설정을 넣었을 때 차단되는지 확인하는 겁니다.

    예를 들어 아래처럼 위험한 설정이 들어간 테스트 파드를 만들어 봅니다.

    apiVersion: v1
    kind: Pod
    metadata:
      name: privileged-test
      namespace: app-team
    spec:
      containers:
        - name: test
          image: busybox:1.36
          command: ["sh", "-c", "sleep 3600"]
          securityContext:
            privileged: true

    그리고 배포를 시도합니다.

    kubectl apply -f privileged-test.yaml

    정상이라면 보안 정책 위반으로 거부되는 흐름이 나와야 합니다. 반대로, 팀 표준 매니페스트는 무리 없이 통과해야 하고요. 여기서 중요한 건 단순히 “막혔다”가 아니라, 왜 막혔는지 개발자도 이해할 수 있어야 한다

    추가로 확인했던 항목은 아래와 같습니다.

    • 새 네임스페이스 생성 시 기본 라벨 정책이 누락되지 않는지
    • CI/CD에서 배포 실패 메시지가 충분히 읽히는지
    • 예외 네임스페이스가 무분별하게 늘어나지 않는지
    • 운영 문서와 실제 클러스터 라벨 상태가 일치하는지
    Kubernetes PodSecurity 도입 후 배포 검증 결과 이미지

    정상 배포와 정책 위반 배포가 어떻게 구분되는지, 운영자가 어떤 식으로 결과를 확인하는지 보여주는 이미지입니다.

    운영 기준을 문서화해야 도입이 끝납니다

    사실 PodSecurity는 라벨 몇 개 붙인다고 끝나지 않습니다. 진짜 끝은 팀 운영 기준이 문서로 남고, 다음 사람도 같은 방식으로 적용할 수 있을 때입니다. 저도 처음엔 클러스터에만 반영해두고 안심했었는데, 시간이 지나니 “이 네임스페이스는 왜 baseline이지?” 같은 질문이 계속 나오더라고요.

    그래서 저는 아래 항목을 최소 문서 세트로 남깁니다.

    • 네임스페이스별 목표 수준: baseline 또는 restricted
    • 예외 허용 기준: 누가 승인하고 언제 재검토하는지
    • 개발팀용 체크리스트: 보안 컨텍스트 기본 예시
    • 배포 실패 시 확인 순서: 어떤 필드를 먼저 볼지

    혹시 이런 경험 있으신가요? 정책은 분명 있는데, 새 프로젝트가 생길 때마다 같은 설명을 또 하고 또 하게 되는 상황 말이죠. 이런 반복을 줄이려면 결국 문서화와 템플릿화가 답입니다. 다음 글에서는 OPA Gatekeeper(오픈 정책 에이전트 게이트키퍼)나 Kyverno(카이버노) 같은 정책 엔진과 PodSecurity를 어떻게 역할 분담할지 다뤄볼 예정입니다. 이전 글에서 다룬 네임스페이스 표준화 내용이 있으시다면 같이 연결해서 보시는 것도 좋습니다.

    도입 전후의 차이, 운영 체크리스트, 다음 단계까지 한 장으로 요약한 인포그래픽 이미지입니다.

    정리: Kubernetes 보안은 작은 강제에서 시작됩니다

    Kubernetes PodSecurity 도입은 거창한 보안 프로젝트처럼 보일 수 있지만, 실제로는 “위험한 기본값을 그냥 두지 않겠다”는 아주 현실적인 출발점에 가깝습니다. 제가 여러 번 적용해보니 가장 효과적인 방식은 이렇습니다. 먼저 경고와 감사로 현재 상태를 파악하고, 그다음 Baseline으로 정리한 뒤, 준비된 워크로드부터 Restricted로 올리는 흐름이었습니다. 처음엔 이게 뭔가 싶었는데, 한 번 기준이 잡히고 나면 운영이 훨씬 편해집니다. 드디어 됐다! 싶은 순간이 오더라고요.

    마지막으로 한 줄 요약하면 이겁니다. Pod Security Standards는 문서가 아니라 운영 습관으로 들어와야 합니다. 보안 정책이 실제 배포 과정에 녹아들면, 리뷰 품질도 좋아지고 예외도 줄고, 나중에 감사 대응도 덜 힘들어집니다. Kubernetes 보안을 어디서부터 손대야 할지 막막하셨다면, PodSecurity부터 차근차근 시작해보세요.

    FAQ: 자주 묻는 질문

    모든 네임스페이스를 바로 Restricted로 가야 하나요?

    아닙니다. 레거시 워크로드가 많다면 Baseline부터 정리하는 편이 현실적입니다. 무리하게 올리면 예외만 늘어날 수 있습니다.

    PodSecurity만 있으면 보안 정책이 충분한가요?

    기본 보안 가드레일로는 매우 유용하지만, 조직별 세부 규칙까지 모두 대신하진 못합니다. 필요한 경우 별도 정책 엔진과 역할을 나눠가는 게 좋습니다.

    가장 먼저 점검할 필드는 무엇인가요?

    runAsNonRoot, allowPrivilegeEscalation, privileged, capabilities, hostPath 같은 항목부터 보는 걸 권합니다.

  • [k8s] 쿠버네티스 RBAC 보안 강화 체크리스트: 최소 권한 원칙 적용 가이드

    [k8s] 쿠버네티스 RBAC 보안 강화 체크리스트: 최소 권한 원칙 적용 가이드

    [Kubernetes] 쿠버네티스 RBAC 보안 강화 체크리스트

    쿠버네티스 RBAC 보안은 클러스터 운영에서 생각보다 빨리 발목을 잡는 주제입니다. 처음엔 워크로드만 잘 뜨면 된다고 생각했었는데, 실제 운영에 들어가면 누가 어디까지 볼 수 있고, 무엇을 수정할 수 있는지부터 꼬이더라고요. 저도 홈랩에서 Kubernetes(쿠버네티스, 컨테이너 오케스트레이션 플랫폼) 클러스터를 굴리면서 서비스 계정 하나를 너무 넓게 열어뒀다가, 나중에 권한 정리하느라 삽질 좀 했습니다 ㅎㅎ 그래서 오늘은 쿠버네티스 RBAC 보안을 기준으로, 실무에서 바로 점검할 수 있는 체크리스트 형태로 정리해보겠습니다. 특히 k8s 권한 관리가 막막한 분, RBAC 최소 권한을 어디서부터 적용해야 할지 헷갈리는 분께 실질적인 도움이 될 거라고 생각합니다.

    이 글은 특정 벤더 기능이 아니라 Kubernetes 기본 RBAC(Role-Based Access Control, 역할 기반 접근 제어)를 중심으로 설명합니다. 즉, 대부분의 표준 클러스터에서 바로 적용 가능한 실전 내용만 담았습니다.

    쿠버네티스 RBAC 보안 아키텍처 개요 이미지

    RBAC의 큰 흐름을 한 장으로 보면, 왜 최소 권한이 중요한지 훨씬 빨리 감이 옵니다.

    1. 왜 쿠버네티스 RBAC 보안이 먼저냐

    많은 분들이 보안이라고 하면 NetworkPolicy(네트워크폴리시, 네트워크 접근 제어)나 이미지 스캔부터 떠올리시는데요. 근데 여기서 중요한 포인트! 권한이 과하면 그 뒤 보안 장치들이 의미가 많이 줄어듭니다. 읽기 전용이어야 할 계정이 Secret(시크릿, 민감 정보 객체)을 읽을 수 있다거나, 특정 네임스페이스만 다뤄야 하는 자동화 계정이 클러스터 전체를 수정할 수 있다면 문제는 금방 커지더라고요.

    제가 직접 해보니 RBAC는 꼭 사고 대응 때문에만 필요한 게 아니었습니다. 운영자끼리 역할을 분리할 때도 좋고, CI/CD 파이프라인이 쓸 계정을 분리할 때도 좋고, 나중에 감사(Audit, 행위 추적)할 때도 훨씬 편해지더라고요. 누가 왜 그 권한을 갖는지 설명할 수 있어야 관리가 됩니다.

    2. RBAC를 쉽게 말하면: 누가, 어디서, 뭘 하느냐

    쉽게 말해 RBAC는 세 가지를 묶는 구조입니다.

    • 누가: User(사용자), Group(그룹), ServiceAccount(서비스어카운트, 파드가 쓰는 계정)
    • 어디서: Namespace(네임스페이스, 논리적 격리 단위) 또는 클러스터 전체
    • 뭘 하느냐: verbs(동작)와 resources(리소스)

    여기서 자주 헷갈리는 게 Role과 ClusterRole 차이입니다. 저도 처음엔 이게 뭔가 싶었는데, 정리하면 꽤 단순합니다.

    구성 요소 범위 용도 예시
    Role 특정 Namespace 네임스페이스 내부 권한 정의 dev 네임스페이스의 Pod 조회
    ClusterRole 클러스터 전체 또는 공통 리소스 광범위 권한 또는 공통 읽기 권한 정의 노드 조회, 전체 네임스페이스 읽기
    RoleBinding 특정 Namespace Role 또는 ClusterRole을 특정 주체에 연결 개발자 그룹에 dev 조회 권한 부여
    ClusterRoleBinding 클러스터 전체 ClusterRole을 클러스터 범위로 연결 운영팀에 클러스터 전역 읽기 권한 부여

    즉, RBAC 최소 권한의 핵심은 필요한 주체에게 필요한 리소스만, 필요한 동작만 허용하는 겁니다. 말은 쉬운데 실제로는 여기서 많이 넓어지더라고요. 특히 * 와일드카드, 무심코 준 cluster-admin, 그리고 서비스 계정 재사용이 흔한 함정입니다.

    3. 쿠버네티스 보안 체크리스트: 먼저 이것부터 보세요

    운영 중인 클러스터가 있다면 아래 항목부터 점검해보시면 됩니다. 저는 새 클러스터를 만들 때도 이 리스트부터 잡고 시작합니다.

    1. cluster-admin 바인딩 확인: 정말 필요한 주체만 갖고 있는지 봅니다.
    2. ServiceAccount 분리: 애플리케이션별, 작업별로 계정을 나눕니다.
    3. 와일드카드 금지: resources, verbs에 * 사용을 최대한 피합니다.
    4. Secret 접근 최소화: 꼭 필요한 워크로드만 읽게 합니다.
    5. Namespace 단위 분리: 팀, 환경, 서비스 기준으로 경계를 명확히 둡니다.
    6. 읽기와 쓰기 분리: 조회 전용 계정과 변경 가능 계정을 나눕니다.
    7. 권한 검증 자동화: kubectl auth can-i로 배포 전에 확인합니다.
    8. 휴면 계정 정리: 더 이상 쓰지 않는 RoleBinding, ClusterRoleBinding 제거합니다.
    9. 기본 토큰 사용 점검: default ServiceAccount에 불필요한 권한을 주지 않습니다.
    10. 감사 로그 연계 검토: 누가 어떤 API를 호출했는지 추적 가능한 구조를 만듭니다.

    혹시 이런 경험 있으신가요? 급해서 일단 권한부터 열고, 나중에 줄이자고 생각했는데 그 나중이 안 오는 경우요. 실제로 써보니까 RBAC는 처음에 30분 더 쓰는 게 나중에 몇 시간을 아껴줍니다.

    4. 실전 구현: 네임스페이스 단위로 RBAC 최소 권한 적용하기

    이제 예제로 가보겠습니다. 시나리오는 단순합니다. ops-viewer라는 ServiceAccount가 production 네임스페이스 안에서 Pod와 Deployment만 읽을 수 있게 만들겠습니다. 수정, 삭제, Secret 조회는 못 하게 두는 방식입니다. 이런 식으로 시작하면 k8s 권한 관리가 훨씬 명확해집니다.

    4-1. 네임스페이스와 서비스 계정 만들기

    kubectl create namespace production
    kubectl -n production create serviceaccount ops-viewer

    여기서 서비스 계정을 애플리케이션용 계정과 섞지 않는 게 중요합니다. 저는 예전에 배치 작업과 운영 조회 계정을 하나로 묶었다가, 생각보다 많은 권한이 전파되더라고요.

    4-2. Role 정의

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: pod-deploy-readonly
      namespace: production
    rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get", "list", "watch"]
    - apiGroups: ["apps"]
      resources: ["deployments"]
      verbs: ["get", "list", "watch"]

    포인트는 명확합니다. pods, deployments만 읽을 수 있게 열었습니다. 여기서 Secret이나 ConfigMap까지 습관적으로 넣지 않는 게 핵심입니다. 진짜 필요한지부터 따져봐야 합니다.

    4-3. RoleBinding 연결

    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: pod-deploy-readonly-binding
      namespace: production
    subjects:
    - kind: ServiceAccount
      name: ops-viewer
      namespace: production
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: pod-deploy-readonly

    이제 이 ServiceAccount는 production 네임스페이스 내부의 조회 권한만 갖습니다. 클러스터 전체 권한이 아니라는 점이 중요합니다. 바로 이 경계 설정이 쿠버네티스 RBAC 보안의 기본입니다.

    쿠버네티스 RBAC 보안의 네임스페이스 권한 구성 이미지

    실전에서는 Role과 Binding 연결 관계를 그림으로 한 번 정리해두면 팀원 온보딩 때 정말 편합니다.

    4-4. 적용 명령

    kubectl apply -f role.yaml
    kubectl apply -f rolebinding.yaml

    4-5. 권한 검증

    kubectl auth can-i get pods \
      --as=system:serviceaccount:production:ops-viewer \
      -n production
    
    kubectl auth can-i get secrets \
      --as=system:serviceaccount:production:ops-viewer \
      -n production
    
    kubectl auth can-i delete deployments \
      --as=system:serviceaccount:production:ops-viewer \
      -n production

    정상이라면 첫 번째는 yes, 나머지는 no에 가깝게 나와야 합니다. 드디어 됐다! 싶은 순간이 여기입니다. RBAC는 적용보다 검증이 더 중요하거든요.

    5. 실무 체크포인트: ClusterRole은 언제 써야 하나

    모든 걸 Role로만 처리할 수는 없습니다. 예를 들어 여러 네임스페이스에서 공통 조회가 필요하거나, Node(노드), Namespace 같은 클러스터 범위 리소스를 읽어야 하는 경우엔 ClusterRole이 맞습니다. 다만 ClusterRole을 쓸 때도 바인딩 범위를 생각해야 합니다.

    예를 들어 ClusterRole 자체는 공통 읽기 정책으로 만들고, 실제 부여는 Namespace별 RoleBinding으로 제한하는 방식도 가능합니다. 이 패턴이 꽤 유용합니다. 권한 정의는 재사용하고, 적용 범위는 좁히는 거죠.

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: view-pods-common
    rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get", "list", "watch"]

    이렇게 만들어두고 필요한 네임스페이스에서 RoleBinding으로 참조하면 운영이 조금 더 단정해집니다. 저는 환경이 여러 개일 때 이 방식을 자주 씁니다.

    6. ⚠️ 주의사항과 트러블슈팅: 제가 실제로 많이 헷갈렸던 부분

    쿠버네티스 보안 체크리스트에서 자주 놓치는 함정을 몇 가지 적어보겠습니다. 저도 처음엔 꽤 많이 틀렸습니다.

    • default ServiceAccount 사용: 아무 설정 없이 파드가 default 계정을 쓰는 경우가 많습니다. 이 계정에 권한이 붙어 있으면 의도치 않은 확장이 생깁니다.
    • Secret 읽기 권한 과다: Pod 조회만 필요했는데 디버깅 편하다고 Secret 읽기까지 열어두더라고요. 이건 생각보다 위험합니다.
    • ClusterRoleBinding 남발: 편해서 전역 바인딩을 쓰다 보면, 나중에 어느 팀이 무엇을 할 수 있는지 안 보입니다.
    • verbs 누락 또는 과다: list만 필요한데 create, delete까지 들어가 있는 경우가 있습니다.
    • 서브리소스 누락: pods/log 같은 서브리소스 접근이 필요한데 본 리소스만 열어서 안 되는 경우도 많습니다.

    예를 들어 로그 조회가 안 될 때는 이런 식으로 서브리소스를 분리해줘야 합니다.

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: pod-log-reader
      namespace: production
    rules:
    - apiGroups: [""]
      resources: ["pods", "pods/log"]
      verbs: ["get", "list", "watch"]

    처음엔 Pod 읽기 권한이 있으면 로그도 되겠지 싶었는데, 여기서 막히더라고요. 이런 디테일이 RBAC에서 은근 많습니다.

    문제가 생겼을 때 확인 순서

    1. kubectl auth can-i로 해당 주체 기준 권한을 확인합니다.
    2. Role/ClusterRole의 resources, verbs, apiGroups가 맞는지 봅니다.
    3. RoleBinding이 올바른 네임스페이스에 연결됐는지 확인합니다.
    4. 서비스 계정 이름과 네임스페이스가 정확한지 다시 봅니다.
    5. 서브리소스가 필요한 작업인지 확인합니다.
    쿠버네티스 RBAC 보안 권한 검증 흐름 이미지

    권한 검증 흐름을 따로 정리해두면 트러블슈팅 시간이 확실히 줄어듭니다.

    7. 검증과 운영 결과: RBAC 최소 권한이 주는 실제 이점

    RBAC 최소 권한을 적용하고 나면 체감되는 변화가 분명합니다. 이거 진짜 편하더라고요.

    • 장애 대응 시 누가 무엇을 바꿀 수 있는지 명확해집니다.
    • 자동화 계정과 사람 계정의 역할이 분리됩니다.
    • 실수로 인한 삭제나 수정 범위를 줄일 수 있습니다.
    • 감사와 변경 이력 추적이 쉬워집니다.
    • 새 팀원에게 권한 구조를 설명하기 쉬워집니다.

    검증할 때는 아래 항목을 한 번 더 보시면 좋습니다.

    1. 서비스 계정별 허용 동작 목록이 문서화되어 있는가
    2. 네임스페이스를 넘는 불필요한 권한이 없는가
    3. Secret, Role, RoleBinding 수정 권한이 꼭 필요한 주체에만 있는가
    4. 휴면 바인딩이 남아 있지 않은가
    5. 정기 점검 시나리오가 있는가

    특히 운영 문서에 단순히 YAML만 남기지 말고, 왜 이 권한이 필요한지도 같이 적어두세요. 나중에 본인이 봐도 살짝 감동합니다. 저는 예전 설정 파일만 덩그러니 남겨놨다가, 몇 달 뒤에 제가 만든 정책을 제가 못 읽겠더라고요.

    8. 자주 묻는 질문: k8s 권한 관리에서 많이 나오는 질문

    Q1. 읽기 전용 계정인데 왜 로그가 안 보일까요?

    pods만 열고 pods/log를 빼먹은 경우가 많습니다. 서브리소스 권한을 따로 확인해보세요.

    Q2. Role만 쓰면 안 되나요?

    네임스페이스 내부만 다루면 Role로 충분한 경우가 많습니다. 하지만 클러스터 범위 리소스나 공통 정책 재사용이 필요하면 ClusterRole이 더 적합합니다.

    Q3. RBAC 최소 권한은 얼마나 잘게 쪼개야 하나요?

    정답은 없습니다. 다만 사람별로가 아니라 역할별로 나누는 쪽이 운영하기 쉽습니다. 배포 전용, 조회 전용, 로그 조회 전용처럼 나누는 방식이 실무적입니다.

    Q4. Secret 접근은 정말 따로 봐야 하나요?

    네, 저는 따로 봐야 한다고 생각합니다. Secret은 영향도가 커서 다른 읽기 권한과 한 묶음으로 주지 않는 편이 안전합니다.

    쿠버네티스 RBAC 보안 최소 권한 적용 전후 비교 이미지

    최소 권한 전후 차이를 시각화하면 팀 설득이나 운영 표준화에 꽤 도움이 됩니다.

    9. 마무리: 쿠버네티스 RBAC 보안은 결국 운영 습관입니다

    쿠버네티스 RBAC 보안은 어려운 개념이라기보다, 귀찮아서 미루기 쉬운 운영 습관에 가깝습니다. 저도 처음엔 일단 되게 만드는 쪽으로 갔었는데, 실제로 써보니까 결국 돌아와서 정리하게 되더라고요. 그래서 처음부터 RBAC 최소 권한 기준으로 설계하는 게 낫습니다.

    오늘 내용은 체크리스트 중심으로 정리했지만, 다음 단계에서는 ServiceAccount 토큰 관리, Admission Controller(어드미션 컨트롤러, 요청 검증/제어), NetworkPolicy와 함께 묶어서 보시면 훨씬 좋습니다. 이전 글에서 다뤘던 네임스페이스 분리 전략이 있다면 같이 연결해서 보셔도 좋고요. 다음 글에서는 쿠버네티스 보안 체크리스트를 확장해서 Pod Security와 Secret 관리까지 묶어 다뤄볼 예정입니다.

    정리하자면 이렇습니다. 권한은 넓게 주는 순간 편하지만, 운영은 그때부터 복잡해집니다. 반대로 처음부터 좁게 주면 조금 번거롭지만, 나중에 훨씬 덜 흔들립니다. 저는 후자가 맞더라고요.

  • [k8s] 쿠버네티스 보안 강화: RBAC와 네트워크 정책 실전 설정 가이드

    [k8s] 쿠버네티스 보안 강화: RBAC와 네트워크 정책 실전 설정 가이드

    쿠버네티스 보안 강화: RBAC, 네트워크 정책 설정 실전 가이드

    안녕하세요, 13년차 서버실 지킴이, 인프라 엔지니어 “13년차의 서버실”입니다. 오늘도 여러분의 튼튼한 인프라를 위한 이야기를 들고 왔습니다.

    도입부: 왜 쿠버네티스 보안이 중요할까요?

    요즘 클라우드 환경에서 쿠버네티스(Kubernetes, k8s)는 정말 대세 중의 대세죠. 저도 홈랩에서 다양한 애플리케이션들을 쿠버네티스 위에 올려서 실험하고 운영하고 있거든요. 그런데 이렇게 편리하고 강력한 도구일수록 보안(Security)은 더욱 중요해집니다.

    솔직히 처음엔 저도 쿠버네티스 보안? 그냥 방화벽 잘 치고, SSL/TLS 잘 적용하면 되는 거 아냐? 라고 쉽게 생각했었어요. 근데 이게 웬걸, 클러스터 내부의 파드(Pod)나 서비스(Service) 간의 통신, 그리고 누가 어떤 리소스(Resource)에 접근할 수 있는지 같은 복잡한 문제들이 산적해 있더라고요. 클라우드 환경은 기존 온프레미스(On-premise)와는 또 다른 공격 표면(Attack Surface)을 가지거든요. 특히나 클러스터 보안(Cluster Security)은 한번 뚫리면 전체 시스템이 위험해질 수 있어서 각별히 신경 써야 합니다.

    그래서 오늘은 쿠버네티스 보안을 강화하기 위한 핵심 두 가지, 바로 RBAC(Role-Based Access Control, 역할 기반 접근 제어)와 네트워크 정책(Network Policy) 설정에 대한 실전 가이드를 준비해 봤습니다. 끊임없이 변화하는 클라우드 환경에서 항상 최신 보안 트렌드를 반영하는 방법을 익혀두면 좋겠죠? 제가 직접 삽질하며 배운 내용들을 아낌없이 공유해 드릴게요!

    쿠버네티스 클러스터는 여러 보안 계층으로 보호됩니다. RBAC는 ‘누가 무엇을 할 수 있는가’를, 네트워크 정책은 ‘누가 누구와 통신할 수 있는가’를 제어하며 핵심적인 역할을 합니다.

    쿠버네티스 RBAC, 왜 필요할까요? (개념 설명)

    RBAC(Role-Based Access Control)는 말 그대로 ‘역할 기반 접근 제어’입니다. 쉽게 말해, “누가 쿠버네티스 클러스터 내에서 무엇을 할 수 있는가?”를 정의하는 메커니즘이에요. 예를 들어, 개발팀은 자기네 네임스페이스(Namespace)에 파드만 배포할 수 있고, 운영팀은 클러스터 전체의 모든 리소스를 관리할 수 있도록 권한을 나누는 거죠.

    RBAC의 주요 구성 요소는 다음과 같습니다:

    • Role (역할): 특정 네임스페이스 내에서 허용되는 권한 집합을 정의합니다. (예: ‘default’ 네임스페이스에서 파드를 조회, 생성할 수 있는 권한)
    • ClusterRole (클러스터 역할): 클러스터 전체에 걸쳐 허용되는 권한 집합을 정의합니다. 네임스페이스에 종속되지 않는 리소스(노드, 퍼시스턴트 볼륨 등)나 모든 네임스페이스에 대한 권한을 부여할 때 사용합니다.
    • RoleBinding (역할 바인딩): Role을 특정 사용자(User), 그룹(Group), 또는 서비스 계정(ServiceAccount)에 연결하여 해당 네임스페이스 내에서 권한을 부여합니다.
    • ClusterRoleBinding (클러스터 역할 바인딩): ClusterRole을 사용자, 그룹, 또는 서비스 계정에 연결하여 클러스터 전체에 걸쳐 권한을 부여합니다.

    이걸 왜 써야 하냐고요? 권한을 너무 많이 주면 보안 사고로 이어지기 쉽고, 너무 적게 주면 개발이나 운영이 힘들어지거든요. 최소 권한 원칙(Principle of Least Privilege)을 지키면서 효율적인 협업 환경을 만드는 데 RBAC가 필수적입니다.

    RBAC 설정, 저도 처음엔 삽질 좀 했습니다 (실전 구현)

    자, 이제 RBAC를 직접 설정해볼까요? 제가 홈랩에서 개발팀과 운영팀의 권한을 분리했던 경험을 바탕으로 설명해 드릴게요. 처음엔 Role과 ClusterRole, Binding의 차이가 헷갈려서 삽질 좀 했습니다 ㅎㅎ.

    1. 개발팀 전용 네임스페이스 생성

    먼저 개발팀이 사용할 네임스페이스를 만듭니다. 여기서는 <code>dev-team이라는 네임스페이스를 사용할게요.

    kubectl create namespace dev-team
    

    2. 개발팀 역할(Role) 정의

    dev-team 네임스페이스 내에서 파드와 디플로이먼트(Deployment)를 조회, 생성, 업데이트, 삭제할 수 있는 권한을 부여하는 Role을 만듭니다. 이 Role은 dev-team 네임스페이스에만 적용됩니다.

    # dev-team-role.yaml
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: dev-team-pod-deployment-manager
      namespace: dev-team
    rules:
    - apiGroups: ["", "apps"]
      resources: ["pods", "deployments"]
      verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
    - apiGroups: [""]
      resources: ["pods/log"]
      verbs: ["get"]
    

    파일을 저장하고 적용합니다.

    kubectl apply -f dev-team-role.yaml
    

    3. 개발팀 서비스 계정(ServiceAccount) 생성 및 RoleBinding

    실제 사용자 대신 서비스 계정(ServiceAccount)을 만들어서 권한을 부여하는 게 일반적입니다. 여기서는 dev-user-sa라는 서비스 계정을 만들고, 위에서 정의한 Role을 이 서비스 계정에 바인딩(Binding)합니다.

    # dev-team-sa.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: dev-user-sa
      namespace: dev-team
    ---
    # dev-team-rolebinding.yaml
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: dev-user-pod-deployment-binding
      namespace: dev-team
    subjects:
    - kind: ServiceAccount
      name: dev-user-sa
      namespace: dev-team
    roleRef:
      kind: Role
      name: dev-team-pod-deployment-manager
      apiGroup: rbac.authorization.k8s.io
    

    파일을 저장하고 적용합니다.

    kubectl apply -f dev-team-sa.yaml
    

    이제 dev-user-sa 서비스 계정은 dev-team 네임스페이스에서 파드와 디플로이먼트를 관리할 수 있게 됩니다. 이 서비스 계정의 토큰을 활용해 개발자는 해당 권한으로 클러스터에 접근할 수 있어요. 💡 팁: 실제 사용자에게는 OIDC(OpenID Connect) 연동을 통해 사용자 인증 후 RoleBinding을 연결하는 경우가 많습니다.

    쿠버네티스 RBAC는 사용자/서비스 계정, Role/ClusterRole, 그리고 이들을 연결하는 RoleBinding/ClusterRoleBinding으로 구성되어 권한 흐름을 제어합니다.

    네트워크 정책(Network Policy), 통제된 소통의 시작 (개념 설명)

    RBAC가 “누가 무엇을 할 수 있는가”를 정의한다면, 네트워크 정책(Network Policy)은 “쿠버네티스 파드(Pod) 간에 누가 누구와 통신할 수 있는가”를 정의하는 기능입니다. 기본적으로 쿠버네티스 클러스터 내의 모든 파드는 서로 아무런 제약 없이 통신할 수 있어요. 하지만 프로덕션 환경에서는 이게 너무 위험할 수 있겠죠?

    예를 들어, 웹 프론트엔드 파드가 데이터베이스 파드에 직접 접근할 필요는 없을 거예요. API 백엔드 파드만 DB에 접근해야 하고요. 이럴 때 네트워크 정책을 사용해서 파드 간의 통신을 세밀하게 제어할 수 있습니다.

    네트워크 정책은 특정 파드(podSelector)에 적용되며, 해당 파드로 들어오는 트래픽(Ingress)과 나가는 트래픽(Egress)을 정의할 수 있습니다. 한 번 정책이 적용되면, 명시적으로 허용된 트래픽 외에는 모두 차단됩니다. 이게 핵심이에요!

    Network Policy 적용, 드디어 통신이 막히네요! (실전 구현)

    이번에는 간단한 웹 서비스 환경에서 네트워크 정책을 적용해 볼게요. 프론트엔드, 백엔드, 데이터베이스 파드가 있다고 가정해 봅시다. 목표는 다음과 같습니다:

    • 프론트엔드는 백엔드에만 접근 가능해야 합니다.
    • 백엔드는 프론트엔드와 데이터베이스에만 접근 가능해야 합니다.
    • 데이터베이스는 그 어떤 파드로부터의 Egress 트래픽도 허용하지 않습니다 (오직 백엔드로부터의 Ingress만 허용).

    먼저 예시 파드들을 생성합니다. 각각 app: frontend, app: backend, app: database 라벨을 가지고 있다고 가정할게요.

    1. 기본 차단 정책(Default Deny) (선택적)

    특정 네임스페이스의 모든 Ingress/Egress 트래픽을 기본적으로 차단하는 정책입니다. 이렇게 해두면 명시적으로 허용한 트래픽만 통과하게 되므로 보안을 강화할 수 있어요. 저는 보통 이걸 먼저 적용하고 시작합니다.

    # default-deny.yaml
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: default-deny-all
      namespace: default
    spec:
      podSelector: {}
      policyTypes:
      - Ingress
      - Egress
    
    kubectl apply -f default-deny.yaml
    

    ⚠️ 주의: 이 정책을 적용하면 해당 네임스페이스의 모든 파드 통신이 끊기므로, 이후 허용 정책을 바로 적용해야 합니다!

    2. 백엔드 접근 허용 정책

    프론트엔드 파드(app: frontend)가 백엔드 파드(app: backend)에 접근하도록 허용하는 정책입니다. 백엔드 파드 입장에서는 프론트엔드로부터의 Ingress를 허용해야겠죠?

    # backend-allow-frontend.yaml
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: backend-allow-frontend
      namespace: default
    spec:
      podSelector:
        matchLabels:
          app: backend
      policyTypes:
      - Ingress
      ingress:
      - from:
        - podSelector:
            matchLabels:
              app: frontend
        ports:
        - protocol: TCP
          port: 8080 # 백엔드 서비스 포트
    
    kubectl apply -f backend-allow-frontend.yaml
    

    3. 데이터베이스 접근 허용 정책

    백엔드 파드(app: backend)가 데이터베이스 파드(app: database)에 접근하도록 허용하는 정책입니다. 데이터베이스 파드 입장에서는 백엔드로부터의 Ingress를 허용합니다.

    # database-allow-backend.yaml
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: database-allow-backend
      namespace: default
    spec:
      podSelector:
        matchLabels:
          app: database
      policyTypes:
      - Ingress
      ingress:
      - from:
        - podSelector:
            matchLabels:
              app: backend
        ports:
        - protocol: TCP
          port: 5432 # 데이터베이스 포트 (예: PostgreSQL)
    
    kubectl apply -f database-allow-backend.yaml
    

    이제 app: backend 파드는 app: frontend 파드와 app: database 파드에만 접근할 수 있게 됩니다. app: frontend 파드는 app: backend 파드에만 접근할 수 있고요. 다른 파드들과의 통신은 모두 차단됩니다. 드디어 통제된 소통이 시작되는 거죠!

    네트워크 정책을 통해 프론트엔드, 백엔드, 데이터베이스 파드 간의 통신 흐름을 세밀하게 제어하여 불필요한 접근을 차단하는 모습입니다.

    ⚠️ RBAC & Network Policy 설정 시 주의할 점 (트러블슈팅)

    제가 직접 겪었던 삽질 경험들을 바탕으로 몇 가지 주의사항을 알려드릴게요.

    RBAC 관련 주의사항

    • 최소 권한 원칙(Principle of Least Privilege): 항상 필요한 최소한의 권한만 부여해야 합니다. *(모든 권한)을 남발하면 안 돼요. 저도 처음에 귀찮아서 *로 줬다가 나중에 감사(Audit) 때 식은땀 좀 흘렸습니다 😅.
    • ClusterRole 오남용 금지: ClusterRole은 클러스터 전체에 영향을 미치기 때문에 정말 신중하게 사용해야 합니다. 웬만하면 Role과 RoleBinding으로 네임스페이스 단위로 권한을 관리하는 것이 좋습니다.
    • ServiceAccount 권한 관리: 파드 내부에서 쿠버네티스 API와 통신할 때 ServiceAccount가 사용됩니다. 각 파드에 적절한 ServiceAccount를 할당하고, 해당 ServiceAccount에 최소한의 권한을 가진 RoleBinding을 연결해야 합니다.
    • kubectl auth can-i 활용: 특정 서비스 계정이나 사용자가 어떤 작업을 할 수 있는지 확인하는 데 이 명령어가 정말 유용합니다. 설정 후 꼭 확인해 보세요!

    Network Policy 관련 주의사항

    • Default Deny 정책의 영향: 네임스페이스에 podSelector: {}와 policyTypes: [Ingress, Egress]가 포함된 Network Policy를 적용하면 해당 네임스페이스의 모든 파드 통신이 기본적으로 차단됩니다. 이때 시스템 파드나 kube-dns 같은 핵심 파드의 통신까지 막힐 수 있으니, 적용 전에 충분히 테스트하고 필요한 허용 정책을 바로 이어서 적용해야 합니다. 저도 이걸 모르고 적용했다가 클러스터가 먹통이 돼서 밤샘 삽질 좀 했습니다…
    • 라벨링(Labeling)의 중요성: Network Policy는 파드의 라벨을 기반으로 동작합니다. 따라서 일관성 있고 의미 있는 라벨을 파드에 잘 붙이는 것이 매우 중요합니다. 라벨링이 엉망이면 정책 적용이 어렵거나 잘못 적용될 수 있어요.
    • CNI 플러그인 확인: 모든 CNI(Container Network Interface) 플러그인이 Network Policy를 지원하는 것은 아닙니다. Calico, Cilium, Weave Net 등 대부분의 인기 있는 CNI는 지원하지만, 사용 중인 CNI가 지원하는지 확인해야 합니다.

    확인하고 넘어가시죠! (검증/결과)

    설정만 하고 끝내면 안 되겠죠? 제대로 적용되었는지 꼭 확인해야 합니다.

    RBAC 검증

    특정 서비스 계정이 특정 리소스에 대해 어떤 권한을 가지고 있는지 확인하려면 kubectl auth can-i 명령어를 사용합니다.

    # dev-team 네임스페이스에서 dev-user-sa 서비스 계정이 파드를 생성할 수 있는지 확인
    kubectl auth can-i create pods --as=system:serviceaccount:dev-team:dev-user-sa -n dev-team
    
    # dev-team 네임스페이스에서 dev-user-sa 서비스 계정이 노드를 조회할 수 있는지 확인 (권한 없음 예상)
    kubectl auth can-i get nodes --as=system:serviceaccount:dev-team:dev-user-sa -n dev-team
    

    RoleBinding이나 ClusterRoleBinding의 상세 정보를 조회하여 어떤 Role이 누구에게 바인딩되었는지도 확인할 수 있습니다.

    kubectl describe rolebinding dev-user-pod-deployment-binding -n dev-team
    

    Network Policy 검증

    먼저 적용된 네트워크 정책 목록을 확인합니다.

    kubectl get netpol -n default
    

    그리고 실제 파드에 접속해서 curl 등으로 통신 테스트를 해보는 것이 가장 확실합니다. 예를 들어, 프론트엔드 파드에서 데이터베이스 파드로 접속을 시도하면 실패해야 합니다.

    # 프론트엔드 파드에서 백엔드 파드로 요청 (성공 예상)
    k exec -it <frontend-pod-name> -- curl backend-service:8080
    
    # 프론트엔드 파드에서 데이터베이스 파드로 요청 (실패 예상)
    k exec -it <frontend-pod-name> -- curl database-service:5432
    

    이런 식으로 실제 시나리오를 만들어 검증해보면 됩니다. 🎉 드디어 됐다! 라고 외칠 때의 그 쾌감은 정말 최고죠!

    RBAC는 ‘누가 무엇을 할 수 있는가’를, 네트워크 정책은 ‘누가 누구와 통신할 수 있는가’를 제어하는 핵심적인 쿠버네티스 보안 메커니즘입니다.

    마무리하며: 안전한 쿠버네티스 클러스터를 향한 여정

    오늘은 쿠버네티스 보안(Kubernetes Security)을 강화하기 위한 핵심 요소인 RBAC 설정(Role-Based Access Control)과 네트워크 정책(Network Policy)에 대해 자세히 알아봤습니다. RBAC는 인적 오류나 악의적인 접근으로부터 클러스터 리소스를 보호하고, 네트워크 정책은 파드 간의 불필요한 통신을 차단하여 내부 공격 표면을 줄이는 데 큰 역할을 합니다.

    이 두 가지는 k8s 보안 가이드(k8s Security Guide)의 가장 기본적인 출발점이라고 할 수 있습니다. 물론 쿠버네티스 보안은 이것이 다가 아닙니다. 시크릿(Secret) 관리, Pod Security Admission(PSA), 컨테이너 이미지 보안 스캐닝, 그리고 클러스터 로깅 및 모니터링 등 고려해야 할 부분이 정말 많아요.

    하지만 오늘 다룬 RBAC와 네트워크 정책만 제대로 적용해도 여러분의 클러스터 보안(Cluster Security) 수준은 한 단계 높아질 겁니다. 제가 직접 겪어보니, 처음에는 어렵고 복잡해 보여도 하나하나 적용해 보면서 얻는 경험이 가장 중요하더라고요. 여러분도 이 가이드를 바탕으로 안전하고 튼튼한 쿠버네티스 클러스터를 만들어 가시길 응원합니다!

    다음 글에서는 쿠버네티스 시크릿 관리나 Pod Security Admission에 대해 더 깊이 다뤄볼 예정이니, 많은 관심 부탁드립니다!