네트워크 폴리시를 이용하여 컨테이너 통신 제약하기파드 격리의 두가지 종류(Ingress & Egress)Ingress 격리Egress 격리네트워크 정책으로 할 수 없는 것네트워크 플러그인CNICNI 표준을 따르는 플러그인보안 컨텍스트를 이용하여 컨테이너 기능 제약하기SecurityContext, 파드와 컨테이너 단위로 보안 적용API 토큰의 컨테이너 파일 시스템 마운트 금지웹훅을 이용한 워크로드의 차단 또는 변경하기웹훅 서버단점장점오픈 폴리시 에이전트를 이용한 어드미션 컨트롤제약 템플릿에 있는 레고(Rego) 언어운영환경의 베스트 프랙티스로 삼을 만한 어드미션 폴리시쿠버네티스의 보안 그 깊은 곳
네트워크 폴리시를 이용하여 컨테이너 통신 제약하기
- 인그레스 객체를 사용하여 HTTP 라우팅을 제어하는 방법은 외부에서 클러스터로 인입되는 트래픽만 제어할 수 있음
- 클러스터 내 트래픽 역시 같은 방식으로 제어할 수 있는 수단이 필요한데 이를 위한 기능이
네트워크 폴리시
- 네트워크 폴리시는 포트 단위로 파드 사이의 트래픽을 차단하며 마치 방화벽처럼 동작
- 파드는 자신에게 필요한 컴포넌트에서 온 트래픽만 받아들이고, 자신에게 필요한 컴포넌트에만 트래픽을 보낼 수 있도록 고립되어야 한다.
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: apod-api labels: kiamol: ch16 spec: podSelector: matchLabels: app: apod-api ingress: - from: - podSelector: matchLabels: app: apod-web ports: - port: api
- 네트워크 폴리시의 단점은 규칙 자체를 배치하는 것만으로는 아무 소용 없고, 인그레스 객체에 인그레스 컨트롤러가 필요했듯이, 네트워크 폴리시 객체 역시 클러스터에 정의된 규칙에 맞는 네트워크가 정의되어 있어야 함
- 쿠버네티스의 네트워크 계층은 플러그인 방식으로 교체가 가능한데, 네트워크 플러그인 중에 네트워크 폴리시가 적용되지 않는 것이 있음
- 표준 클러스터 배치에 사용되는 단순 네트워크가 네트워크 폴리시 적용 x
- 네트워크 폴리시 지원여부는 클라우드 사업자에 따라 다름
- AKS 는 클럭스터 생성시 사용 여부 지정
- EKS 는 클러스터 생성 후 네트워크 플러그인 직접 교체해야 네트워크 폴리시 적용가능
파드 격리의 두가지 종류(Ingress & Egress)
Ingress 격리
- 기본 상태: 파드는 비격리 상태이며, 모든 인바운드 연결이 허용됨
- 격리 조건:
policyTypes
에"Ingress"
가 포함된 NetworkPolicy가 적용되면 격리
- 격리 효과:
- 파드로 들어오는 연결은 NetworkPolicy의
ingress
리스트에 명시된 조건만 허용 - 여러 정책이 적용되면, 허용 조건의 합집합만 유효
Egress 격리
- 기본 상태: 파드는 비격리 상태이며, 모든 아웃바운드 연결이 허용됨
- 격리 조건:
policyTypes
에"Egress"
가 포함된 NetworkPolicy가 적용되면 격리
- 격리 효과:
- 파드에서 나가는 연결은 NetworkPolicy의
egress
리스트에 명시된 조건만 허용 - 여러 정책이 적용되면, 허용 조건의 합집합만 유효
- NetworkPolicy는 Ingress와 Egress를 독립적으로 선언 하고 적용할 수 있다
- 파드 간 연결은 송신 파드의 Egress 정책과 수신 파드의 Ingress 정책이 모두 허용해야 성립한다
네트워크 정책으로 할 수 없는 것
- 내부 클러스터 트래픽이 공통 게이트웨이를 통과하도록 강제한다(서비스 메시나 기타 프록시와 함께 제공하는 것이 가장 좋을 수 있음).
- TLS와 관련된 모든 것(이를 위해 서비스 메시나 인그레스 컨트롤러 사용).
- 노드별 정책(이에 대해 CIDR 표기법을 사용할 수 있지만, 특히 쿠버네티스 ID로 노드를 대상으로 지정할 수 없음)
- 이름으로 서비스를 타겟팅한다(그러나, 레이블로 파드나 네임스페이스를 타겟팅할 수 있으며, 이는 종종 실행할 수 있는 해결 방법임).
- 타사 공급사가 이행한 "정책 요청"의 생성 또는 관리.
- 모든 네임스페이스나 파드에 적용되는 기본 정책(이를 수행할 수 있는 타사 공급사의 쿠버네티스 배포본 및 프로젝트가 있음).
- 고급 정책 쿼리 및 도달 가능성 도구.
- 네트워크 보안 이벤트를 기록하는 기능(예: 차단되거나 수락된 연결).
- 명시적으로 정책을 거부하는 기능(현재 네트워크폴리시 모델은 기본적으로 거부하며, 허용 규칙을 추가하는 기능만 있음).
- 루프백 또는 들어오는 호스트 트래픽을 방지하는 기능(파드는 현재 로컬 호스트 접근을 차단할 수 없으며, 상주 노드의 접근을 차단할 수 있는 기능도 없음)
네트워크 플러그인
CNI(Container Network Interface) 의 구현체
CNI
컨테이너 네트워크 설정을 표준화하기 위해 설계된 플러그인 인터페이스
- Pod에 네트워크 인터페이스를 할당
- IP 주소를 Pod에 제공
- Pod 간 트래픽이 전달될 수 있도록 네트워크를 설정.
- 컨테이너 종료 시 네트워크 리소스를 정리.
CNI 표준을 따르는 플러그인
- Calico: 네트워크 정책 및 보안에 중점
- Weave: 간단하고 자동화된 네트워크 설정
- Flannel: 간단한 오버레이 네트워크 제공 (네트워크 폴리시 미지원)
- Cilium: eBPF 기반으로 고성능 네트워킹과 보안 제공
- AWS VPC CNI: EKS에서 기본 제공, Pod에 VPC IP 할당 (네트워크 폴리시 미지원)
화물잇고는 AWS VPC CNI 를 사용하고 있다 - 네트워크 폴리시 미지원
하지만 Security Group 을 활용할 수 있다
보안 컨텍스트를 이용하여 컨테이너 기능 제약하기


Configure a Security Context for a Pod or Container
A security context defines privilege and access control settings for a Pod or Container. Security context settings include, but are not limited to: Discretionary Access Control: Permission to access an object, like a file, is based on user ID (UID) and group ID (GID). Security Enhanced Linux (SELinux): Objects are assigned security labels. Running as privileged or unprivileged. Linux Capabilities: Give a process some privileges, but not all the privileges of the root user.
- 리눅스 컨테이너는 보통 root 라는 이름의 관리자 계정의 권한으로 실행되는데, 이 때문에 별도의 설정이 없다면 컨테이너 속 관리자 권한이 곧바로 호스트의 관리자 권한이 됨
- 따라서 root 계정의 권한으로 실행 중인 컨테이너에 공격자가 침입했다면, 이 공격자는 컨테이너를 실행 중인 서버 전체를 장악한 것이 됨
kubectl apply -f pi/ kubectl wait --for=condition=ContainersReady pod -l app=pi-web # 파드 컨테이너의 사용자명 확인 kubectl exec deploy/pi-web -- whoami root # 그대로 쿠버네티스 api 서버에 접근을 시도 kubectl exec deploy/pi-web -- sh -c 'curl -k -s https://kubernetes.default || grep message' # API 접근 토큰을 출력하기 -> 가능함 kubectl exec deploy/pi-web -- cat /run/secrets/kubernetes.io/serviceaccount/token
- 쿠버네티스에는 파드와 컨테이너 수준에서 적용 가능한 복수의 보안 제어 수단이 존재. 초기에는 활성화 x(애플리케이션이 정상동작하지 않을 수 있기에)
- root 외의 사용자 권한으로도 애플리케이션 실행가능하지만, root 권한으로 실행되어야만 정상동작하는 기능이 있을 수 있음
- 컨테이너에서 사용할 수 있는 리눅스 운영체제 기능을 제한할 수도 있지만, 이때도 역시 일부 애플리케이션 기능이 정상동작하지 않을 수 있음
⇒ 자동화 테스트가 중요
SecurityContext, 파드와 컨테이너 단위로 보안 적용
root 외의 권한으로 컨테이너를 실행할 수 있게 해줌
apiVersion: apps/v1 kind: Deployment metadata: name: pi-web labels: kiamol: ch16 spec: selector: matchLabels: app: pi-web template: metadata: labels: app: pi-web spec: securityContext: runAsUser: 65534 # 'unknown' 사용자 권한으로 애플리케이션 실행 runAsGroup: 3000 # 'nonexistent' 그룹 권한으로 애플리케이션 실행 containers: - image: kiamol/ch05-pi command: ["dotnet", "Pi.Web.dll", "-m", "web"] name: web ports: - containerPort: 5001 name: http env: - name: ASPNETCORE_URLS value: http://+:5001
- 애플리케이션 실행 권한은 낮추었지만, 파일 시스템에는 접근이 가능하기에 공격자는 쿠버네티스 API를 사용할 수 있음
- 쿠버네티스 API 접근 권한은 리눅스 사용자와는 별도이며, 애플리케이션 프로세스를 실행하는 사용자의 권한이 낮더라도 클러스터 전체에 관한 권한을 가질 수 있음
kubectl exec deploy/pi-web -- whoami nobody kubectl exec deploy/pi-web -- cat /run/secrets/kubernetes.io/serviceaccount/token token값출력됨
API 토큰의 컨테이너 파일 시스템 마운트 금지
apiVersion: apps/v1 kind: Deployment metadata: name: pi-web labels: kiamol: ch16 spec: selector: matchLabels: app: pi-web template: metadata: labels: app: pi-web spec: automountServiceAccountToken: false securityContext: runAsUser: 65534 runAsGroup: 3000 containers: - image: kiamol/ch05-pi command: ["dotnet", "Pi.Web.dll", "-m", "web"] name: web ports: - containerPort: 5001 name: http env: - name: ASPNETCORE_URLS value: http://+:5001 securityContext: allowPrivilegeEscalation: false capabilities: drop: - all
capabilities
필드는 특정한 리눅스 커널의 기능을 명시적으로 추가하거나 제거할 수 있음- readOnlyRootFileSystem 기능 : 애플리케이션이 읽기 전용 파일 시스템을 갖도록 하는 것으로 매우 강력한 기능임
모든 애플리케이션을 일반 사용자 권한으로 실행하고 읽기 전용 파일 시스템을 사용하도록 강제할 수도 있지만, 이렇게 하려면 애플리케이션 대부분의 코드를 수정해야 할 것
웹훅을 이용한 워크로드의 차단 또는 변경하기
어드미션 컨트롤
: 쿠버네티스에서 어떤 리소스 생성 시, 이 객체가 클러스터에서 실행되어도 괜찮은지 확인하는 절차어드미션 컨트롤러
- 예 : 네임스페이스에 허용된 것보다 더 많은 자원이 필요한 파드 배치 시, 파드 실행되지 않도록 해줌
- 플러그인 방식으로 동작하여, 사용자 정의 어드미션 컨트롤 규칙 추가 가능함
- ValidatingAdmission Webhook : 리소스쿼터와 비슷하게 객체 생성을 허용하거나 차단
- MutatingAdminssion Webhook : 객체 정의를 변경해서 애초 요청과 다른 객체가 생성되도록 하는 역할
- 뮤테이션 웹 훅 호출 후, 밸리데이팅 웹훅이 호출 됨
- 동작 방식: 설정객체를 통해
객체의 생애 주기 설정
과해당 규칙을 적용할 웹 서버의 URL
을 지정
웹훅 서버
웹훅 서버는 클러스터 내부 또는 외부 어디에서든 실행될 수 있는데, HTTPS 를 사용해야 함
apiVersion: v1 kind: Service metadata: name: admission-webhook labels: kiamol: ch16 spec: type: ClusterIP ports: - port: 443 targetPort: https selector: app: admission-webhook --- apiVersion: apps/v1 kind: Deployment metadata: name: admission-webhook labels: kiamol: ch16 spec: selector: matchLabels: app: admission-webhook template: metadata: labels: app: admission-webhook spec: containers: - name: admission-webhook image: kiamol/ch16-admission-webhook env: - name: USE_HTTPS value: "true" - name: PORT value: "8443" ports: - name: https containerPort: 8443 volumeMounts: - name: certs mountPath: /run/secrets/tls readOnly: true volumes: - name: certs secret: secretName: admission-webhook-secret
apiVersion: admissionregistration.k8s.io/v1beta1 kind: ValidatingWebhookConfiguration metadata: name: servicetokenpolicy labels: kiamol: ch16 webhooks: - name: servicetokenpolicy.kiamol.net rules: - operations: [ "CREATE", "UPDATE" ] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] clientConfig: service: name: admission-webhook namespace: default path: "/validate" caBundle: {{ .Values.caBundle }}
apiVersion: admissionregistration.k8s.io/v1beta1 kind: MutatingWebhookConfiguration metadata: name: nonrootpolicy labels: kiamol: ch16 webhooks: - name: nonrootpolicy.kiamol.net rules: - operations: [ "CREATE", "UPDATE" ] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] clientConfig: service: name: admission-webhook namespace: default path: "/mutate" caBundle: {{ .Values.caBundle }}
- 웹훅 설정은 유연하다. 웹훅이 호출되는 연산(
operations
)과 적용될 대상(resources
)을 원하는 대로 지정할 수 있음 - 웹훅 적용시, 어떤 대상에 어떤 작용을 해야 하는지 잘 계획이 필요
- 검증 과정이 디플로이먼트 단위였다면 사용자 입장에서 편할 수 있지만, 다른 컨트롤러가 생성하거나 사용자가 직접 생성하는 파드를 검증에서 빠뜨릴 여지가 있음
- 같은 대상에 여러 개의 웹훅을 설정할 수도 있다. 밸리데이팅웹훅은 모두 병렬로 실행되며 그 중 하나만 실패해도 객체 생성 차단할 수 있다.
- 여기서 웹훅은 파드 단위로 동작함(resources: [”pods”])
- 레플리카셋이나 디플로이먼트 자체는 만들어지지만, 그 안에서 파드가 안만들어짐. 왜 제대로 동작하지 않는지 파악하려면 describe를 통해 살펴봐야함
단점
- 눈에 잘 띄지 않는다. kubectl을 사용하여 밸리데이팅웹훅이 있는지 확인할 수는 있어도 실제 검증 규칙의 내용은 확인하기 어려움. 따라서 이들 규칙을 클러스터 외부에 문서로 작성해 두는 수 밖에 없음
- 뮤테이팅 웹훅에서 더 심각. 제대로 동작하면 사용자가 의도한 것과 다른 객체를 내놓기에
장점
- 좋은 예 : 뮤테이팅훅으로 사이드카 컨테이너를 추가하는 방법, 로그를 남기는 모든 애플리케이션을 레이블로 식별하고 웹훅을 통해 자동으로 로그 사이드카를 이들 파드에 추가하는 것도 가능
- 위험한 부분도 많지만 충분한 테스트와 최소한의 범위로 규칙을 적용하면 위험을 최소화시킬 수 있음
오픈 폴리시 에이전트를 이용한 어드미션 컨트롤
Introduction | Gatekeeper Library
Artifact Hub
gatekeeper-library
open-policy-agent • Updated Jan 14, 2025
OPA GateKeeper 구성 (GPT)
- 웹훅 서버 (Webhook Server):
- Gatekeeper의 핵심 컴포넌트로, Kubernetes API Server와 통신하여 요청을 처리.
- API Server에서 들어오는 리소스 생성/수정 요청을 받아 정책을 검증하거나 위반 여부를 확인.
- ValidatingAdmissionWebhook으로 동작.
- ValidatingWebhookConfiguration:
- API Server와 Gatekeeper를 연결하는 설정 리소스.
- Kubernetes에 Gatekeeper 웹훅 서버가 활성화되었음을 알리고, 어떤 리소스와 요청에 대해 웹훅 검증을 수행할지 정의.
- 예: Pod 생성, 네임스페이스 업데이트 등에 대해 정책 적용.
- ConstraintTemplate (제약 템플릿):
- OPA의 정책을 정의하는 템플릿.
- 정책을 Rego 언어로 작성하며, 어떤 조건에서 위반이 발생하는지를 정의.
- Constraint(제약)의 기반이 되는 규칙을 제공.
- Constraint (제약):
- ConstraintTemplate을 기반으로 특정 정책 조건을 설정.
- 어떤 리소스와 조건에 대해 정책을 적용할지 정의.
- 예: 모든 Pod에 특정 레이블(
team
)이 있어야 한다.
OPA(Open Policy Agent)
: 쿠버네티스 객체 형태로 규칙을 기술할 수 있게 해 주어 웹훅에 적용되는 규칙이 클러스터에 남아 있도록 하는 기술(밸리데이팅웹훅을 사용하되 이를 관리 레이어로 감싸 두는 형태로 구성)- 폴리시의 기술과 구현을 하나로 합치기 위함
- OPA(Open Policy Agent)의 목적은 모든 종류의 폴리시를 기술하고 서로 다른 플랫폼에 함께 적용할 수 있는 표준 언어를 만드는 것
OPA 게이트 키퍼(사용자 정의 밸리데이팅 웹훅을 깔끔하게 대체할 수 있음)
- 게이트키퍼 컴포넌트를 클러스터에 배치
- 컴포넌트는
웹훅 서버
와 일반적인밸리데이팅웹훅컨피규레이션
으로 구성
- 어드미션컨트롤폴리시가 기술될
제약 템플릿을 생성한
후 제약 템플릿을 따라 원하는제약을 생성
- 예시
- 모든 파드는 특정한 레이블을 가져야 한다 라는 템플릿 생성
- 제약 만들 시, 해당 네임스페이스 내 대상이 될 레이블만 지정
OPA는 사용자 정의 밸리데이팅웹훅에 비하면 훨씬 깔끔하게 어드미션 컨트롤을 적용할 수 있다.
게이트키퍼에는 조건에 부합하도록 정의를 변경하는 뮤테이션 기능은 없지만 명확히 정의 변경이 필요한 사항이 있다면 여러분 사용자 정의 웹훅과 OPA를 조합하면 됨
제약 템플릿에 있는 레고(Rego) 언어
Rego 언어로 일반화된 폴리시 정의가 담겨 있고, 이 언어는 입력된 객체 속성이 정의된 조건과 부합하는지 평가하는 데 특화된 언어임
사용자 정의 웹훅 때 처럼 애플리케이션 코드 속 대신에 YAML 파일 안에 폴리시 정의를 둘 수 있음
apiVersion: templates.gatekeeper.sh/v1beta1 kind: ConstraintTemplate metadata: name: requiredlabels spec: crd: spec: names: kind: RequiredLabels validation: openAPIV3Schema: properties: labels: type: array items: string targets: - target: admission.k8s.gatekeeper.sh rego: | package requiredlabels violation[{"msg": msg, "details": {"missing_labels": missing}}] { provided := {label | input.review.object.metadata.labels[label]} required := {label | label := input.parameters.labels[_]} missing := required - provided count(missing) > 0 msg := sprintf("you must provide labels: %v", [missing]) }
게이트키퍼에서 이 폴리시를 제약 템플릿으로 배치하면 이 템플릿이 적용된 제약 객체를 배치할 수 있음
이 둘이 어떻게 연관이 될까? → kind 를 커스텀하게 만든 것
RequiredLabels
이 필드로 둘이 연관됨apiVersion: constraints.gatekeeper.sh/v1beta1 kind: RequiredLabels metadata: name: requiredlabels-kiamol spec: match: kinds: - apiGroups: [""] kinds: ["Service", "Deployment", "ConfigMap"] - apiGroups: ["apps"] kinds: ["Deployment"] parameters: labels: ["kiamol"] --- apiVersion: constraints.gatekeeper.sh/v1beta1 kind: RequiredLabels metadata: name: requiredlabels-app spec: match: kinds: - apiGroups: [""] kinds: ["Pod"] parameters: labels: ["app", "version"] # 모든 파드에 app과 version 레이블을 강제
- 동일한 템플릿을 사용하는 여러 개의 제약을 배치할 수도 있음
# 제약 템플릿에는 파라미터를 가진 규칙이 정의 kubectl apply -f opa/templates/requiredLabels-template.yaml # 제약은 제약 템플릿에 파라미터 값을 추가하여 정의. 제약마다 각기 다른 유형의 객체에 서로 다른 레이블을 필수로 kubectl apply -f opa/constraints/requiredLabels.yaml # to-do 애플리케이션 정의는 모든 제약에 위배. 생성 실패하게됨 kubectl apply -f todo-list/
- 게이트키퍼도 밸리데이팅웹훅을 통해 제약을 검증
운영환경의 베스트 프랙티스로 삼을 만한 어드미션 폴리시
- 모든 파드에 컨테이너 프로브를 강제
- 승인된 리포지터리의 이미지만 사용하라
- 모든 컨테이너에 메모리 및 CPU 사용량 제한 추가를 강제
쿠버네티스의 보안 그 깊은 곳
- 보안 출발점은 컨테이너 이미지에 알려진 취약점을 탐색하는 것 부터
- 보안 탐색 결과에서 취약점이 발견되지 않은 이미지만 운영 리포지터리로 푸시되도록 파이프라인 구성
- 이런 구성을 리포지터리 제약과 결합하면 효과적으로 이미지 안전성 확보가 가능함
- 안전한 이미지로 컨테이너 실행하더라도, 공격 목표가 될 수 있으므로 실행 시점의 보안 역시 모니터링 도구로 수상쩍은 동작을 하는 컨테이너가 있으면 경고를 보내거나 아예 컨테이너를 종료
- Tip :
보안 컨텍스트
를 먼저 적용한 후네트워크 폴리시
를 적용한다. 그리고 설정할 규칙을 확실히 정한 후어드미션 컨트롤
로 진행