Istio를 사용하여 Cloud Run for Anthos 서비스에 대한 액세스 승인

이 가이드에서는 Istio를 사용하여 Cloud Run for Anthos에 배포한 서비스에 대한 액세스를 승인하는 방법을 보여줍니다.

Cloud Run은 앱 및 기능을 배포하고 제공하는 개발자 중심 환경을 제공합니다. Cloud Run은 완전 관리형 환경 또는 Cloud Run for Anthos라고 하는 Google Kubernetes Engine(GKE) 클러스터에서 서비스를 실행합니다.

두 환경의 개발자 경험은 동일하지만 기본 플랫폼의 기능은 다릅니다.

Cloud Run for Anthos는 ID 및 액세스 관리(IAM)를 사용하여 서비스를 호출하기 위한 권한을 부여하지 않습니다. 대신 Istio를 사용하여 인증 및 승인을 구현합니다. 이 가이드에서는 Cloud Run for Anthos 샘플 서비스를 보호하는 데 도움이 되는 Istio 인증 및 승인 정책을 사용합니다.

인증 및 승인 정책은 샘플 서비스를 호출할 수 있는 ID(IAM 서비스 계정 및 사용자)를 지정합니다.

이 가이드에서는 GKE 클러스터 외부에서 실행되는 클라이언트에 대한 인증 및 승인을 구현합니다.

목표

  • Cloud Run이 사용 설정된 GKE 클러스터를 만듭니다.
  • Cloud Run for Anthos에 샘플 서비스를 배포합니다.
  • IAM 서비스 계정을 만듭니다.
  • Istio 인증 정책을 만듭니다.
  • Istio 승인 정책을 만듭니다.
  • 솔루션을 테스트합니다.

비용

이 가이드에서는 비용이 청구될 수 있는 다음과 같은 Google Cloud 구성요소를 사용합니다.

프로젝트 사용량을 기준으로 예상 비용을 산출하려면 가격 계산기를 사용하세요. Google Cloud를 처음 사용하는 사용자는 무료 체험판을 사용할 수 있습니다.

이 가이드를 마치면 만든 리소스를 삭제하여 비용이 계속 청구되지 않게 할 수 있습니다. 자세한 내용은 삭제를 참조하세요.

시작하기 전에

  1. Google Cloud 계정에 로그인합니다. Google Cloud를 처음 사용하는 경우 계정을 만들고 Google 제품의 실제 성능을 평가해 보세요. 신규 고객에게는 워크로드를 실행, 테스트, 배포하는 데 사용할 수 있는 $300의 무료 크레딧이 제공됩니다.
  2. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

  3. Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다. 프로젝트에 결제가 사용 설정되어 있는지 확인하는 방법을 알아보세요.

  4. Google Kubernetes Engine API, Cloud Run API, Cloud APIs를 사용 설정합니다.
    API 사용 설정
  5. Cloud Console에서 Cloud Shell로 이동합니다.
    Cloud Shell로 이동
    Cloud Console 하단에 Cloud Shell 세션이 열리면서 명령줄 프롬프트가 표시됩니다. Cloud Shell은 gcloud 명령줄 도구가 포함되고 Cloud SDK가 사전 설치된 셸 환경으로, 현재 프로젝트의 값이 이미 설정되어 있습니다. 세션이 초기화되는 데 몇 초 정도 걸릴 수 있습니다. Cloud Shell을 사용하여 가이드의 모든 명령어를 실행합니다.

환경 설정

  1. 이 가이드에서 사용할 Compute Engine 영역의 환경 변수 및 gcloud 도구 기본값을 정의합니다.

    ZONE=us-central1-f
    gcloud config set compute/zone $ZONE
    

    영역을 변경할 수 있습니다.

  2. Cloud Run 부가기능으로 GKE 클러스터를 만듭니다.

    CLUSTER=cloud-run-gke-invoker-tutorial
    
    gcloud beta container clusters create $CLUSTER \
        --addons HorizontalPodAutoscaling,HttpLoadBalancing,CloudRun \
        --enable-ip-alias \
        --enable-stackdriver-kubernetes \
        --machine-type e2-standard-2 \
        --release-channel regular
    

    이 가이드에는 GKE 버전 1.15.11-gke.9 이상, 1.16.8-gke.7 이상 또는 1.17.4-gke.5 이상이 필요합니다. regular 출시 채널을 사용하는 새 GKE 클러스터는 버전 제약조건을 충족합니다.

샘플 서비스 배포

  1. Cloud Shell에서 GKE 클러스터에 tutorial이라는 네임스페이스를 만듭니다.

    kubectl create namespace tutorial
    

    네임스페이스의 이름은 변경할 수 있습니다.

  2. sample라는 서비스를 tutorial 네임스페이스의 Cloud Run for Anthos에 배포합니다.

    gcloud run deploy sample \
        --cluster $CLUSTER \
        --cluster-location $ZONE \
        --namespace tutorial \
        --image gcr.io/knative-samples/simple-api \
        --platform gke
    

    이 명령어는 Knative Serving 서비스 객체를 만듭니다.

  3. Cloud Run for Anthos는 Istio 인그레스 게이트웨이의 외부 IP 주소에 서비스를 노출합니다. 외부 IP 주소를 가져와 EXTERNAL_IP라는 환경 변수와 external-ip.txt라는 파일에 저장합니다.

    export EXTERNAL_IP=$(./get-external-ip.sh | tee external-ip.txt)
    
    echo $EXTERNAL_IP
    
    get_external_ip () {
        external_ip=$(kubectl -n gke-system get svc istio-ingress \
            -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    }
    get_external_ip
    while [ -z "$external_ip" ]; do
        sleep 2
        get_external_ip
    done
    echo "$external_ip"
  4. 샘플 서비스가 HTTP/1.1 200 OK로 응답하는지 확인합니다.

    curl -siH "Host: sample.tutorial.example.com" $EXTERNAL_IP | head -n1
    

서비스 계정 만들기

  1. 이후 단계에서 샘플 서비스에 대한 액세스 권한을 부여하는 IAM 서비스 계정을 만듭니다. 서비스 계정 이메일 주소를 환경 변수에 저장합니다.

    export SERVICE_ACCOUNT_EMAIL=$(gcloud iam service-accounts create \
        cloudrun-gke-invoker-tutorial \
        --display-name "Cloud Run for Anthos authorization tutorial service account" \
        --format "value(email)")
    

    이 가이드에서는 서비스 계정 이름 cloudrun-gke-invoker-tutorial을 사용합니다. 이 이름은 변경할 수 있습니다.

Istio 인증 및 승인 구성

  1. Istio 인증 정책을 만듭니다.

    kubectl apply -f authenticationpolicy.yaml
    
    apiVersion: authentication.istio.io/v1alpha1
    kind: Policy
    metadata:
      name: istio-ingress-jwt
      namespace: gke-system
    spec:
      targets:
      - name: istio-ingress
        ports:
        - name: http2
        - name: https
      origins:
      - jwt:
          issuer: https://accounts.google.com
          jwksUri: https://www.googleapis.com/oauth2/v3/certs
      originIsOptional: true # use authorization policy to allow or deny request
      principalBinding: USE_ORIGIN

    이 인증 정책은 Istio 인그레스 게이트웨이의 http2https 포트에 대한 수신 요청의 Authorization 헤더에서 JSON 웹 토큰(JWT)을 검증합니다. 정책을 충족하려면 JWT가 Google에서 발급하고 서명한 OpenID Connect(OIDC) ID 토큰이어야 합니다. ID 토큰 검증에 대한 자세한 내용은 Google Identity Platform 문서를 참조하세요.

    속성 originIsOptional: true는 지정된 제약조건을 충족하는 JWT가 포함되어 있지 않더라도 인증 정책이 요청을 수락한다는 것을 의미합니다. 이 인증 정책의 목적은 다음 단계에서 만드는 승인 정책에 JWT의 속성을 사용할 수 있도록 하는 것입니다. 승인 정책은 요청의 수락 또는 거부 여부를 결정합니다.

  2. invoke-tutorial이라는 Istio 승인 정책을 만듭니다.

    envsubst < authorizationpolicy-invoker.tmpl.yaml | kubectl apply -f -
    

    이 승인 정책은 http://example.com의 대상(aud) 클레임을 포함하는 $SERVICE_ACCOUNT_EMAIL으로 식별된 서비스 계정에 대한 모든 요청(Google(https://accounts.google.com)에서 발급되고 서명된 JWT를 포함)과 *.tutorial.example.com이 일치하는 호스트 이름을 가진 모든 워크로드에 대한 액세스를 HTTP 메서드 및 URL 경로를 사용하여 제공합니다.

    Cloud Run for Anthos 기본 도메인을 자체 환경에서 변경하는 경우 hosts 필드의 값을 도메인에 맞게 조정합니다.

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: invoke-tutorial
      namespace: gke-system
    spec:
      action: ALLOW
      rules:
      - to:
        - operation:
            hosts:
            - '*.tutorial.example.com'
        when:
        - key: request.auth.claims[aud]
          values:
          - http://example.com
        - key: request.auth.claims[email]
          values:
          - $SERVICE_ACCOUNT_EMAIL
        - key: request.auth.claims[iss]
          values:
          - https://accounts.google.com
      selector:
       matchLabels:
         istio: ingress-gke-system

    중요: 액세스 제어를 위해 경로 유형 값 일치와 Istio의 AuthorizationPolicy를 사용할 경우 알려진 취약점이 있습니다. 자세한 내용은 Cloud Run for Anthos 문서의 보안 권장사항을 참조하세요.

  3. 인증 및 승인 정책이 적용되는 데 다소 시간이 걸릴 수 있습니다. 다음 명령어를 실행하고 출력에 HTTP/1.1 403 Forbidden이 표시될 때까지 기다립니다.

    while sleep 2; do
        curl -siH "Host: sample.tutorial.example.com" $EXTERNAL_IP | head -n1
    done
    

    EnvoyIstio의 정책 변경사항은 eventual consistency를 갖기 때문에 처음에는 HTTP/1.1 200 OKHTTP/1.1 403 Forbidden가 번갈아 출력될 수 있습니다.

    HTTP/1.1 403 Forbidden이 반복적으로 표시되면 Ctrl+C를 눌러 대기를 중지합니다.

서비스 액세스

생성한 인증 및 승인 정책에는 Google에서 발급하고 서명한 ID 토큰이 필요합니다. 여러 가지 방법으로 토큰을 받을 수 있습니다. 다음은 4개 옵션을 권장사항 순으로 보여줍니다.

  1. Compute Engine 인스턴스, GKE 클러스터 또는 메타데이터 서버에 액세스할 수 있는 다른 환경에서 보호된 서비스에 액세스하는 경우 Compute Engine 메타데이터 서버를 사용합니다.
  2. 원하는 프로그래밍 언어에 사용할 수 있는 경우 Google 인증 클라이언트 라이브러리를 사용하세요.
  3. Google 인증 클라이언트 라이브러리를 사용할 수 없는 경우 직접 IAM Service Account Credentials API를 사용하세요.
  4. 서비스를 대화형으로 테스트하려는 경우(예: 구현 시) gcloud 명령줄 도구를 사용하세요.

Compute Engine 메타데이터 서버 사용

  1. Cloud Shell에서 Compute Engine 인스턴스(VM)를 만들고 이전에 만든 서비스 계정을 연결합니다.

    VM=cloudrun-gke-invoker-tutorial-vm
    
    gcloud compute instances create $VM \
        --scopes cloud-platform \
        --service-account $SERVICE_ACCOUNT_EMAIL
    

    이 가이드에서는 인스턴스 이름에 cloudrun-gke-invoker-tutorial-vm을 사용합니다. 이 이름은 변경할 수 있습니다.

  2. Istio Ingress Gateway의 공개 IP 주소가 포함된 파일을 VM에 복사합니다.

    gcloud compute scp external-ip.txt $VM:~
    
  3. SSH를 사용하여 VM에 연결합니다.

    gcloud compute ssh $VM
    
  4. SSH 세션에서는 Compute Engine 메타데이터 서버로부터 ID 토큰을 받습니다.

    ID_TOKEN=$(curl -s -H Metadata-Flavor:Google \
        --data-urlencode format=full \
        --data-urlencode audience=http://example.com \
        http://metadata/computeMetadata/v1/instance/service-accounts/default/identity)
    
  5. ID 토큰이 포함된 요청을 샘플 Cloud Run for Anthos 서비스로 보냅니다.

    curl -s -w"\n" -H "Host: sample.tutorial.example.com" \
        -H "Authorization: Bearer $ID_TOKEN" $(cat external-ip.txt)
    

    출력은 다음과 같습니다.

    OK
    
  6. SSH 세션을 종료합니다.

    exit
    

클라이언트 라이브러리 사용

Python, 자바, Go, Node.js용 Google 인증 클라이언트 라이브러리를 사용하면 편리한 API를 통해 ID 토큰을 가져올 수 있습니다. Python 라이브러리와 IDTokenCredentials 클래스를 사용하려면 다음 단계를 따르세요.

  1. Cloud Shell에서 서비스 계정 사용자 인증 정보를 만들고 다운로드합니다.

    gcloud iam service-accounts keys create service-account.json \
        --iam-account $SERVICE_ACCOUNT_EMAIL
    
  2. Python 가상 환경을 만듭니다.

    virtualenv -p python3 .venv
    
  3. 이 터미널 세션에서 Python 가상 환경을 활성화합니다.

    source .venv/bin/activate
    
  4. 종속 항목을 설치합니다.

    pip install -r requirements.txt
    
  5. ID 토큰을 가져와 샘플 Cloud Run for Anthos 서비스에 요청을 보냅니다.

    python id_token_request.py http://$EXTERNAL_IP http://example.com \
        Host:sample.tutorial.example.com
    

    출력은 다음과 같습니다.

    OK
    
    import os
    import sys
    from google.auth.transport.requests import AuthorizedSession
    from google.oauth2 import service_account
    
    def request(method, url, target_audience=None, service_account_file=None,
                data=None, headers=None, **kwargs):
        """Obtains a Google-issued ID token and uses it to make a HTTP request.
    
        Args:
          method (str): The HTTP request method to use
                ('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE')
          url: The URL where the HTTP request will be sent.
          target_audience (str): Optional, the value to use in the audience
                ('aud') claim of the ID token. Defaults to the value of 'url'
                if not provided.
          service_account_file (str): Optional, the full path to the service
                account credentials JSON file. Defaults to
                '<working directory>/service-account.json'.
          data: Optional dictionary, list of tuples, bytes, or file-like object
                to send in the body of the request.
          headers (dict): Optional dictionary of HTTP headers to send with the
                request.
          **kwargs: Any of the parameters defined for the request function:
                https://github.com/requests/requests/blob/master/requests/api.py
                If no timeout is provided, it is set to 90 seconds.
    
        Returns:
          The page body, or raises an exception if the HTTP request failed.
        """
        # Set target_audience, if missing
        if not target_audience:
            target_audience = url
    
        # Set service_account_file, if missing
        if not service_account_file:
            service_account_file = os.path.join(os.getcwd(),
                                                'service-account.json')
    
        # Set the default timeout, if missing
        if 'timeout' not in kwargs:
            kwargs['timeout'] = 90  # seconds
    
        # Obtain ID token credentials for the specified audience
        creds = service_account.IDTokenCredentials.from_service_account_file(
            service_account_file, target_audience=target_audience)
    
        # Create a session for sending requests with the ID token credentials
        session = AuthorizedSession(creds)
    
        # Send a HTTP request to the provided URL using the Google-issued ID token
        resp = session.request(method, url, data=data, headers=headers, **kwargs)
        if resp.status_code == 403:
            raise Exception('Service account {} does not have permission to '
                            'access the application.'.format(
                                creds.service_account_email))
        elif resp.status_code != 200:
            raise Exception(
                'Bad response from application: {!r} / {!r} / {!r}'.format(
                    resp.status_code, resp.headers, resp.text))
        else:
            return resp.text

Cloud IAP(Identity-Aware Proxy) 문서에는 다른 프로그래밍 언어를 사용하여 Google에서 발급한 ID 토큰을 가져오는 방법을 설명하는 샘플 코드가 있습니다.

IAM Service Account Credentials API 사용

ID 토큰 생성 기능이 있으며 사용하는 프로그래밍 언어에 사용 가능한 Google 인증 클라이언트 라이브러리가 없으면 직접 IAM Service Account Credentials API를 사용할 수 있습니다.

  1. 서비스 계정의 ID 토큰 생성 권한이 있는 커스텀 IAM 역할을 만듭니다.

    gcloud iam roles create serviceAccountIdTokenCreator \
        --project $GOOGLE_CLOUD_PROJECT \
        --description "Impersonate service accounts to create OpenID Connect ID tokens" \
        --permissions "iam.serviceAccounts.getOpenIdToken" \
        --stage GA \
        --title "Service Account ID Token Creator"
    

    커스텀 역할을 만들지 않고 사전 정의된 서비스 계정 토큰 생성자 역할(iam.serviceAccountTokenCreator)을 사용할 수 있지만 이 역할은 Service Account Credentials API로 ID 토큰을 만들 때 필요 없는 추가 권한을 제공합니다.

  2. 이전에 만든 서비스 계정에 대해 커스텀 역할을 직접 부여할 수 있는 정책 결합을 만듭니다.

    gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_EMAIL \
        --member user:$(gcloud config get-value account) \
        --role projects/$GOOGLE_CLOUD_PROJECT/roles/serviceAccountIdTokenCreator
    

    정책 결합이 적용되는 데 다소 시간이 걸릴 수 있습니다. 다음 명령어는 5초마다 정책 결합이 적용되는지 확인합니다. 정책 결합이 적용되면 검사를 중지하고 터미널 프롬프트로 돌아갑니다.

    status_code=""
    while [ "$status_code" != "200" ] ; do
        sleep 5
        echo "Checking permissions"
        status_code=$(curl -s -w "%{http_code}" -o /dev/null -X POST \
            -H "Authorization: Bearer $(gcloud auth print-access-token)" \
            --data-urlencode audience=http://example.com \
            --data-urlencode includeEmail=true \
            "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:generateIdToken")
    done
    
  3. IAM Service Account Credentials API의 generateIdToken 메서드를 사용하여 서비스 계정의 ID 토큰을 생성합니다.

    ID_TOKEN=$(curl -s -X POST \
        -H "Authorization: Bearer $(gcloud auth print-access-token)" \
        --data-urlencode audience=http://example.com \
        --data-urlencode includeEmail=true \
        "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:generateIdToken" | jq -r '.token')
    
  4. ID 토큰이 포함된 요청을 샘플 Cloud Run for Anthos 서비스로 보냅니다.

    curl -s -w"\n" -H "Host: sample.tutorial.example.com" \
        -H "Authorization: Bearer $ID_TOKEN" $EXTERNAL_IP
    

    출력은 다음과 같습니다.

    OK
    

gcloud 명령줄 도구 사용

Google에서 발급한 ID 토큰을 가져오려면 gcloud 명령줄 도구를 사용하면 됩니다. 이 토큰은 사용자 또는 서비스 계정을 식별하며 대화형으로 서비스를 테스트하는 데 유용할 수 있습니다.

  1. 이전에 만든 서비스 계정에 대해 사전 정의된 서비스 계정 토큰 생성자 역할을 직접 부여합니다.

    gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_EMAIL \
        --member user:$(gcloud config get-value account) \
        --role roles/iam.serviceAccountTokenCreator
    

    정책 결합이 적용되는 데 다소 시간이 걸릴 수 있습니다. 다음 명령어는 5초마다 정책 결합이 적용되는지 확인합니다. 정책 결합이 적용되면 검사를 중지하고 터미널 프롬프트로 돌아갑니다.

    status_code=""
    while [ "$status_code" != "200" ] ; do
        sleep 3
        echo "Checking permissions"
        status_code=$(curl -s -w "%{http_code}" -o /dev/null -X POST \
            -H "Authorization: Bearer $(gcloud auth print-access-token)" \
            -H "Content-Type: application/json" \
            -d '{"payload": "{}"}' \
            "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:signJwt")
    done
    
  2. 서비스 계정을 가장하여 ID 토큰을 받습니다.

    ID_TOKEN=$(gcloud auth print-identity-token \
        --audiences http://example.com \
        --impersonate-service-account $SERVICE_ACCOUNT_EMAIL \
        --include-email)
    
  3. ID 토큰이 포함된 요청을 샘플 Cloud Run for Anthos 서비스로 보냅니다.

    curl -s -w"\n" -H "Host: sample.tutorial.example.com" \
        -H "Authorization: Bearer $ID_TOKEN" $EXTERNAL_IP
    

    출력은 다음과 같습니다.

    OK
    

삭제

이 가이드에서 사용된 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 리소스가 포함된 프로젝트를 삭제하거나 프로젝트를 유지하고 개별 리소스를 삭제하세요.

프로젝트 삭제

  1. Cloud Console에서 리소스 관리 페이지로 이동합니다.

    리소스 관리로 이동

  2. 프로젝트 목록에서 삭제할 프로젝트를 선택하고 삭제를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 후 종료를 클릭하여 프로젝트를 삭제합니다.

리소스 삭제

이 가이드에서 사용된 Google Cloud 프로젝트를 유지하려면 개별 리소스를 삭제합니다.

  1. GKE 클러스터를 삭제합니다.

    gcloud container clusters delete $CLUSTER --async --quiet
    
  2. 서비스 계정을 삭제합니다.

    gcloud iam service-accounts delete $SERVICE_ACCOUNT_EMAIL --quiet
    
  3. 서비스 계정 사용자 인증 정보 파일을 삭제합니다.

    rm -f service-account.json
    
  4. Compute Engine 인스턴스를 삭제합니다.

    gcloud compute instances delete $VM --quiet
    

다음 단계