13년차의 서버실

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

[태그:] 쿠버네티스 보안

  • [보안] Trivy, Aqua Security, Snyk: 컨테이너 보안 스캐너 비용 및 기능 분석

    [보안] Trivy, Aqua Security, Snyk: 컨테이너 보안 스캐너 비용 및 기능 분석

    안녕하세요, 13년차 서버실 지킴이입니다. 🤓

    요즘 인프라 이야기에서 컨테이너(Container)를 빼놓을 수 없죠? Docker(도커)와 Kubernetes(쿠버네티스)가 대세가 되면서 개발 속도는 엄청나게 빨라졌지만, 그만큼 보안(Security)에 대한 고민도 깊어졌습니다. 제가 직접 여러 환경을 구축하고 운영하면서 느낀 건데, ‘개발 속도만 빠르면 장땡이지!’ 했다가 크게 데인 적이 한두 번이 아니거든요. 😅

    특히 컨테이너 이미지는 한 번 만들면 여러 환경에서 재사용되기 때문에, 이미지 안에 숨겨진 취약점은 나중에 큰 사고로 이어질 수 있습니다. 그래서 오늘은 컨테이너 이미지의 취약점을 미리 스캔하고 관리해주는 도구들, 그중에서도 Trivy, Aqua Security, Snyk 세 가지 솔루션의 비용(Cost)과 기능(Feature)을 제 경험을 바탕으로 심층 분석해보려고 합니다. 어떤 도구가 우리 환경에 가장 적합할지 함께 고민해보시죠!

    ⚠️ 면책 조항: 본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근이나 공격은 불법이며 법적 처벌 대상이 될 수 있습니다. 모든 실습은 반드시 허가된 환경에서만 진행해주세요.

    컨테이너 보안 스캔 워크플로우 다이어그램: 개발부터 CI/CD, 스캐너를 통한 취약점 발견까지

    컨테이너 보안 스캐너는 개발-배포 파이프라인의 핵심 방어선입니다.

    컨테이너 보안 스캐너, 왜 필요할까요? (Concept Explanation)

    쉽게 말해, 컨테이너 보안 스캐너(Container Security Scanner)는 우리가 만든 도커 이미지(Docker Image) 안에 혹시 모를 취약점(Vulnerability)이나 잘못된 설정(Misconfiguration)이 없는지 꼼꼼히 검사해주는 도구입니다. 운영체제 라이브러리, 애플리케이션 종속성(dependencies), 심지어 설정 파일까지 싹 다 뒤져서 알려진 문제점들을 찾아주죠.

    이런 스캐너가 중요한 이유는 다음과 같거든요:

    • 조기 발견 및 수정(Early Detection & Remediation): 개발 단계에서 미리 취약점을 찾아서 고치면, 나중에 운영 환경에서 문제가 터지는 것보다 훨씬 적은 비용과 노력으로 해결할 수 있어요.
    • 규정 준수(Compliance): 특정 산업군에서는 보안 규정 준수가 필수인데, 스캐너를 활용하면 이런 요구사항을 더 쉽게 만족시킬 수 있거든요.
    • DevSecOps(데브섹옵스) 구현: 개발(Dev), 보안(Sec), 운영(Ops)을 통합하는 DevSecOps 문화에서 보안 스캐닝은 CI/CD 파이프라인에 필수적으로 통합되어야 할 요소입니다.

    Trivy로 컨테이너 이미지 스캔하기 (Hands-on Implementation)

    자, 그럼 제가 홈랩에서 가장 즐겨 쓰는 오픈소스 스캐너인 Trivy(트리비)를 직접 써보면서 어떻게 동작하는지 알아볼까요? Trivy는 Aqua Security에서 만든 오픈소스 취약점 스캐너인데, 빠르고 사용하기 쉽다는 장점 때문에 많은 개발자/운영자들에게 사랑받고 있습니다. 저도 처음엔 ‘이거 정말 공짜 맞아?’ 싶을 정도로 만족하며 쓰고 있거든요. 👍

    1. Trivy 설치

    macOS 기준으로 Homebrew(홈브루)를 사용하면 아주 간단하게 설치할 수 있습니다.

    
    # Trivy 설치 (macOS 예시)
    brew install aquasecurity/trivy/trivy
    
    # 설치 확인
    trivy --version
    # Output 예시:
    # Version: 0.49.1
    # ...
    

    다른 운영체제나 Docker 컨테이너로 실행하는 방법은 Trivy 공식 문서를 참고해주세요!

    2. Docker 이미지 스캔

    이제 설치된 Trivy로 특정 Docker 이미지를 스캔해볼까요? 저는 안정적인 버전의 Nginx(엔진엑스) 이미지를 예시로 사용하겠습니다. `nginx:1.21.6`은 비교적 널리 알려진 버전이라, 학습 데이터 컷오프 이전에도 충분히 존재했던 이미지입니다.

    
    # Nginx 1.21.6 이미지 스캔
    trivy image nginx:1.21.6
    

    명령어를 실행하면 Trivy가 해당 이미지의 레이어들을 분석하고, 발견된 취약점들을 severity(심각도)별로 정리해서 보여줄 겁니다. 처음엔 이게 뭔가 싶었는데, 자세히 보면 CVE(Common Vulnerabilities and Exposures) 번호와 함께 어떤 패키지에 어떤 문제가 있는지, 그리고 권고되는 해결책까지 친절하게 알려줘요. 정말 편하더라고요!

    Trivy 컨테이너 이미지 스캔 결과 예시: 심각도별 취약점 목록과 해결책

    Trivy 스캔 결과는 심각도와 함께 취약점 정보, 해결책을 명확하게 보여줍니다.

    삽질 경험: 너무 많은 경고, 어떻게 다루지? (Troubleshooting & Best Practices)

    Trivy를 처음 써봤을 때, ‘와, 세상에 이렇게 취약점이 많았다고?!’ 하면서 놀랐던 기억이 있습니다. 특히 공식 이미지인데도 CRITICAL(치명적), HIGH(높음) 등급의 경고가 수십 개씩 쏟아져 나오는 걸 보고 멘붕이 오기도 했었죠. 🤯

    여기서 중요한 포인트! 모든 경고를 당장 다 해결할 수는 없습니다. 중요한 건 우선순위(Priority)를 정하고, 우리 서비스에 미치는 영향(Impact)을 분석하는 겁니다.

    1. 미해결 취약점 필터링 (`–ignore-unfixed`)

    간혹 어떤 취약점은 아직 패치(patch)가 나오지 않아 해결할 수 없는 경우가 있습니다. 이런 경우까지 경고로 계속 뜨면 노이즈가 너무 심해져요. 이럴 땐 `–ignore-unfixed` 옵션을 사용해서 아직 수정되지 않은 취약점은 제외하고 볼 수 있습니다.

    2. 심각도별 필터링 (`–severity`)

    모든 MEDIUM(중간)이나 LOW(낮음) 레벨의 취약점을 당장 해결하기는 어렵습니다. 보통은 CRITICAL이나 HIGH 레벨부터 우선 처리하는 게 일반적이거든요. `–severity` 옵션으로 원하는 심각도만 지정해서 볼 수 있어요.

    
    # CRITICAL, HIGH 심각도의 미해결 취약점 제외 스캔
    trivy image --severity HIGH,CRITICAL --ignore-unfixed nginx:1.21.6
    

    이렇게 옵션을 조합해서 사용하면, 정말 중요한 취약점만 집중해서 볼 수 있고, 불필요한 노이즈를 줄여서 실제 액션으로 이어질 가능성이 훨씬 높아지더라고요. 제가 직접 해보니, 이 필터링 작업이 엄청 중요하더라고요. 삽질 좀 했습니다 ㅎㅎ

    스캔 결과 해석 및 후속 조치 (Verification & Results)

    Trivy 스캔 결과는 보통 다음과 같은 정보를 포함합니다.

    • CVE ID: 고유한 취약점 식별 번호 (예: CVE-2023-XXXX)
    • Severity: 취약점의 심각도 (CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN)
    • Package: 취약점이 발견된 패키지 이름 및 버전
    • Installed Version: 현재 이미지에 설치된 패키지 버전
    • Fixed Version: 취약점이 해결된 패키지 버전 (이 버전 이상으로 업데이트해야 함)
    • Title/Description: 취약점에 대한 간략한 설명

    스캔 결과를 보고 나면 어떻게 해야 할까요?

    1. 가장 먼저 Fixed Version 확인: 해당 패키지를 Fixed Version 이상으로 업데이트할 수 있는지 확인하고, Dockerfile(도커파일)을 수정해서 이미지를 다시 빌드(rebuild)합니다.
    2. 영향 분석: 패치 적용이 어렵다면, 해당 취약점이 우리 서비스에 실제로 어떤 영향을 미칠 수 있는지 심층 분석합니다. 예를 들어, 특정 포트가 외부에 노출되지 않는다면 일부 네트워크 관련 취약점은 우선순위가 낮아질 수 있겠죠.
    3. 예외 처리(Suppression): 불가피하게 당장 해결할 수 없거나, 우리 서비스에 영향이 없다고 판단되면, 문서화된 근거를 가지고 해당 취약점을 일시적으로 무시(suppress)할 수 있습니다. 하지만 이는 최후의 수단이며, 지속적으로 재평가해야 합니다.

    이런 과정을 통해 저는 ‘보안은 개발의 한 부분’이라는 것을 다시 한번 깨달았습니다. 단순히 스캔만 하고 끝내는 게 아니라, 결과를 이해하고 적절한 조치를 취하는 것이 중요하더라고요.

    Trivy, Aqua Security, Snyk 컨테이너 보안 솔루션 기능 및 비용 모델 비교 인포그래픽

    세 가지 컨테이너 보안 스캐너의 핵심 기능과 비용 모델을 한눈에 비교할 수 있습니다.

    Trivy, Aqua Security, Snyk: 비용 및 기능 비교 (Cost & Feature Analysis)

    이제 Trivy 외에 다른 상용 솔루션인 Aqua Security와 Snyk는 어떤 특징을 가지고 있는지, 그리고 비용(Cost) 측면에서는 어떻게 다른지 비교해보겠습니다. 이들은 단순 취약점 스캔을 넘어, 더 넓은 범위의 컨테이너 보안(Container Security) 기능을 제공합니다.

    컨테이너 보안 스캐너 주요 솔루션 비교

    제가 직접 사용해보고, 또 주변 동료들의 경험담을 들으면서 느낀 점들을 바탕으로 비교표를 만들어봤습니다.

    항목 Trivy Aqua Security (Aqua Cloud Native Security Platform) Snyk (스닉)
    주 사용 목적 개발/CI/CD 단계의 빠른 취약점 스캔 및 SBOM 생성 엔터프라이즈급 통합 Cloud Native 보안 플랫폼 (스캔, 런타임, 네트워크, 워크로드 보호 등) 개발자 중심의 코드, 종속성, 컨테이너, IaC 보안 및 취약점 자동 수정 제안
    라이선스 오픈소스 (Apache 2.0) 상용 솔루션 (유료 구독) 상용 솔루션 (유료 구독, 제한적 무료 플랜 제공)
    통합 용이성 CLI, Docker, Kubernetes, CI/CD 파이프라인 (GitHub Actions, GitLab CI 등) CI/CD, Kubernetes, Cloud Platform, Registry 등 전방위 통합 IDE, Git Repositories, CI/CD, Container Registries 등 개발자 워크플로우 중심 통합
    스캔 범위 OS 패키지, 프로그래밍 언어 종속성, IaC(Infrastructure as Code), 설정 파일, SBOM 이미지, 컨테이너, 서버리스, Kubernetes, 런타임 환경, 클라우드 자산 등 광범위 코드, 오픈소스 종속성, 컨테이너 이미지, IaC 설정
    부가 기능 SBOM(Software Bill of Materials) 생성, 이미지 서명 검증 런타임 보호, 네트워크 방화벽, KSPM(Kubernetes Security Posture Management), 데이터 보호, 통합 대시보드 및 보고서 자동 취약점 수정 제안, 라이선스 준수 검사, 코드 보안, 개발자 교육 자료
    비용 모델 무료 (오픈소스) 구독 기반 (스캔 대상 이미지 수, 보호하는 노드/워크로드 수, 사용량 기반 등 복합적) 구독 기반 (개발자 수, 월별 테스트 횟수, 프로젝트 수 등 복합적)

    비용 측면에서 보면, Trivy는 오픈소스이므로 직접 운영하는 데 드는 인프라 비용 외에는 무료입니다. 하지만 Aqua Security나 Snyk 같은 상용 솔루션은 기업 규모와 사용량에 따라 상당한 구독료가 발생할 수 있습니다. 예를 들어, 수백 개의 컨테이너 이미지를 관리하고 수십 대의 Kubernetes 노드에서 런타임 보호까지 필요하다면, 연간 수천만 원에서 억대까지도 비용이 들 수 있더라고요. 물론 이 금액은 제품마다, 계약 조건마다 천차만별입니다. 제가 특정 가격을 확언할 수는 없지만, ‘오픈소스와 상용 솔루션의 가격 차이는 크다’는 점은 분명합니다.

    따라서 컨테이너 보안 스캐너를 선택할 때는 단순히 기능만 볼 것이 아니라, 우리 조직의 규모, 예산, 기존 인프라와의 통합 용이성, 그리고 필요한 보안 범위 등을 종합적으로 고려해야 합니다. 무조건 비싼 솔루션이 좋다고 할 수는 없거든요. “우리 팀은 개발 속도가 생명인데, 보안 스캐너 때문에 속도가 느려지면 안 돼!” 같은 고민도 충분히 할 수 있습니다. 이럴 땐 Snyk처럼 개발자 워크플로우에 녹아드는 솔루션이 더 효과적일 수 있고요.

    Trivy, Aqua Security, Snyk 장단점 및 추천 사용 사례 요약 비교

    Trivy, Aqua Security, Snyk 각 솔루션의 장단점과 추천 사용 시나리오를 요약한 비교표입니다.

    마무리: 우리에게 맞는 컨테이너 보안 스캐너는? (Conclusion)

    오늘은 컨테이너 보안의 중요한 축인 Trivy, Aqua Security, Snyk 세 가지 솔루션에 대해 기능과 비용(Cost) 관점에서 자세히 알아봤습니다. 제가 직접 써보고, 삽질하면서 느낀 점들을 솔직하게 공유해드렸는데요.

    결론적으로 어떤 도구를 선택할지는 여러분의 상황에 따라 달라집니다.

    • 작은 프로젝트, 개인 홈랩, 또는 제한된 예산으로 기본적인 취약점 스캔을 시작하고 싶다면? ✅ Trivy가 최고의 선택입니다. CLI 기반으로 가볍게 시작하고, CI/CD에 통합하기도 쉽습니다.
    • 개발 초기 단계부터 보안을 녹여내고 싶고, 개발자 중심의 자동화된 워크플로우와 코드 레벨의 빠른 피드백이 중요하다면? 💡 Snyk를 고려해보세요. IDE 통합과 자동 수정 제안 기능이 개발 생산성을 해치지 않으면서 보안을 강화하는 데 도움을 줄 겁니다.
    • 대규모 엔터프라이즈 환경에서 통합적인 Cloud Native 보안 플랫폼이 필요하고, 취약점 스캔을 넘어 런타임 보호, 네트워크 보안, 규정 준수까지 전방위적인 관리가 필요하다면? 🚀 Aqua Security가 적합합니다. 물론 그만큼 비용 투자는 필요하겠죠.

    어떤 도구를 선택하든 중요한 건, 보안은 한 번 하고 끝나는 게 아니라 지속적인 관심과 개선이 필요하다는 점입니다. 스캐너는 도구일 뿐, 그 결과를 이해하고 적절한 조치를 취하는 것이 가장 중요합니다. 여러분의 컨테이너 환경이 더욱 안전해지기를 바라며, 다음 글에서는 컨테이너 런타임 보안에 대해 더 자세히 다뤄볼게요. 궁금한 점이 있다면 언제든 댓글 남겨주세요! 😉

  • [k8s] GKE WireGuard 보안 취약점 분석: 매니지드 쿠버네티스 보안 강화 전략

    [k8s] GKE WireGuard 보안 취약점 분석: 매니지드 쿠버네티스 보안 강화 전략

    GKE WireGuard 보안 취약점 분석: 매니지드 쿠버네티스 보안 강화 전략

    안녕하세요, 13년차 서버실 지킴이입니다. 요즘 클라우드 환경에서 쿠버네티스(Kubernetes)를 운영하는 분들이 정말 많으시더라고요. 저도 홈랩(Homelab)에서 이것저것 만져보면서 GKE(Google Kubernetes Engine)의 편리함에 푹 빠져 있는데, 이 편리함 뒤에는 우리가 꼭 알아야 할 보안의 그림자가 숨어있다는 걸 아시나요?

    특히 온프레미스(On-premise) 환경이나 다른 클라우드에 있는 시스템과 GKE 클러스터를 안전하게 연결할 때 WireGuard(와이어가드) 같은 VPN(Virtual Private Network)을 많이 쓰는데요. 오늘은 이 GKE와 WireGuard 조합에서 발생할 수 있는 잠재적인 보안 취약점들을 어떻게 분석하고 효과적으로 보안을 강화할 수 있을지 저의 삽질 경험을 바탕으로 솔직하게 풀어보려 합니다. 매니지드 쿠버네티스(Managed Kubernetes)의 보안, 과연 어디까지가 우리의 책임일까요? 함께 파헤쳐 봅시다! 💡

    GKE와 WireGuard, 왜 함께 쓸까요?

    먼저, GKE와 WireGuard가 무엇이고 왜 이 둘을 함께 쓰는지 간단히 짚고 넘어갈게요. 이미 잘 아시는 분들도 계시겠지만, 쉽게 말해드릴게요.

    • GKE (Google Kubernetes Engine): 구글에서 제공하는 매니지드 쿠버네티스 서비스예요. 복잡한 쿠버네티스 클러스터(Cluster) 구축과 운영을 구글이 대신 해주니까, 우리는 애플리케이션 개발에만 집중할 수 있죠. 노드(Node) 관리, 업그레이드, 확장성 등 많은 부분이 자동화되어 있어서 정말 편합니다.
    • WireGuard (와이어가드): 최근 각광받는 오픈소스 VPN 프로토콜이에요. 기존 VPN(예: OpenVPN, IPSec)보다 훨씬 가볍고 빠르면서도 강력한 암호화를 제공하거든요. 설정도 간단해서 저도 홈랩에서 자주 써먹고 있습니다.

    그럼 이 둘을 왜 같이 쓸까요? 주로 이런 시나리오에서 빛을 발합니다:

    • 하이브리드 클라우드(Hybrid Cloud) 환경 구축: 온프레미스 데이터센터나 다른 클라우드에 있는 시스템이 GKE 클러스터 내부의 서비스와 안전하게 통신해야 할 때요.
    • 개발자/운영자 원격 접속: 관리자들이 집이나 외부에서 GKE 클러스터의 프라이빗(Private) 네트워크에 안전하게 접속하여 kubectl(쿠버네티스 커맨드라인 툴) 명령어를 실행하거나 내부 서비스에 접근해야 할 때죠.
    • 보안 강화: GKE 클러스터의 외부 노출을 최소화하고, 특정 허용된 경로(VPN)를 통해서만 접근을 허용하여 전반적인 보안 수준을 높일 때입니다.

    이렇게 들으면 정말 만능 조합 같죠? 근데 여기서부터 우리의 보안 취약점 분석이 시작됩니다.

    GKE와 WireGuard를 연동하여 안전한 접근 경로를 확보하는 일반적인 아키텍처 다이어그램

    GKE와 WireGuard를 연동하여 안전한 접근 경로를 확보하는 일반적인 아키텍처 다이어그램입니다.

    “취약점 분석”의 의미: 잠재적 위험 요소 파악

    제목에 “GKE WireGuard 보안 취약점 분석”이라고 했더니, 혹시 특정 버그(Bug)나 제로데이(Zero-day) 취약점을 떠올리셨나요? 사실 오늘 다룰 내용은 특정 소프트웨어의 버그보다는, GKE와 WireGuard를 연동하는 과정에서 발생할 수 있는 설계적, 설정적 미흡함으로 인한 잠재적 보안 문제들에 대한 분석입니다. 이걸 저는 넓은 의미의 ‘취약점’으로 보고 접근해요.

    매니지드 서비스라고 마냥 안심할 수 없는 이유는 바로 공동 책임 모델 (Shared Responsibility Model) 때문입니다. GKE 자체의 인프라(Infrastructure), 즉 쿠버네티스 컨트롤 플레인(Control Plane)이나 노드 운영체제(Operating System) 등은 구글이 관리하지만, 그 위에서 돌아가는 우리의 워크로드(Workload), 네트워크 구성, 데이터 보안 등은 전적으로 사용자의 책임이거든요. WireGuard 설정 역시 마찬가지예요. 이 경계를 명확히 이해하는 것이 보안 강화의 첫걸음입니다. ⚠️

    WireGuard 구성 시 고려해야 할 보안 취약점

    제가 실제로 GKE에 WireGuard를 연결하면서 “아차!” 했던 부분들을 중심으로 잠재적 취약점들을 짚어볼게요.

    1. 키 관리 (Key Management)의 허술함

    WireGuard는 공개키 암호화(Public-key cryptography)를 사용해요. 각 피어(Peer)마다 고유한 프라이빗 키(Private Key)와 퍼블릭 키(Public Key)를 가지죠. 이 프라이빗 키가 유출된다면 어떻게 될까요? 해당 키를 가진 누구나 VPN을 통해 우리 GKE 네트워크에 접근할 수 있게 됩니다. 홈랩에서는 제가 직접 관리하니 괜찮지만, 여러 팀원이나 시스템이 사용하는 프로덕션(Production) 환경에서는 정말 치명적입니다.

    • 문제점: 프라이빗 키를 코드 저장소에 올리거나, 관리 서버에 평문(Plaintext)으로 저장하는 경우가 있어요.
    • 강화 전략: Google Secret Manager(시크릿 매니저) 같은 안전한 키 관리 서비스(KMS, Key Management Service)를 활용하여 키를 안전하게 보관하고, 필요한 인스턴스(Instance)나 서비스 계정(Service Account)에만 최소한의 권한으로 접근을 허용해야 합니다.

    2. 과도한 접근 제어 (Access Control)

    WireGuard 서버를 설정할 때, `AllowedIPs` 옵션으로 허용할 IP 대역을 지정하죠. “일단 다 열어두고 나중에 좁혀야지” 하는 생각으로 0.0.0.0/0이나 너무 넓은 대역을 설정하면 곤란합니다. GKE 내부의 민감한 서비스까지 접근이 허용될 수 있거든요.

    • 문제점: WireGuard 피어가 GKE 클러스터의 모든 서브넷(Subnet)에 접근 가능하게 설정되는 경우예요.
    • 강화 전략: 최소 권한의 원칙 (Principle of Least Privilege)을 적용해야 해요. 특정 WireGuard 피어는 필요한 GKE 내부 서비스의 IP 대역이나 특정 파드(Pod)의 IP 대역에만 접근할 수 있도록 `AllowedIPs`를 정확하게 지정해야 하니까요.

    3. 네트워크 분리 (Network Segmentation)의 부재

    WireGuard VPN으로 GKE VPC(Virtual Private Cloud)에 연결했다고 해서 모든 것이 끝나는 게 아닙니다. VPN 터널(Tunnel)은 단지 ‘길’을 만들어줄 뿐, 그 ‘길’을 통해 어디까지 갈 수 있는지는 GKE와 GCP(Google Cloud Platform)의 네트워크 규칙이 결정하거든요.

    • 문제점: WireGuard 서버가 위치한 서브넷과 GKE 클러스터 서브넷 간의 방화벽 규칙(Firewall Rule)이 너무 느슨하거나, GKE 내부의 네트워크 정책(Network Policy)이 없는 경우를 말해요.
    • 강화 전략: Shared VPC(공유 VPC)를 활용하여 네트워크를 중앙에서 관리하고, GCP 방화벽 규칙을 통해 WireGuard 서버에서 GKE 클러스터로 들어오는 트래픽을 세밀하게 제어해야 합니다. 예를 들어, 특정 포트(Port)와 프로토콜(Protocol)만 허용하는 식이죠. 또한, GKE 내부에서는 쿠버네티스 네트워크 정책(Kubernetes Network Policies)을 사용하여 파드 간 통신을 제어해야 한답니다.

    4. 모니터링 및 로깅 (Monitoring & Logging)의 부족

    아무리 보안을 강화해도 사고는 발생할 수 있어요. 중요한 건 사고 발생 시 얼마나 빠르게 인지하고 대응하느냐죠. WireGuard 트래픽이나 GKE 내부 네트워크 트래픽에 대한 가시성(Visibility)이 없다면 문제가 생겨도 알 수가 없습니다.

    • 문제점: WireGuard 서버의 접속 로그(Log)나 GKE의 감사 로깅(Audit Logging)을 제대로 설정하지 않거나, 모니터링 시스템과 연동하지 않는 경우가 많아요.
    • 강화 전략: WireGuard 서버의 접속 및 트래픽 로그를 Cloud Logging(클라우드 로깅)으로 연동하고, GKE의 Cloud Audit Logs(클라우드 감사 로그)를 통해 클러스터 내의 모든 활동을 기록해야 해요. Cloud Monitoring(클라우드 모니터링)으로 관련 지표를 대시보드(Dashboard)에 띄우고, 비정상적인 활동에 대한 알림(Alert)을 설정하는 것이 필수입니다.

    GKE 환경에서 WireGuard 보안 강화 전략 (실전 팁)

    그럼 이제 위에서 언급한 취약점들을 보완하면서 실제로 어떻게 GKE와 WireGuard의 보안을 강화할 수 있는지 제가 사용해본 몇 가지 실전 팁을 공유해드릴게요.

    1. 프라이빗 GKE 클러스터 (Private GKE Cluster) 활용

    GKE 클러스터의 마스터(Master)와 노드(Node) 모두 프라이빗 IP 주소만 가지도록 설정하여 인터넷에 노출되지 않게 하는 것이 가장 강력한 보안 조치 중 하나예요. WireGuard VPN을 통해서만 접근 가능하게 만들 수 있거든요.

    
    gcloud container clusters create my-private-gke \
        --region=asia-east1 \
        --enable-private-nodes \
        --enable-private-endpoint \
        --master-ipv4-cidr=172.16.0.0/28 \
        --create-subnetwork name=my-gke-subnet,range=10.10.0.0/20
    

    이렇게 하면 외부에서는 프라이빗 IP로만 접근할 수 있기 때문에, WireGuard VPN을 통해 VPC 네트워크에 연결된 경우에만 GKE API 서버에 접근할 수 있어요. 🔒

    2. Shared VPC(공유 VPC)와 세분화된 방화벽 규칙

    WireGuard 서버는 별도의 서브넷에 두거나, 아예 다른 GCP 프로젝트(Project)의 Shared VPC에 위치시켜 GKE 클러스터와 네트워크를 분리하는 것을 권장합니다. 그리고 반드시 필요한 트래픽만 허용하는 방화벽 규칙을 적용해야 해요.

    
    gcloud compute firewall-rules create allow-wireguard-to-gke-api \
        --network=my-shared-vpc \
        --allow=tcp:443 \
        --source-ranges=YOUR_WIREGUARD_SERVER_IP/32 \
        --target-tags=gke-master-api-endpoint
    
    gcloud compute firewall-rules create allow-wireguard-to-gke-nodes \
        --network=my-shared-vpc \
        --allow=tcp:10250,tcp:30000-32767 \
        --source-ranges=YOUR_WIREGUARD_SERVER_IP/32 \
        --target-tags=gke-node
    

    위 예시처럼 WireGuard 서버 IP에서 GKE 마스터 API(tcp:443)와 노드(tcp:10250, NodePort 범위)로만 접근을 허용하는 방화벽 규칙을 만들 수 있어요. `YOUR_WIREGUARD_SERVER_IP` 부분은 실제 WireGuard 서버의 IP 주소로 바꿔주세요.

    GCP 콘솔에서 GKE와 WireGuard 간의 통신을 제어하는 방화벽 규칙을 설정하는 화면 예시

    GCP 콘솔에서 GKE와 WireGuard 간의 통신을 제어하는 방화벽 규칙을 설정하는 화면 예시입니다.

    3. Kubernetes Network Policies(네트워크 정책) 활용

    WireGuard VPN을 통해 GKE 내부 네트워크에 접근하더라도, 클러스터 내부의 파드 간 통신은 네트워크 정책으로 다시 한번 제어해야 합니다. 특정 네임스페이스(Namespace)나 파드에만 접근을 허용하는 식으로 말이에요. 이건 GKE 내부 보안의 핵심이거든요.

    
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-wireguard-access-to-backend
      namespace: default
    spec:
      podSelector:
        matchLabels:
          app: backend-service
      policyTypes:
        - Ingress
      ingress:
        - from:
            - ipBlock:
                cidr: 10.128.0.0/14  # GKE 클러스터의 Pod CIDR 범위 (WireGuard 서버에서 접근할 수 있는 IP 대역)
            - namespaceSelector:
                matchLabels:
                  name: wireguard-client-namespace # WireGuard 클라이언트 Pod가 있다면 해당 네임스페이스
          ports:
            - protocol: TCP
              port: 8080
    

    이 정책은 `backend-service` 레이블을 가진 파드가 특정 IP 대역(WireGuard를 통해 접근하는 클라이언트 IP 범위) 또는 특정 네임스페이스의 파드로부터 8080 포트로만 트래픽을 허용하도록 한답니다.

    WireGuard 키를 안전하게 생성하고 Secret Manager를 통해 관리하는 프로세스 다이어그램

    WireGuard 키를 안전하게 생성하고 Secret Manager를 통해 관리하는 프로세스 다이어그램입니다.

    삽질 경험: “나는 괜찮겠지” 하다가 겪은 일

    저도 처음엔 “GKE는 구글이 관리해주니까 보안은 기본으로 되겠지? WireGuard는 빠르고 가볍다니까 그냥 연결만 하면 되겠지!” 하는 안일한 생각으로 시작했었어요. 홈랩에서 대충 WireGuard 서버 하나 띄워서 GKE VPC에 연결하고 `AllowedIPs = 0.0.0.0/0` 해놓고 썼습니다. 처음엔 잘 되더라고요. 🎉

    근데 어느 날, 개발자 한 분이 “특정 마이크로서비스(Microservice)에만 접속해서 디버깅(Debugging)해야 하는데, 뭔가 불안해요”라는 피드백을 주시더라고요. 그때 정신이 번쩍 들었습니다. WireGuard VPN을 통해 들어오면 마치 데이터센터 안에 있는 것처럼 GKE 클러스터의 모든 파드와 서비스에 접근이 가능한 상태였던 거죠. 😱

    이거 진짜 삽질 좀 했습니다 ㅎㅎ. 처음엔 방화벽 규칙을 좁혔는데, GKE 노드들이 서로 통신을 못 해서 클러스터가 깨지는 일도 있었고요. Network Policy를 적용하는 과정에서도 파드 셀렉터(Pod Selector)를 잘못 지정해서 멀쩡한 서비스가 통신 불가가 되는 상황도 겪었죠. 결국은 GCP 방화벽 규칙과 K8s 네트워크 정책을 촘촘하게, 그리고 단계적으로 적용하면서 테스트하고 또 테스트하는 과정을 거쳤어요. 이 과정에서 최소 권한의 원칙과 네트워크 분리의 중요성을 뼈저리게 느꼈습니다. 💡

    결과 및 검증

    위에 설명드린 보안 강화 전략들을 적용하고 나면, 반드시 검증 과정을 거쳐야 해요. 저는 주로 다음과 같은 방법으로 확인했습니다.

    • 접근 테스트: WireGuard VPN을 통해 접속한 후, 허용되지 않은 IP 대역이나 포트로 접근을 시도하여 차단되는지 확인합니다.
    • GCP Security Command Center(보안 명령 센터): GKE Security Posture(보안 상태) 대시보드를 통해 클러스터의 전반적인 보안 상태를 점검하고, 취약점이 없는지 확인했어요.
    • 로깅 확인: Cloud Logging에서 WireGuard 서버 로그와 GKE 감사 로그를 확인하여 비정상적인 접근 시도나 차단 기록이 잘 남는지 확인해요.

    이 과정을 통해 “아, 이제야 좀 안심하고 쓸 수 있겠구나” 하는 생각이 들더라고요. ✅

    GCP Security Command Center의 GKE Security Posture 대시보드를 통해 클러스터의 보안 상태를 점검하는 화면 예시

    GCP Security Command Center의 GKE Security Posture 대시보드를 통해 클러스터의 보안 상태를 점검하는 화면 예시입니다.

    마무리: 결국 보안은 지속적인 관리입니다

    오늘은 GKE와 WireGuard를 연동할 때 발생할 수 있는 잠재적인 보안 취약점들을 분석하고, 이를 강화하기 위한 전략들을 저의 경험을 바탕으로 이야기해봤습니다.

    핵심은 이거예요. 매니지드 서비스라고 해도 ‘내 책임’ 영역은 분명히 존재한다는 점, 그리고 그 영역에서 우리는 ‘최소 권한의 원칙’과 ‘네트워크 분리’를 철저히 지켜야 한다는 것. WireGuard 키 관리부터 시작해서 GCP 방화벽, K8s 네트워크 정책까지, 겹겹이 보안을 쌓아 올리는 것이 중요합니다.

    보안은 한 번 설정해두면 끝나는 게 아니라, 지속적인 관심과 관리가 필요해요. 정기적인 구성 감사, 취약점 스캔, 그리고 최신 보안 패치 적용을 게을리하지 마세요. 우리 서버실의 평화를 위해서 말이에요! 🛡️

    다음번엔 GKE 보안 모범 사례를 좀 더 깊게 파고들어볼까 합니다. 기대해주세요!

    GKE WireGuard 보안 강화 전략의 주요 내용을 요약한 인포그래픽입니다.

  • [k8s] 쿠버네티스 Secret 관리 베스트 프랙티스: 민감 데이터 보안 강화 전략

    [k8s] 쿠버네티스 Secret 관리 베스트 프랙티스: 민감 데이터 보안 강화 전략

    도입부: 민감 데이터, 이렇게 방치해도 될까요?

    안녕하세요, 13년차 서버실 지킴이입니다. 쿠버네티스(Kubernetes)를 운영하다 보면 정말 많은 난관에 부딪히게 되죠. 그중에서도 민감 데이터(Sensitive Data) 관리는 늘 머리 아픈 숙제였어요. 저도 처음엔 별생각 없이 Secret 리소스를 사용했었는데, 시간이 지날수록 ‘이대로 괜찮을까?’ 하는 불안감이 커지더라고요. 개발자들이 접속 정보, API 키 같은 걸 그냥 Secret에 넣고 쓰는 모습을 보면서 ‘언젠가 터지겠구나’ 싶었거든요.

    실제로 쿠버네티스 환경에서 Secret 관리가 제대로 되지 않아 보안 사고로 이어진 사례도 심심치 않게 들려옵니다. 외부 공격은 물론이고, 내부에서도 불필요한 접근 권한 때문에 문제가 생기기도 하고요. 그래서 오늘은 제가 13년간 인프라 엔지니어로 일하며 겪었던 경험과 홈랩에서 직접 실험해본 것들을 바탕으로, 쿠버네티스 Secret 관리 베스트 프랙티스 체크리스트를 공유해드리려고 합니다. 민감 데이터를 안전하게 보호하기 위한 전략들을 함께 알아볼까요?

    쿠버네티스 클러스터 내 Secret 관리의 중요성을 보여주는 개요 다이어그램

    쿠버네티스 클러스터 내 Secret 관리의 중요성을 보여주는 개요 다이어그램입니다.

    쿠버네티스 Secret, 넌 누구니? (개념 설명)

    먼저, 쿠버네티스 Secret이 정확히 무엇인지부터 짚고 넘어갈게요. 쿠버네티스 Secret(시크릿)은 말 그대로 민감한 정보(Sensitive Information)를 저장하기 위한 오브젝트입니다. 데이터베이스 암호, OAuth 토큰, SSH 키 등을 여기에 저장하고 파드(Pod)에 주입해서 사용하죠. 흔히 환경 변수나 파일 형태로 파드에 마운트(Mount)해서 애플리케이션이 접근하도록 합니다.

    쉽게 말해, 컨테이너 이미지에 직접 민감 정보를 하드코딩(Hard-coding)하거나 YAML 매니페스트에 평문(Plaintext)으로 노출하는 대신, 쿠버네티스 자체적으로 제공하는 저장소를 이용하는 방식이라고 보시면 됩니다. 그런데 여기서 중요한 포인트! 쿠버네티스 Secret은 기본적으로 Base64(베이스64) 인코딩만 되어 있을 뿐, 암호화(Encryption)되어 있지 않습니다. 이 말은 인코딩된 문자열을 누구나 쉽게 디코딩해서 원본 값을 볼 수 있다는 뜻이에요. 이걸 모르고 Secret이 완전히 안전하다고 생각했다간 큰코다칠 수 있습니다.

    Secret 관리, 어떤 점이 문제일까요? (삽질 경험 공유)

    저도 처음엔 ‘그래도 쿠버네티스에서 제공하는 거니까 안전하겠지!’ 하고 막연하게 생각했었어요. 그래서 개발팀에서 요청하는 대로 DB 접속 정보나 외부 API 키를 kubectl create secret generic 명령어로 뚝딱 만들어서 배포했었죠. 그러다가 문득 kubectl get secret <secret-name> -o yaml 명령어를 쳐봤는데, 인코딩된 값들이 너무 쉽게 디코딩되는 걸 보고 깜짝 놀랐습니다. ‘아, 이건 아니구나’ 싶더라고요. 사실 Secret의 가장 큰 문제점은 다음과 같습니다.

    • Base64 인코딩은 암호화가 아니다: 위에서 설명했듯이, 누구나 쉽게 원본 값을 볼 수 있다는 점이 가장 치명적입니다.
    • etcd(엣시디)에 평문 저장 가능성: 쿠버네티스의 모든 오브젝트는 etcd라는 분산 키-값 저장소에 저장됩니다. etcd 자체가 암호화되어 있지 않다면, Secret 내용이 그대로 노출될 수 있습니다.
    • 접근 제어의 복잡성: Secret에 대한 접근 권한을 세밀하게 제어하지 않으면, 불필요한 사용자나 파드가 민감 정보에 접근할 수 있게 됩니다. RBAC(Role-Based Access Control) 설정을 잘못하면 문제가 생기죠.
    • GitOps(깃옵스) 환경과의 충돌: Secret을 Git 리포지토리에 저장하고 싶은데, 평문으로 올릴 수는 없잖아요? 그렇다고 인코딩된 값을 올리는 것도 보안상 좋지 않습니다. 버전 관리와 보안 사이에서 딜레마에 빠지게 됩니다.

    이런 문제점들 때문에 제가 밤늦게까지 홈랩에서 씨름하며 여러 솔루션을 찾아보고 적용해봤었죠. 삽질 좀 했습니다 ㅎㅎ

    민감 데이터 보안 강화 전략 체크리스트 (베스트 프랙티스)

    이제 본격적으로 쿠버네티스 Secret 관리 베스트 프랙티스 체크리스트를 공유해드릴게요. 이 전략들을 하나씩 적용해나가시면 훨씬 안전한 쿠버네티스 환경을 만들 수 있을 겁니다.

    1. etcd 암호화 (Encryption at Rest)

    가장 기본 중의 기본입니다. 쿠버네티스 클러스터의 핵심 데이터 저장소인 etcd에 저장되는 모든 데이터를 암호화해야 합니다. 특히 Secret 오브젝트는 반드시 암호화해야 합니다. 이를 Encryption at Rest(저장 데이터 암호화)라고 부르는데요, kube-apiserver(쿠브-API서버) 설정을 통해 Secret 리소스만 선택적으로 암호화할 수 있습니다.

    2. RBAC(Role-Based Access Control)으로 Secret 접근 제어

    누가 어떤 Secret에 접근할 수 있는지 RBAC를 통해 세밀하게 제어해야 합니다. 최소 권한 원칙(Principle of Least Privilege)을 적용하여, 꼭 필요한 사용자나 파드에게만 필요한 Secret에 대한 읽기 권한을 부여해야 합니다. 예를 들어, 특정 네임스페이스(Namespace)의 파드만 해당 네임스페이스의 Secret을 사용할 수 있도록 제한하는 것이죠.

    3. 외부 Secret 관리 솔루션 활용

    쿠버네티스 기본 Secret의 한계를 보완하기 위해 외부 Secret 관리 솔루션을 적극적으로 고려해보세요. 제가 홈랩에서도 여러 솔루션을 테스트해봤는데, 정말 편하더라고요.

    • HashiCorp Vault(하시코프 볼트): 중앙 집중식 Secret 관리 솔루션의 대명사입니다. 동적 Secret 생성, Secret 리스(Lease), 감사 로그(Audit Log) 등 강력한 기능을 제공합니다. 쿠버네티스와의 연동도 잘 되어 있어서 많은 기업에서 사용하고 있습니다.
    • Sealed Secrets(실드 시크릿): Bitnami에서 개발한 솔루션으로, Secret을 암호화하여 Git 리포지토리에 안전하게 저장할 수 있게 해줍니다. 클러스터 내의 컨트롤러(Controller)가 암호화된 SealedSecret을 복호화하여 일반 Secret으로 만들어줍니다. GitOps 환경에서 특히 유용하죠.
    • 클라우드 제공 Secret Manager: AWS Secrets Manager, Google Secret Manager, Azure Key Vault 등 클라우드 서비스에서 제공하는 Secret 관리 솔루션을 활용하는 것도 좋은 방법입니다.
    쿠버네티스와 외부 Secret 관리 솔루션(Vault, Sealed Secrets) 연동 아키텍처 예시입니다.

    쿠버네티스와 외부 Secret 관리 솔루션(Vault, Sealed Secrets) 연동 아키텍처 예시입니다.

    4. Kubelet Secret 볼륨 암호화 (KMS Provider)

    파드에 마운트(Mount)되는 Secret 볼륨은 tmpfs(임시 파일 시스템)에 저장되지만, 만약 Kubelet(쿠블릿) 노드가 탈취당했을 경우 메모리 덤프 등을 통해 Secret에 접근할 위험이 있습니다. 클라우드 환경에서는 KMS(Key Management Service) Provider를 활용하여 Secret 볼륨을 암호화할 수 있습니다. 예를 들어 AWS EKS나 GCP GKE에서는 KMS를 etcd 암호화뿐만 아니라 Secret 볼륨 암호화에도 활용할 수 있습니다.

    5. Secret 변경 시 애플리케이션 재시작/재배포

    쿠버네티스 Secret은 파드에 환경 변수나 볼륨으로 주입되는데, Secret의 내용이 변경되어도 파드는 자동으로 업데이트된 Secret을 로드하지 않습니다. 즉, 변경 사항을 적용하려면 해당 파드를 재시작(Restart)하거나 재배포(Redeploy)해야 합니다. 이 점을 잊고 ‘왜 Secret 바꿨는데 적용이 안 되지?’ 하고 저처럼 삽질하는 일이 없으시길 바랍니다. Deployment(디플로이먼트)의 Hash(해시) 기반 롤링 업데이트(Rolling Update)를 활용하거나, Reloader 같은 툴을 사용하면 좀 더 자동화할 수 있습니다.

    6. Secret Rotation (정기적 교체)

    아무리 강력하게 암호화하고 접근 제어를 해도, Secret이 영원히 안전하다고 보장할 수는 없습니다. 따라서 Secret을 정기적으로 교체(Rotation)하는 정책을 수립하고 자동화하는 것이 중요합니다. 예를 들어 데이터베이스 암호는 3개월마다, API 키는 6개월마다 교체하는 식이죠. 외부 Secret 관리 솔루션들은 이러한 Secret Rotation 기능을 자체적으로 제공하기도 합니다.

    실전 적용 가이드: etcd 암호화와 Sealed Secrets 예시

    이론만으로는 부족하죠! 제가 직접 적용해봤던 etcd 암호화와 Sealed Secrets의 간단한 예시를 보여드릴게요.

    etcd 암호화 설정

    kube-apiserver 설정을 수정하여 etcd에 저장되는 Secret을 암호화할 수 있습니다. EncryptionConfiguration API를 사용하는데, AES-CBC(고급 암호화 표준-Cipher Block Chaining) 방식이 널리 사용됩니다.

    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
          - secrets
        providers:
          - aescbc:
              keys:
                - secretbox:
                    secret: <YOUR_AES_KEY_BASE64> # Base64 인코딩된 32바이트 AES 키
          - identity: {}
          - secretbox: {}
    

    여기서 <YOUR_AES_KEY_BASE64>는 직접 생성한 AES 키를 Base64 인코딩한 값입니다. 이 키는 안전하게 보관해야 합니다. 이 설정 파일을 kube-apiserver에 적용하면, 이후 생성되는 Secret들은 모두 etcd에 암호화된 상태로 저장됩니다. 기존 Secret들도 재작성(rewrite)해야 암호화가 적용됩니다.

    Sealed Secrets 도입

    Sealed Secrets는 GitOps 환경에서 Secret을 안전하게 관리하기 위한 좋은 방법입니다. 먼저 클러스터에 Sealed Secrets 컨트롤러를 설치합니다.

    # 컨트롤러 설치 (helm 사용 예시)
    helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
    helm repo update
    helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system
    
    # public key 추출
    kubeseal --fetch-cert > public-cert.pem
    

    그다음, 일반 Secret YAML 파일을 kubeseal CLI 툴을 사용하여 암호화합니다.

    # original-secret.yaml (평문 Secret 예시)
    apiVersion: v1
    kind: Secret
    metadata:
      name: my-app-secret
      namespace: default
    data:
      username: dXNlcg==
      password: cGFzcw==
    type: Opaque
    
    # Secret 암호화
    cat original-secret.yaml | kubeseal --cert public-cert.pem --format yaml > sealed-secret.yaml
    

    이렇게 생성된 sealed-secret.yaml 파일은 암호화되어 Git 리포지토리에 안전하게 저장할 수 있습니다. 클러스터 내의 Sealed Secrets 컨트롤러가 이 파일을 감지하고 자동으로 복호화하여 일반 Secret으로 만들어줍니다.

    Sealed Secrets의 작동 원리를 보여주는 시퀀스 다이어그램입니다.

    Sealed Secrets의 작동 원리를 보여주는 시퀀스 다이어그램입니다.

    제가 겪은 트러블슈팅: 권한 문제와 재배포

    이런 설정들을 적용하다 보면 예상치 못한 문제에 부딪히기 마련이죠. 제가 가장 많이 겪었던 ‘삽질’은 바로 RBAC 권한 문제였습니다. Secret에 대한 접근 권한을 너무 강하게 제한했더니, 정작 애플리케이션 파드가 Secret을 읽지 못해서 배포가 실패하는 경우가 많았어요. 에러 메시지에 permission denied나 secret not found가 뜨면 정말 머리아파죠.

    해결책은 간단했습니다. Role과 RoleBinding을 세밀하게 조정하는 것이죠. 특정 네임스페이스의 ServiceAccount(서비스 계정)에 해당 네임스페이스 내의 Secret에 대한 get, list, watch 권한만 부여하도록 했습니다. 그리고 kubectl auth can-i get secrets --as=system:serviceaccount:<namespace>:<serviceaccount-name> 명령어로 실제 권한을 꼼꼼히 확인하는 습관을 들였어요.

    또 하나는 위에서 언급했던 Secret 변경 후 파드 재배포 문제였습니다. Secret만 바꾸고 파드를 재시작하지 않아서 한참을 헤매다가 결국 kubectl rollout restart deployment <deployment-name> 명령어를 날려야 해결되는 걸 보고 허탈했던 기억이 있네요. 자동화 툴을 사용하기 전까지는 배포 스크립트에 반드시 재시작 로직을 포함시키는 것이 중요합니다.

    마무리하며: 안전한 쿠버네티스 운영을 위한 필수 요소

    오늘은 쿠버네티스 Secret 관리의 중요성과 함께, 제가 직접 겪고 배운 민감 데이터 보안 강화 전략 체크리스트를 공유해드렸습니다. 사실 쿠버네티스 Secret은 그 자체로 완벽한 보안 솔루션이라기보다는, 다른 보안 도구들과 함께 사용될 때 비로소 제 역할을 하는 퍼즐 조각이라고 생각합니다.

    정리하자면:

    1. etcd 암호화는 기본 중의 기본!
    2. RBAC로 Secret 접근 권한을 꼼꼼히 관리하기.
    3. Sealed Secrets나 Vault 같은 외부 솔루션으로 보안 강화 및 GitOps 환경 통합.
    4. KMS Provider를 활용한 Secret 볼륨 암호화 고려.
    5. Secret 변경 시 파드 재시작/재배포 잊지 말기.
    6. Secret Rotation으로 주기적인 보안 강화.

    이 체크리스트를 바탕으로 여러분의 쿠버네티스 환경이 더욱 안전해지기를 바랍니다. 민감 데이터는 언제나 최우선으로 보호해야 할 대상이니까요. 다음 글에서는 특정 외부 Secret 관리 솔루션을 더 깊이 파고드는 내용을 다뤄볼까 합니다. 기대해주세요!

    안전한 쿠버네티스 Secret 관리를 위한 핵심 체크리스트 요약 인포그래픽입니다.

    안전한 쿠버네티스 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에 대해 더 깊이 다뤄볼 예정이니, 많은 관심 부탁드립니다!

  • [k8s] 쿠버네티스 보안 강화: 베스트 프랙티스 가이드

    쿠버네티스 보안, 왜 지금 더 중요해졌나요?

    솔직히 말씀드리면, 저도 처음 쿠버네티스를 도입했을 때는 “일단 돌아가면 됐지”라는 마음이 컸어요. Pod가 뜨고, 서비스가 노출되고, 배포 파이프라인이 연결되면 그걸로 충분하다고 생각했거든요. 근데 운영 2년차쯤 됐을 때 실제로 클러스터 하나가 크립토마이닝 공격에 당하는 걸 목격하고 나서… 그때부터 쿠버네티스 보안을 진지하게 공부하기 시작했습니다.

    최근 들어 클라우드 네이티브 환경이 보편화되면서 쿠버네티스(Kubernetes) 클러스터를 겨냥한 공격도 눈에 띄게 늘었어요. 잘못 설정된 RBAC(Role-Based Access Control, 역할 기반 접근 제어), 노출된 API 서버, 권한 과잉 서비스 계정… 이런 것들이 실제로 침해 사고로 이어지는 경우가 많더라고요.

    이 글에서는 제가 13년간 인프라를 운영하면서 직접 적용해보고, 실제로 효과를 확인한 쿠버네티스 보안 베스트 프랙티스를 정리해드릴게요. 개념만 나열하는 게 아니라 실제 적용 가능한 YAML 설정과 명령어도 함께 다룰 겁니다.

    ▲ 쿠버네티스 클러스터의 주요 보안 레이어 — API 서버, RBAC, 네트워크 정책, Pod 보안이 어떻게 계층적으로 동작하는지 보여주는 아키텍처 다이어그램


    쿠버네티스 보안의 핵심 개념 먼저 짚고 가기

    쿠버네티스 보안은 크게 4가지 영역으로 나뉘어요. 흔히 “4C 보안 모델”이라고 부르는데, 쉽게 말해 Cloud → Cluster → Container → Code 순서로 각 레이어를 지켜야 한다는 개념이에요.

    레이어 설명 주요 관리 포인트
    Cloud 클라우드 인프라 수준 보안 IAM, VPC, 방화벽, 노드 OS 보안
    Cluster 쿠버네티스 클러스터 수준 보안 API 서버 접근 제어, RBAC, Audit 로깅
    Container 컨테이너 런타임 수준 보안 이미지 취약점 스캔, securityContext 설정
    Code 애플리케이션 코드 수준 보안 시크릿 관리, TLS 통신, 의존성 취약점

    이 중에서 Cluster와 Container 레이어가 실제로 가장 많이 놓치는 부분이에요. Cloud는 CSP(클라우드 서비스 제공자)가 어느 정도 기본 보호를 해주고, Code는 개발팀이 챙기는 경우가 많은데, 정작 클러스터 내부 설정은 인프라팀도 개발팀도 서로 남의 일이라고 생각하는 경우가 많거든요. 제가 딱 그랬었어요 ㅎㅎ.


    RBAC 설정: 최소 권한 원칙을 철저히 지켜라

    RBAC(Role-Based Access Control)는 쿠버네티스 보안의 핵심 중 핵심입니다. “필요한 권한만, 필요한 리소스에만”이라는 최소 권한 원칙(Principle of Least Privilege)을 RBAC로 구현하는 거예요.

    흔히 보이는 실수가 뭔지 아세요? 빠르게 개발하려고 ServiceAccount에 cluster-admin 권한을 줘버리는 거예요. 저도 초반에 “일단 개발 환경이니까”라며 그냥 넘어간 적이 있는데, 그 습관이 프로덕션까지 이어지면 진짜 큰일 납니다.

    나쁜 예시 — 절대 이렇게 하지 마세요

    # ⚠️ 절대 금지: 모든 권한을 서비스 계정에 부여
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: dangerous-binding
    subjects:
    - kind: ServiceAccount
      name: my-app
      namespace: default
    roleRef:
      kind: ClusterRole
      name: cluster-admin  # 이거 쓰면 안 됩니다!
      apiGroup: rbac.authorization.k8s.io
    

    좋은 예시 — 최소 권한으로 Role 정의하기

    # ✅ 권장: 특정 네임스페이스의 특정 리소스에만 접근 허용
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      namespace: production
      name: pod-reader
    rules:
    - apiGroups: [""]  # core API 그룹
      resources: ["pods", "pods/log"]
      verbs: ["get", "list", "watch"]  # 읽기 전용만 허용
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: read-pods
      namespace: production
    subjects:
    - kind: ServiceAccount
      name: monitoring-agent
      namespace: production
    roleRef:
      kind: Role
      name: pod-reader
      apiGroup: rbac.authorization.k8s.io
    

    💡 팁: 현재 클러스터에서 과도한 권한이 있는 바인딩을 빠르게 확인하려면 아래 명령어를 써보세요.

    # cluster-admin 권한을 가진 바인딩 전체 조회
    kubectl get clusterrolebindings -o json | \
      jq '.items[] | select(.roleRef.name == "cluster-admin") | .metadata.name'
    
    # 특정 서비스 계정의 권한 확인
    kubectl auth can-i --list --as=system:serviceaccount:production:my-app
    

    네트워크 정책으로 Pod 간 통신 제어하기

    기본적으로 쿠버네티스 클러스터 내 모든 Pod는 서로 통신할 수 있어요. 이게 편리하긴 한데, 보안 관점에서는 꽤 위험한 기본값이에요. 만약 하나의 Pod가 침해당하면 클러스터 내 모든 서비스로 횡적 이동(Lateral Movement)이 가능하거든요.

    NetworkPolicy(네트워크 정책)는 Pod 간 또는 Pod와 외부 간의 트래픽을 화이트리스트 방식으로 제어하는 k8s 리소스예요. 처음엔 이게 뭔가 복잡해 보였는데, 개념만 잡으면 생각보다 직관적이더라고요.

    기본 원칙: 먼저 모든 트래픽 차단 후 필요한 것만 열기

    # 1단계: 특정 네임스페이스의 모든 인바운드/아웃바운드 트래픽 차단
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: default-deny-all
      namespace: production
    spec:
      podSelector: {}  # 네임스페이스 내 모든 Pod에 적용
      policyTypes:
      - Ingress
      - Egress
    
    # 2단계: 필요한 트래픽만 허용 (예: frontend -> backend 통신)
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-frontend-to-backend
      namespace: production
    spec:
      podSelector:
        matchLabels:
          app: backend  # 이 정책이 적용될 Pod
      policyTypes:
      - Ingress
      ingress:
      - from:
        - podSelector:
            matchLabels:
              app: frontend  # frontend Pod에서 오는 트래픽만 허용
        ports:
        - protocol: TCP
          port: 8080
    

    ⚠️ 주의사항: NetworkPolicy는 CNI(Container Network Interface) 플러그인이 지원해야 동작해요. Calico, Cilium, Weave Net 등은 지원하지만, 기본 flannel은 NetworkPolicy를 지원하지 않아요. 클러스터 구성 전에 꼭 확인하세요!

    ▲ NetworkPolicy 적용 전후 Pod 간 트래픽 흐름 비교 — 기본 개방 상태와 화이트리스트 방식 적용 후 허용된 경로만 표시된 다이어그램


    컨테이너 보안 컨텍스트 설정하기

    컨테이너 보안에서 제일 많이 놓치는 부분이 바로 SecurityContext(시큐리티 컨텍스트)예요. 이 설정 하나만 제대로 해도 컨테이너 탈출(Container Escape) 공격을 상당 부분 방어할 수 있거든요.

    제가 보안 감사를 해드린 한 팀의 클러스터를 보니까, 거의 모든 Pod가 root 권한으로 실행되고 있었어요. 개발 편의를 위해 그렇게 설정했다고 하는데, 그건 컨테이너 안에서 root 권한을 얻은 공격자가 호스트 노드까지 공격할 수 있는 발판을 주는 거나 마찬가지예요.

    apiVersion: v1
    kind: Pod
    metadata:
      name: secure-pod
      namespace: production
    spec:
      securityContext:
        runAsNonRoot: true          # root로 실행 금지
        runAsUser: 1000             # 특정 UID로 실행
        runAsGroup: 3000            # 특정 GID로 실행
        fsGroup: 2000               # 볼륨 파일시스템 그룹
        seccompProfile:
          type: RuntimeDefault      # 기본 seccomp 프로파일 적용
      containers:
      - name: app
        image: my-app:1.0.0
        securityContext:
          allowPrivilegeEscalation: false  # 권한 상승 금지 (핵심!)
          readOnlyRootFilesystem: true     # 루트 파일시스템 읽기 전용
          capabilities:
            drop:
            - ALL                          # 모든 Linux 기능(capability) 제거
            add:
            - NET_BIND_SERVICE             # 필요한 것만 다시 추가
        resources:
          limits:
            cpu: "500m"
            memory: "128Mi"
          requests:
            cpu: "250m"
            memory: "64Mi"
    

    💡 핵심 포인트! allowPrivilegeEscalation: false는 반드시 설정하세요. 이 설정이 없으면 컨테이너 내부에서 sudo나 setuid 바이너리를 통해 권한 상승이 가능해요.

    Pod Security Admission으로 클러스터 전체에 정책 적용하기

    쿠버네티스 1.25부터 기존의 PodSecurityPolicy(PSP)가 제거되고 Pod Security Admission(PSA)이 정식 출시됐어요. 네임스페이스 레벨에서 보안 프로파일을 강제할 수 있는데, 세 가지 레벨이 있어요.

    레벨 설명 적용 권장 환경
    privileged 제한 없음 (기본값) 시스템 네임스페이스 (kube-system 등)
    baseline 최소한의 보안 제한 적용 일반 애플리케이션 네임스페이스
    restricted 가장 강력한 보안 제한 보안이 중요한 프로덕션 환경
    # 네임스페이스에 PSA 레벨 적용 (enforce: 위반 시 Pod 생성 거부)
    kubectl label namespace production \
      pod-security.kubernetes.io/enforce=restricted \
      pod-security.kubernetes.io/enforce-version=latest
    
    # warn: 경고만 (생성은 허용), audit: 감사 로그만 기록
    kubectl label namespace staging \
      pod-security.kubernetes.io/warn=baseline \
      pod-security.kubernetes.io/audit=restricted
    

    시크릿 관리와 etcd 암호화

    이건 진짜 많은 분들이 모르고 지나치는 부분인데요. 쿠버네티스 Secret은 기본적으로 암호화되지 않은 채로 etcd에 저장돼요. Base64 인코딩이 되어 있긴 하지만, 이건 암호화가 아니라 그냥 인코딩이에요. etcd에 직접 접근하면 바로 읽을 수 있거든요.

    # etcd에서 시크릿 평문 확인 (이게 되면 안 되는 거예요!)
    ETCDCTL_API=3 etcdctl get /registry/secrets/default/my-secret \
      --endpoints=https://127.0.0.1:2379 \
      --cacert=/etc/kubernetes/pki/etcd/ca.crt \
      --cert=/etc/kubernetes/pki/etcd/server.crt \
      --key=/etc/kubernetes/pki/etcd/server.key | hexdump -C
    

    etcd 암호화 설정하기

    # /etc/kubernetes/encryption-config.yaml
    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
    - resources:
      - secrets
      - configmaps  # configmap도 같이 암호화 권장
      providers:
      - aescbc:     # AES-CBC 암호화 방식
          keys:
          - name: key1
            secret: 
      - identity: {}  # 암호화 안 된 기존 데이터 읽기 위해 fallback
    
    # kube-apiserver에 암호화 설정 파일 적용 (kubeadm 기준)
    # /etc/kubernetes/manifests/kube-apiserver.yaml에 아래 플래그 추가
    # --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
    
    # 기존 시크릿 모두 재암호화 (설정 후 반드시 실행!)
    kubectl get secrets --all-namespaces -o json | kubectl replace -f -
    

    ⚠️ 중요: 암호화 키는 반드시 안전하게 보관하세요. 키를 잃어버리면 모든 시크릿을 복구할 수 없어요. 실제 프로덕션에서는 AWS KMS, HashiCorp Vault, Azure Key Vault 같은 외부 키 관리 서비스(KMS)와 연동하는 걸 강력히 권장합니다.


    감사 로깅과 런타임 보안 모니터링

    보안은 예방도 중요하지만, 이상 행동을 빠르게 탐지하는 것도 똑같이 중요해요. Audit Logging(감사 로깅)을 설정하면 API 서버에 대한 모든 요청을 기록할 수 있어요.

    # /etc/kubernetes/audit-policy.yaml
    apiVersion: audit.k8s.io/v1
    kind: Policy
    rules:
    # 시크릿 접근은 반드시 RequestResponse 레벨로 상세 기록
    - level: RequestResponse
      resources:
      - group: ""
        resources: ["secrets"]
    
    # 인증 실패 이벤트 기록
    - level: Request
      users: ["system:anonymous"]
    
    # exec 명령어 실행 기록 (공격자가 주로 사용)
    - level: RequestResponse
      resources:
      - group: ""
        resources: ["pods/exec", "pods/attach", "pods/portforward"]
    
    # 그 외 일반 요청은 메타데이터만 기록
    - level: Metadata
      omitStages:
      - RequestReceived
    

    런타임 보안 모니터링 도구로는 Falco를 많이 사용해요. Falco는 CNCF(Cloud Native Computing Foundation) 프로젝트로, 커널 시스템 콜을 분석해서 컨테이너 내부의 이상 행동을 실시간으로 탐지해줘요.

    # Helm으로 Falco 설치
    helm repo add falcosecurity https://falcosecurity.github.io/charts
    helm repo update
    helm install falco falcosecurity/falco \
      --namespace falco \
      --create-namespace \
      --set falco.grpc.enabled=true \
      --set falco.grpc_output.enabled=true
    
    # Falco 로그 확인 (이상 탐지 이벤트)
    kubectl logs -l app.kubernetes.io/name=falco -n falco -f
    

    ▲ Falco 런타임 보안 모니터링 — 컨테이너 내부의 의심스러운 시스템 콜과 이상 행동이 실시간으로 탐지되는 대시보드 예시


    이미지 보안: 공급망 공격 방어

    최근 몇 년간 컨테이너 이미지를 통한 공급망 공격이 크게 늘었어요. 신뢰할 수 없는 이미지를 클러스터에 배포하면 그 자체가 침해의 시작이 될 수 있거든요.

    • ✅ 이미지 취약점 스캔: Trivy, Grype 같은 도구로 CI/CD 파이프라인에서 이미지 스캔 필수화
    • ✅ 이미지 서명 검증: Cosign + Sigstore로 이미지 서명 및 검증 (공급망 무결성 보장)
    • ✅ 신뢰할 수 있는 레지스트리만 허용: Admission Webhook으로 허용된 레지스트리 이미지만 배포 가능하도록 제한
    • ✅ latest 태그 금지: 이미지 버전을 명확하게 고정 (mutable tag는 보안 위험)
    • ✅ distroless 또는 minimal 이미지 사용: 불필요한 바이너리가 없는 이미지를 쓰면 공격 표면 자체가 줄어요
    # Trivy로 이미지 취약점 스캔
    trivy image --severity HIGH,CRITICAL nginx:1.25.3
    
    # Cosign으로 이미지 서명
    cosign sign --key cosign.key my-registry.io/my-app:1.0.0
    
    # Cosign으로 이미지 서명 검증
    cosign verify --key cosign.pub my-registry.io/my-app:1.0.0
    

    전체 보안 체크리스트 정리 및 다음 단계

    ▲ 쿠버네티스 보안 베스트 프랙티스 전체 체크리스트 — RBAC, 네트워크 정책, 컨테이너 보안, 시크릿 관리, 모니터링 항목이 단계별로 정리된 요약 인포그래픽

    여기까지 꽤 많은 내용을 다뤘는데요, 핵심을 정리해드릴게요.

    1. RBAC 최소 권한 원칙 — cluster-admin은 진짜 필요한 경우에만, 서비스 계정은 네임스페이스 범위 Role로 제한
    2. 네트워크 정책(k8s 보안의 방화벽) — 기본 차단 후 필요한 트래픽만 허용, CNI 지원 여부 먼저 확인
    3. SecurityContext 설정 — root 실행 금지, 권한 상승 차단, 읽기 전용 파일시스템 적용
    4. etcd 암호화 — Secret은 반드시 암호화, 가능하면 외부 KMS 연동
    5. 감사 로깅 + 런타임 모니터링 — 예방만큼 탐지도 중요, Falco 도입 검토
    6. 이미지 보안 — 스캔, 서명, 신뢰 레지스트리 제한까지 CI/CD에 통합

    처음부터 모든 걸 한 번에 적용하려고 하면 오히려 지쳐서 포기하게 돼요. 제 경험상 RBAC 정리 → NetworkPolicy → SecurityContext 순서로 하나씩 적용해나가는 게 가장 현실적이더라고요.

    다음 글에서는 OPA Gatekeeper를 활용한 쿠버네티스 정책 자동화를 다룰 예정이에요. Admission Webhook으로 보안 정책을 코드로 관리하는 방법인데, 이걸 도입하면 위에서 설명한 SecurityContext 설정 같은 것들을 개발자가 실수로 빠뜨려도 자동으로 차단하거나 수정할 수 있거든요. 꽤 강력합니다.

    혹시 이 글 읽으면서 “우리 클러스터 이거 안 돼 있는데…” 싶은 항목 있으셨나요? 댓글로 공유해주시면 같이 고민해볼게요. 저도 아직 배우는 중이거든요 😄


    자주 묻는 질문 (FAQ)

    Q. RBAC 설정이 복잡한데, 자동으로 감사하는 도구가 있나요?

    네! rbac-tool이나 kubectl-who-can 같은 플러그인을 사용하면 현재 클러스터의 RBAC 설정을 시각화하고 과도한 권한을 쉽게 찾을 수 있어요. kubectl krew install rbac-tool로 설치할 수 있습니다.

    Q. 관리형 쿠버네티스(EKS, GKE, AKS)를 쓰면 보안 설정이 덜 필요한가요?

    아니요. 관리형 서비스는 컨트롤 플레인(마스터 노드) 보안을 CSP가 담당하지만, RBAC, NetworkPolicy, SecurityContext, 시크릿 관리 같은 워크로드 수준의 보안은 여전히 사용자 책임이에요. 오히려 관리형이라고 방심하는 경우가 더 위험할 수 있어요.

    Q. 컨테이너 보안 스캔은 언제 하는 게 좋나요?

    이상적으로는 Shift Left 보안 원칙에 따라 개발자 로컬 환경 → CI 파이프라인 → 레지스트리 푸시 전 → 배포 전 총 4단계에서 스캔하는 게 좋아요. 최소한 CI 파이프라인에는 반드시 포함시키세요.