안녕하세요, 짧은머리 개발자입니다.

클라우드는 보안이 매우 중요한 환경인데요, 오늘은 Google Cloud Platform에서 관리중인 여러 프로젝트간 서비스 접근을 어떻게 구성하는지 공유하려 해요.

클라우드는 서비스로 제공하는 XaaS에 대한 접근을 제어할 수 있도록 IAM을 제공하는데 AWS, GCP와 같은 거대 클라우드는 IAM Role for Service Account(IRSA)라 불리는 메커니즘을 통해 더 세밀한 제어를 가능하게 해요.

이름에서 유추할 수 있듯이 서비스 계정을 위한 IAM 역할을 부여함으로써 그 메커니즘이 동작해요.

GCP의 서비스에 등록된 Service Account는 구글의 메타데이터 서버를 통해 자격 증명에 접근하고, 필요한 토큰을 관리해요.

  • 이전에 작성한 Github과 같이 GCP 외부에서 접근하는 경우에는 Security Token Services를 통해 단기 자격을 증명해주는 토큰을 발급받아야 하고, 이 때 roles/iam.serviceAccountTokenCreator과 같은 역할이 추가로 필요해요.

GCP는 구조를 계정 수준 리소스부터 서비스 수준 리소스로 나누어 관리할 수 있는데, 베스트 프랙티스로 GCP 내부에 생성할 그 구조를 회사의 조직 구조와 일치하게 만드는 것을 권장해요.

https://cloud.google.com/resource-manager/docs/cloud-platform-resource-hierarchy?hl=ko

이는 곧 분리되어 있는 프로젝트 간 배포된 서비스간 접근을 허용해야 하는 경우가 발생할 수 있어요.

제가 수행했던 프로젝트의 경우에도 분리되어있는 프로젝트간 버킷에 접근해야 할 필요가 있었는데요, 이를 어떻게 구성했는지 공유하려 해요.

프로젝트 구성

프로젝트 구성은 위와 같은 형태로, Project A에서 동작중인 Application A는 Project B의 Bucket B의 오브젝트에 접근할 수 있어야 하며, Bucket B는 Private하며 IAM을 통해 접근을 제한하고 있어요.

이를 해결하기 위해 우리는 싱글 프로젝트와 같이 Project A의 Application A에 서비스 계정을 추가한 뒤, Project B의 IAM을 구성해줘야 해요.

이 때 Project B에서 bucket-sa@project-a.iam.gserviceaccount.com 서비스 계정이 가져야할 역할은 다음과 같아요.

  • Bucket B에서 행할 작업에 따른 Storage 접근 권한, roles/storage.objectViewer
💡 roles/iam.serviceAccountUser의 경우, 특정 사용자 혹은 서비스 계정이
bucket-sa와 같이 IAM 역할을 갖는 서비스 계정으로 가장(impersonate)해야 할 때 필요해요.
이 경우에는 Application에 부착된 서비스 계정이 직접적으로 사용하기 때문에 필요하지 않아요.

 

여기까지 수행하고 나면 RBAC이 구성되고, Application A에서 Bucket B에 정상적으로 접근할 수 있어요.

그런데 만약 Project B에 Bucket B 말고 여러 버킷이 존재한다며면, bucket-sa 서비스 계정을 이용해서 모든 버킷에 접근 가능하게 돼요. 이는 보안상 매우 취약한 설정이며, 따라서 Attribute Based Access Control(ABAC)을 구성해야 해요.


ABAC은 이름에서 유추할 수 있듯 속성 기반의 접근 제어 메커니즘이에요. 기존의 bucket-sa 서비스 계정은 역할을 기반으로 Bucket B에 접근 가능하도록 구성했다면, 여기에 속성을 추가하여 더 세밀한 제어를 할 수 있어요.

우리에게 필요한 세밀한 제어는 다음과 같아요.

  • Bucket B에 접근할 수 있어야 한다.
  • Bucket B 내부의 오브젝트를 볼 수 있어야 한다.
  • Bucket B 내부의 디렉토리 구조를 볼 수 있어야 한다.

이를 해결하기 위해 GCP는 IAM 역할에 조건을 설정할 수 있고, 역할의 조건에 필요한 리소스 유형과 리소스 이름은 다음 링크에서 확인할 수 있어요.

우리에게 필요한 속성을 정리하면 다음과 같아요.

# 허용할 서비스 계정
member = "bucket-sa@project-a.iam.gserviceaccount.com"

# 허용할 역할
roles = [
  "roles/storage.objectViewer",
]

# 역할 + 대상 = 서비스 계정이 접근할 수 있는 대상
target_resources = [
  # 버킷의 이름이 bucket-b와 같아야 한다.
  {
    type   = "storage.googleapis.com/Bucket",
    name   = "projects/_/buckets/bucket-b"
    option = "equal"
  },
  # 접근하고자 하는 오브젝트는 bucket-b의 objects여야 한다.
  {
    type = "storage.googleapis.com/Object",
    name   = "projects/_/buckets/bucket-b/objects/"
    option = "startsWith"
  },
  # 접근하고자 하는 폴더는 bucket-b의 관리형 폴더여야 한다.
  {
    type = "storage.googleapis.com/ManagedFolder",
    name   = "projects/_/buckets/bucket-b/managedFolders/"
    option = "startsWith"
  }
]

버킷 뿐만 아니라 다른 XaaS에 대한 접근이 필요할 때도 위와 같이 서비스 계정을 구성한다면 프로젝트간 IRSA를 구성할 수 있어요.

오늘도 읽어주셔서 감사합니다.

'DevOps > Cloud' 카테고리의 다른 글

GKE와 Cloud Load Balancing 연결하기  (1) 2024.07.15

안녕하세요, 짧은머리 개발자에요.

제가 근무하고 있는 회사는 Google Cloud Platform을 이용하여 서비스를 운영하고 있어요. 특히 컨테이너의 시대인 만큼 Google Kubernetes Engine을 이용하여 컨테이너를 관리하고 있는데요, 오늘은 처음 GKE를 도입하면서 GCP의 Cloud Load Balancer를 연동하며 했던 고민을 공유하려 해요.


먼저 GCP는 관리중인 서비스를 외부 혹은 내부에서 접근할 수 있도록 Cload Load Balancing이라는 명칭의 PaaS를 제공하고 있어요. CLB라고 불리는 이 서비스는 GCP에 배포된 서비스들에 고성능 부하 분산 기능을 제공하는 역할을 해요. AWS에는 ALB(NLB)라는 솔루션이 있어요.

우리 회사가 GKE에서 운영중인 서비스들에 대해 외부에서 접근할 수 있도록 하는것이 목표였는데요, GCP에서는 일반적으로 GKE의 외부 노출을 위해 다음의 솔루션을 제공해요.

  1. 쿠버네티스 게이트웨이 API를 이용한 외부 노출
  2. 쿠버네티스 인그레스를 이용한 외부 노출
  3. 서비스 + CLB 직접 관리를 통한 외부 노출

우리 회사가 선택한 방법은 3번인데, 그 이유는 다음과 같아요.

  1. Load Balancer의 직접 관리를 통해 보다 폭 넓은 제어가 필요하다.
  • 2번의 경우 CLB가 자동으로 생성, 관리되는데 이 경우 직접적인 제어가 까다로웠어요. 특히 인그레스 혹은 게이트웨이를 생성할 때 마다 CLB가 생성되는데, 이는 불필요한 CLB의 증가로 인해 관리가 복잡해지고 비용이 늘어나는 원인이 되기도 했어요.
  1. SSL에 대한 폭 넓은 제어가 필요하다.1, 2번의 경우 인그레스, 게이트웨이가 늘어날 때 마다 해당 요청을 처리하는 도메인에 대한 SSL을 명시해 주고 관리해야 하는데 이것은 불필요한 관리를 유발했어요.
  • 특히 초기의 경우 도메인에 대한 asterisk를 SSL에 등록하지 않고, 우리 회사에서 사용하는 도메인들에 대해 SSL을 발급, 관리했는데 이는 매우 까다로운 절차였어요.
  • 인증서를 관리하기 위해 Google Certificates Manager를 이용하고 있어요. 문제는 DevOps 관점에서 SSL의 변경 혹은 추가가 일어날 때, Terraform을 이용한 생성 및 CLB 할당에서 인증서 관리를 마무리 하고 싶었어요.
  1. L7 뿐만 아니라 L4에 대한 트래픽 제어도 필요하다.
  • 블록체인을 운영하는 회사로서 노드에 대한 RPC 통신을 지원해야 했기 때문에 CLB를 통한 직접적인 관리가 필요했어요.
  1. 회사의 규모가 크지 않은만큼 필요한 수의 CLB만 유지하고 싶었어요.
  • GCP의 CLB는 고가용성을 제공하는 만큼 증가하는 트래픽에 대한 CLB를 분리하여 운영할 필요가 없었기에, 필요한 CLB의 숫자만 유지하고 싶었어요.

GCP에서는 최종적으로 GKE의 서비스에 접근하기 위해 GCP Backend Service와 Network Endpoint Group을 이용해야 해요.

GCP는 이를 지원하기 위해 AutoNeg 쿠버네티스 오퍼레이터를 제공하고 있어요. 이를 통해 쿠버네티스에 배포된 서비스를 바탕으로 GCP Backend Service와 Network Endpoint Group을 생성해 줘요.

이를 위해 쿠버네티스 서비스를 생성할 때 다음의 예시처럼 Backend Service와 NEG에 필요한 정보를 어노테이션에 담아 생성해야 해요.

apiVersion: v1
kind: Service
metadata:
  annotations:
    cloud.google.com/neg: '{"exposed_ports": {"80":{"name": "nginx-neg-svc"}}}'
  name: nginx-neg-test
  namespace: devops
  labels:
    app: nginx-neg-test
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    app: nginx

이처럼 서비스에 AutoNeg를 통해 GKE와 CLB를 융합하며 운영했을 때 다음의 큰 문제가 발생했어요.

  • GKE 서비스를 배포할 때, Network Endpoint Group이 생성되는 Zone을 명시할 수 없다.
    • 특히 이는 AWS의 EKS의 경우 AWS Load Balancer Controller와 Target Group Binding 그리고 리스너를 통해 배포되어 있는 EKS Pod에 대한 외부 노출이 가능한데 이에 대해 한번에 지원되지 않는게 의아했어요.

이는 Pod의 Spec이 변경될 때 마다 NEG 위치가 변하는 이슈가 있었고, CLB에서는 변경된 NEG로 다시 설정해 줘야 트래픽의 정상적인 처리가 가능했어요. 즉 Downtime이 발생할 수 있는 위험성을 가지고 있어요.

NEG이 하나만 생성된 모습

이를 해결하기 위해 GCP의 GKE 외부 노출 방법중 Ingress를 이용하여 애플리케이션을 외부로 노출해 봤어요.

# nginx.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: devops
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-neg-test
  namespace: devops
  labels:
    app: nginx-neg-test
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    app: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: devops
spec:
  rules:
    - http:
        paths:
          - path: /*
            pathType: ImplementationSpecific
            backend:
              service:
                name: nginx-neg-test
                port:
                  number: 80
가능한 모든 zone에 NEG이 생성된 모습

이 경우 AutoNeg과는 다르게 Ingress로 배포한 서비스는 자동으로 현재 GKE가 배포되어 있는 Region 내의 모든 Zone에 대해 Network Endpoint Groups를 배포하는 것을 확인했고, 머리속에서 이거다! 하는 생각이 떠올랐어요.

GCP의 AutoNeg의 경우 배포되는 Service가 사용할 Network Endpoint Groud에 대해 다음의 규칙을 지키면 이미 생성된 NEG을 활용해요.

💡 Note
Updating NEG description is impossible, so this only works if the NEG is used for populating endpoints from a specific set: [Cluster, service name, namespace, port] which does not change.


그래서 저는 AWS의 ALB Controller + Target Group Binding 노하우를 활용하여 다음과 같이 GKE 애플리케이션 외부 노출 정책을 결정했답니다.

  1. 인프라에 배포할 애플리케이션 요구사항에 맞춰 CLB, Backend Service, Network Endpoint Group을 먼저 생성한다.
  1. Backend Services를 생성하며 생성된 NEG을 연결한다.
  • AutoNeg를 이용하여 서비스를 배포할 때 Backend Service와 NEG의 이름을 명시적으로 사용할 수 있어요.
  • 이를 적용하기 위해 Backend Service를 생성할 때 쿠버네티스 서비스에 명시할 이름을 스펙으로 설정해야 해요.

3. 쿠버네티스 서비스 배포시 Backend Service를 생성한 스펙을 바탕으로 AutoNeg Annotation을 설정하여 배포한다.

이렇게 할 경우 Pod가 재배포 되면서 스펙에 변경사항이 발생되더라도 자동으로 AutoNeg이 서비스를 검사하여 GCP의 NEG과 Backend Service에 해당하는 서비스를 연결하는것을 확인했고 Downtime 없이 Application을 배포할 수 있었어요.

'DevOps > Cloud' 카테고리의 다른 글

GCP Multi Project에 대한 IRSA 구성하기  (0) 2024.09.03

+ Recent posts