1. 개요
보안을 개선하기 위해 Cloud Run에 서비스를 배포하는 기본 단계를 수정한 다음 배포된 앱에 안전하게 액세스하는 방법을 알아봅니다. 이 앱은 Cymbal Eats 애플리케이션의 '파트너 등록 서비스'로, Cymbal Eats와 협력하여 음식 주문을 처리하는 회사에서 사용합니다.
학습할 내용
Cloud Run에 앱을 배포하기 위한 최소한의 기본 단계를 약간만 변경하면 보안을 크게 강화할 수 있습니다. 기존 앱 및 배포 안내를 사용하여 배포 단계를 변경하여 배포된 앱의 보안을 개선합니다.
그런 다음 앱 액세스를 승인하고 승인된 요청을 실행하는 방법을 확인할 수 있습니다.
이 문서에서는 애플리케이션 배포 보안을 포괄적으로 살펴보지는 않습니다. 대신 앞으로 앱을 배포할 때 보안을 개선하기 위해 취할 수 있는 조치를 간단히 살펴봅니다.
2. 설정 및 요구사항
자습형 환경 설정
- Google Cloud Console에 로그인하여 새 프로젝트를 만들거나 기존 프로젝트를 재사용합니다. 아직 Gmail이나 Google Workspace 계정이 없는 경우 계정을 만들어야 합니다.
- 프로젝트 이름은 이 프로젝트 참가자의 표시 이름입니다. 이는 Google API에서 사용하지 않는 문자열이며 언제든지 업데이트할 수 있습니다.
- 프로젝트 ID는 모든 Google Cloud 프로젝트에서 고유하며, 변경할 수 없습니다(설정된 후에는 변경할 수 없음). Cloud 콘솔은 고유한 문자열을 자동으로 생성합니다. 일반적으로는 신경 쓰지 않아도 됩니다. 대부분의 Codelab에서는 프로젝트 ID (일반적으로
PROJECT_ID
로 식별됨)를 참조해야 합니다. 생성된 ID가 마음에 들지 않으면 다른 임의 ID를 생성할 수 있습니다. 또는 직접 시도해 보고 사용 가능한지 확인할 수도 있습니다. 이 단계 이후에는 변경할 수 없으며 프로젝트 기간 동안 유지됩니다. - 참고로 세 번째 값은 일부 API에서 사용하는 프로젝트 번호입니다. 이 세 가지 값에 대한 자세한 내용은 문서를 참고하세요.
- 다음으로 Cloud 리소스/API를 사용하려면 Cloud 콘솔에서 결제를 사용 설정해야 합니다. 이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 튜토리얼이 끝난 후에 요금이 청구되지 않도록 리소스를 종료하려면 만든 리소스 또는 전체 프로젝트를 삭제하면 됩니다. Google Cloud 새 사용자에게는 미화 $300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.
Cloud Shell 활성화
- Cloud Console에서 Cloud Shell 활성화를 클릭합니다.
이전에 Cloud Shell을 시작하지 않았다면 Cloud Shell에 대한 설명이 포함된 중간 화면 (스크롤해야 볼 수 있는 부분)이 표시됩니다. 이 경우 계속을 클릭합니다 (이후 다시 표시되지 않음). 이 일회성 화면은 다음과 같습니다.
Cloud Shell을 프로비저닝하고 연결하는 데 몇 분 정도만 걸립니다.
이 가상 머신에는 필요한 개발 도구가 모두 포함되어 로드됩니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 이 Codelab에서 대부분의 작업은 브라우저나 Chromebook만 사용하여 수행할 수 있습니다.
Cloud Shell에 연결되면 인증이 완료되었고 프로젝트가 해당 프로젝트 ID로 이미 설정된 것을 볼 수 있습니다.
- Cloud Shell에서 다음 명령어를 실행하여 인증되었는지 확인합니다.
gcloud auth list
명령어 결과
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Cloud Shell에서 다음 명령어를 실행하여 gcloud 명령어가 프로젝트를 알고 있는지 확인합니다.
gcloud config list project
명령어 결과
[core] project = <PROJECT_ID>
또는 다음 명령어로 설정할 수 있습니다.
gcloud config set project <PROJECT_ID>
명령어 결과
Updated property [core/project].
환경 설정
이 실습에서는 Cloud Shell 명령줄에서 명령어를 실행합니다. 일반적으로 명령어를 복사하여 그대로 붙여넣을 수 있지만, 경우에 따라 자리표시자 값을 올바른 값으로 변경해야 할 수 있습니다.
- 나중에 명령어에서 사용할 수 있도록 환경 변수를 프로젝트 ID로 설정합니다.
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=partner-registration-service
- 앱을 실행할 Cloud Run 서비스 API, NoSQL 데이터 저장소를 제공할 Firestore API, 배포 명령어에서 사용할 Cloud Build API, 빌드 시 애플리케이션 컨테이너를 보관하는 데 사용할 Artifact Registry를 사용 설정합니다.
gcloud services enable \
run.googleapis.com \
firestore.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com
- Native 모드에서 Firestore 데이터베이스를 초기화합니다. 이 명령어는 App Engine API를 사용하므로 먼저 사용 설정해야 합니다.
명령어는 App Engine의 리전을 지정해야 합니다. 이 리전은 사용하지 않지만 이전 사유로 만들어야 합니다. 또한 데이터베이스의 리전도 지정해야 합니다. App Engine에는 us-central을, 데이터베이스에는 nam5를 사용합니다. nam5는 미국 멀티 리전 위치입니다. 멀티 리전 위치는 데이터베이스의 가용성과 내구성을 극대화합니다.
gcloud services enable appengine.googleapis.com
gcloud app create --region=us-central
gcloud firestore databases create --region=nam5
- 샘플 앱 저장소를 클론하고 디렉터리로 이동
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git
cd cymbal-eats/partner-registration-service
3. 리드미 검토
편집기를 열고 앱을 구성하는 파일을 살펴봅니다. 이 앱을 배포하는 데 필요한 단계를 설명하는 README.md를 확인합니다. 이러한 단계 중 일부에는 고려해야 할 암시적 또는 명시적 보안 결정이 포함될 수 있습니다. 여기에 설명된 대로 배포된 앱의 보안을 개선하기 위해 다음과 같은 몇 가지 선택사항을 변경합니다.
3단계 - npm install
실행
앱에 사용되는 서드 파티 소프트웨어의 출처와 무결성을 파악하는 것이 중요합니다. 소프트웨어 공급망 보안 관리는 Cloud Run에 배포된 앱뿐만 아니라 모든 소프트웨어 빌드와 관련이 있습니다. 이 실습은 배포에 중점을 두었으므로 이 영역은 다루지 않지만 이 주제를 별도로 조사해 보세요.
4단계 및 5단계 - deploy.sh
수정 및 실행
이 단계에서는 대부분의 옵션을 기본값으로 두고 앱을 Cloud Run에 배포합니다. 이 단계를 수정하여 다음 두 가지 주요 방법으로 배포를 더 안전하게 만듭니다.
- 인증되지 않은 액세스는 허용하지 마세요. 탐색 중에 무언가를 시도해 볼 때는 편리할 수 있지만, 이 웹 서비스는 상업 파트너가 사용하는 웹 서비스이므로 항상 사용자를 인증해야 합니다.
- 애플리케이션이 필요한 권한만으로 조정된 전용 서비스 계정을 사용해야 한다고 지정합니다. 기본 서비스 계정은 필요 이상으로 많은 API 및 리소스 액세스 권한을 보유할 수 있습니다. 이를 최소 권한의 원칙이라고 하며 애플리케이션 보안의 기본 개념입니다.
6~11단계 - 샘플 웹 요청을 실행하여 올바른 동작 확인
이제 애플리케이션 배포에 인증이 필요하므로 이러한 요청에는 요청자의 신원 증빙 서류가 포함되어야 합니다. 이러한 파일을 변경하는 대신 명령줄에서 직접 요청합니다.
4. 서비스를 안전하게 배포
deploy.sh
스크립트에서 인증되지 않은 액세스를 허용하지 않고 최소 권한으로 전용 서비스 계정을 사용하는 두 가지 변경사항이 필요했습니다.
먼저 새 서비스 계정을 만든 다음 deploy.sh
스크립트를 수정하여 해당 서비스 계정을 참조하고 인증되지 않은 액세스를 허용하지 않도록 합니다.그런 다음 수정된 스크립트를 실행하여 서비스를 배포한 후 수정된 deploy.sh 스크립트를 실행할 수 있습니다.
서비스 계정을 만들고 Firestore/Datastore에 필요한 액세스 권한을 부여합니다.
gcloud iam service-accounts create partner-sa
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:partner-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role=roles/datastore.user
deploy.sh
수정
deploy.sh
파일을 수정하여 인증되지 않은 액세스를 허용하지 않도록(–no-allow-unauthenticated) 배포된 앱의 새 서비스 계정(–service-account)을 지정합니다. GOOGLE_PROJECT_ID를 자체 프로젝트 ID로 수정합니다.
아래와 같이 처음 두 줄을 삭제하고 다른 세 줄을 변경합니다.
gcloud run deploy $SERVICE_NAME \
--source . \
--platform managed \
--region ${REGION} \
--no-allow-unauthenticated \
--project=$PROJECT_ID \
--service-account=partner-sa@${PROJECT_ID}.iam.gserviceaccount.com
서비스 배포
명령줄에서 deploy.sh
스크립트를 실행합니다.
./deploy.sh
배포가 완료되면 명령어 출력의 마지막 줄에 새 앱의 서비스 URL이 표시됩니다. URL을 환경 변수에 저장합니다.
export SERVICE_URL=<URL from last line of command output>
이제 curl
도구를 사용하여 앱에서 주문을 가져옵니다.
curl -i -X GET $SERVICE_URL/partners
curl
명령어의 -i
플래그는 출력에 응답 헤더를 포함하도록 지시합니다. 출력의 첫 번째 줄은 다음과 같습니다.
HTTP/2 403
앱이 인증되지 않은 요청을 허용하지 않는 옵션으로 배포되었습니다. 이 curl 명령어에는 인증 정보가 포함되어 있지 않으므로 Cloud Run에서 거부됩니다. 실제로 배포된 애플리케이션은 이 요청을 실행하거나 데이터를 수신하지도 않습니다.
5. 인증된 요청 실행
배포된 앱은 웹 요청을 통해 호출되며, 이제 Cloud Run에서 이를 허용하려면 인증을 받아야 합니다. 웹 요청은 다음 형식의 Authorization
헤더를 포함하여 인증됩니다.
Authorization: Bearer
identity-token
identity-token은 신뢰할 수 있는 인증 공급업체에서 발급하는 단기 암호화 서명된 인코딩된 문자열입니다. 이 경우 만료되지 않고 유효한 Google에서 발급한 ID 토큰이 필요합니다.
사용자 계정으로 요청하기
Google Cloud CLI 도구는 기본 인증된 사용자에게 토큰을 제공할 수 있습니다. 다음 명령어를 실행하여 본인의 계정에 대한 ID 토큰을 가져와 ID_TOKEN 환경 변수에 저장합니다.
export ID_TOKEN=$(gcloud auth print-identity-token)
기본적으로 Google에서 발급한 ID 토큰은 1시간 동안 유효합니다. 다음 curl 명령어를 실행하여 이전에 승인되지 않아 거부된 요청을 다시 실행합니다. 이 명령어에는 필요한 헤더가 포함됩니다.
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $ID_TOKEN"
명령어 출력은 HTTP/2 200
로 시작해야 하며, 이는 요청이 허용되고 처리되고 있음을 나타냅니다. 1시간 후에 이 요청을 다시 시도하면 토큰이 만료되어 실패합니다. 응답 본문은 출력의 끝에 빈 줄 뒤에 있습니다.
{"status":"success","data":[]}
아직 파트너가 없습니다.
두 개의 curl
명령어를 사용하여 디렉터리의 샘플 JSON 데이터를 사용하여 파트너를 등록합니다.
curl -X POST \
-H "Authorization: Bearer $ID_TOKEN" \
-H "Content-Type: application/json" \
-d "@example-partner.json" \
$SERVICE_URL/partner
및
curl -X POST \
-H "Authorization: Bearer $ID_TOKEN" \
-H "Content-Type: application/json" \
-d "@example-partner2.json" \
$SERVICE_URL/partner
이전의 GET 요청을 반복하여 등록된 모든 파트너를 확인합니다.
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $ID_TOKEN"
등록된 두 파트너에 관한 정보를 제공하는 훨씬 더 많은 콘텐츠가 포함된 JSON 데이터가 표시됩니다.
승인되지 않은 계정으로 요청
인증된 사용자 (내 계정)가 승인되었기 때문에 인증된 요청이 마지막 단계에서 성공했습니다. 즉, 계정에 앱을 호출할 권한이 있었습니다. 인증된 모든 계정에 이 권한이 부여되는 것은 아닙니다.
이전 요청에 사용된 기본 계정은 앱이 포함된 프로젝트를 만든 계정이므로 승인되었습니다. 기본적으로 이 계정에는 계정의 Cloud Run 애플리케이션을 호출할 권한이 부여됩니다. 필요한 경우 이 권한을 취소할 수 있으며 이는 프로덕션 애플리케이션에서 바람직합니다. 지금은 그렇게 하지 않고 할당된 권한이나 역할이 없는 새 서비스 계정을 만들고 이를 사용하여 배포된 앱에 액세스해 보겠습니다.
tester
라는 서비스 계정을 만듭니다.
gcloud iam service-accounts create tester
- 이전에 기본 계정의 ID 토큰을 가져온 것과 거의 동일한 방식으로 새 계정의 ID 토큰을 가져올 수 있습니다. 하지만 이렇게 하려면 기본 계정에 서비스 계정을 명의 도용할 권한이 있어야 합니다. 계정에 이 권한을 부여합니다.
export USER_EMAIL=$(gcloud config list account --format "value(core.account)")
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="user:$USER_EMAIL" \
--role=roles/iam.serviceAccountTokenCreator
- 이제 다음 명령어를 실행하여 이 새 계정의 ID 토큰을 TEST_IDENTITY 환경 변수에 저장합니다. 명령어에 오류 메시지가 표시되면 1~2분 정도 기다린 후 다시 시도하세요.
export TEST_TOKEN=$( \
gcloud auth print-identity-token \
--impersonate-service-account \
"tester@$PROJECT_ID.iam.gserviceaccount.com" \
)
- 이전과 같이 인증된 웹 요청을 실행하지만 이 ID 토큰을 사용합니다.
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $TEST_TOKEN"
요청이 인증되었지만 승인되지 않았으므로 명령어 출력은 다시 HTTP/2 403
로 시작합니다. 새 서비스 계정에 이 앱을 호출할 권한이 없습니다.
계정 승인
Cloud Run 서비스를 요청하려면 사용자 또는 서비스 계정에 Cloud Run 서비스에 대한 Cloud Run 호출자 역할이 있어야 합니다. 다음 명령어를 사용하여 테스터 서비스 계정에 해당 역할을 부여합니다.
export REGION=us-central1
gcloud run services add-iam-policy-binding ${SERVICE_NAME} \
--member="serviceAccount:tester@$PROJECT_ID.iam.gserviceaccount.com" \
--role=roles/run.invoker \
--region=${REGION}
새 역할이 업데이트될 때까지 1~2분 정도 기다린 후 인증된 요청을 반복합니다. 처음 저장한 후 1시간 이상 지났다면 새 TEST_TOKEN을 저장합니다.
curl -i -X GET $SERVICE_URL/partners \
-H "Authorization: Bearer $TEST_TOKEN"
이제 명령어 출력은 HTTP/1.1 200 OK
로 시작하고 마지막 줄에는 JSON 응답이 포함됩니다. 이 요청은 Cloud Run에서 수락하고 앱에서 처리했습니다.
6. 프로그램 인증과 사용자 인증 비교
지금까지 인증된 요청은 curl
명령줄 도구를 사용했습니다. 그 밖에 다른 도구와 프로그래밍 언어도 사용할 수 있습니다. 그러나 일반 웹페이지가 있는 웹브라우저를 사용하여 인증된 Cloud Run 요청을 할 수 없습니다. 사용자가 링크를 클릭하거나 버튼을 클릭하여 웹페이지에서 양식을 제출하면 브라우저는 Cloud Run에서 인증된 요청에 필요한 Authorization
헤더를 추가하지 않습니다.
Cloud Run의 기본 제공 인증 메커니즘은 최종 사용자가 아닌 프로그램에서 사용하도록 설계되었습니다.
참고:
Cloud Run은 사용자 대상 웹 애플리케이션을 호스팅할 수 있지만 이러한 유형의 애플리케이션은 사용자 웹브라우저의 인증되지 않은 요청을 허용하도록 Cloud Run을 설정해야 합니다. 애플리케이션에 사용자 인증이 필요한 경우 Cloud Run에 요청하는 대신 애플리케이션에서 이를 처리해야 합니다. 애플리케이션은 Cloud Run 외부의 웹 애플리케이션과 동일한 방식으로 이를 실행할 수 있습니다. 이를 실행하는 방법은 이 Codelab의 범위에 해당하지 않습니다.
지금까지 예시 요청에 대한 응답은 웹페이지가 아닌 JSON 객체였습니다. 이 파트너 등록 서비스는 프로그램에서 사용하도록 설계되었으며 JSON은 프로그램에서 사용하기 편리한 형식입니다. 다음으로 이 데이터를 소비하고 사용하는 프로그램을 작성하고 실행합니다.
Python 프로그램의 인증된 요청
프로그램은 표준 HTTP 웹 요청을 통해 보안 Cloud Run 애플리케이션에 인증된 요청을 할 수 있지만 Authorization
헤더를 포함해야 합니다. 이러한 프로그램의 유일한 새로운 과제는 헤더에 배치할 유효하고 만료되지 않은 ID 토큰을 가져오는 것입니다. 이 토큰은 Google Cloud Identity and Access Management (IAM)를 사용하여 Cloud Run에서 유효성을 검사하므로 IAM에서 인식하는 기관에서 토큰을 발급하고 서명해야 합니다. 프로그램에서 이러한 토큰 발급을 요청하는 데 사용할 수 있는 다양한 언어의 클라이언트 라이브러리가 있습니다. 이 예시에서 사용할 클라이언트 라이브러리는 Python google.auth
입니다. 일반적으로 웹 요청을 처리하는 데는 여러 Python 라이브러리가 있습니다. 이 예에서는 널리 사용되는 requests
모듈을 사용합니다.
첫 번째 단계는 두 클라이언트 라이브러리를 설치하는 것입니다.
pip install google-auth
pip install requests
기본 사용자의 ID 토큰을 요청하는 Python 코드는 다음과 같습니다.
credentials, _ = google.auth.default()
credentials.refresh(google.auth.transport.requests.Request())
identity_token = credentials.id_token
Cloud Shell 또는 자체 컴퓨터의 표준 터미널 셸과 같은 명령어 셸을 사용하는 경우 기본 사용자는 해당 셸 내에서 인증된 사용자입니다. Cloud Shell에서는 일반적으로 Google에 로그인한 사용자입니다. 그 외의 경우에는 gcloud auth login
또는 다른 gcloud
명령어로 인증된 사용자입니다. 사용자가 로그인한 적이 없는 경우 기본 사용자가 없으므로 이 코드가 실패합니다.
다른 프로그램에 요청하는 프로그램의 경우 일반적으로 사용자의 ID가 아닌 요청 프로그램의 ID를 사용해야 합니다. 이러한 경우에 사용하는 것이 바로 서비스 계정입니다. Cloud Firestore와 같은 API 요청을 할 때 사용하는 ID를 제공하는 전용 서비스 계정으로 Cloud Run 서비스를 배포했습니다. 프로그램이 Google Cloud 플랫폼에서 실행되면 클라이언트 라이브러리는 할당된 서비스 계정을 기본 ID로 자동으로 사용하기 때문에 두 경우 모두 동일한 프로그램 코드가 작동합니다.
추가된 Authorization 헤더로 요청을 실행하는 Python 코드는 다음과 같습니다.
auth_header = {"Authorization": "Bearer " + identity_token}
response = requests.get(url, headers=auth_header)
다음의 전체 Python 프로그램은 Cloud Run 서비스에 인증된 요청을 보내 등록된 모든 파트너를 가져온 다음 이름과 할당된 ID를 출력합니다. 아래 명령어를 복사하여 실행하여 이 코드를 print_partners.py
파일에 저장합니다.
cat > ./print_partners.py << EOF
def print_partners():
import google.auth
import google.auth.transport.requests
import requests
credentials, _ = google.auth.default()
credentials.refresh(google.auth.transport.requests.Request())
identity_token = credentials.id_token
auth_header = {"Authorization": "Bearer " + identity_token}
response = requests.get("${SERVICE_URL}/partners", headers=auth_header)
parsed_response = response.json()
partners = parsed_response["data"]
for partner in partners:
print(f"{partner['partnerId']}: {partner['name']}")
print_partners()
EOF
셸 명령어를 사용하여 이 프로그램을 실행합니다. 프로그램에서 이러한 사용자 인증 정보를 사용할 수 있도록 먼저 기본 사용자로 인증해야 합니다. 아래의 gcloud auth 명령어를 실행합니다.
gcloud auth application-default login
안내에 따라 로그인을 완료합니다. 그런 다음 명령줄에서 프로그램을 실행합니다.
python print_partners.py
출력은 다음과 같습니다.
10102: Zippy food delivery
67292: Foodful
프로그램의 요청이 내 ID로 인증되었기 때문에 Cloud Run 서비스에 도달했습니다. 또한 이 프로젝트의 소유자이므로 기본적으로 프로그램을 실행할 권한이 있습니다. 이 프로그램은 서비스 계정의 ID로 실행되는 경우가 더 일반적입니다. Cloud Run 또는 App Engine과 같은 대부분의 Google Cloud 제품에서 실행되는 경우 기본 ID는 서비스 계정이며 개인 계정 대신 사용됩니다.
7. 축하합니다.
축하합니다. Codelab을 완료했습니다.
다음 단계:
다른 Cymbal Eats Codelab을 살펴보세요.
- Eventarc로 Cloud Workflows 트리거
- Cloud Storage에서 이벤트 처리 트리거
- Cloud Run에서 비공개 CloudSQL에 연결
- Cloud Run에서 완전 관리형 데이터베이스에 연결
- IAP (Identity-Aware Proxy)로 서버리스 애플리케이션 보호
- Cloud Scheduler로 Cloud Run 작업 트리거
- Cloud Run 인그레스 트래픽 보안
- GKE Autopilot에서 비공개 AlloyDB에 연결하기
삭제
이 튜토리얼에서 사용된 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 리소스가 포함된 프로젝트를 삭제하거나 프로젝트를 유지하고 개별 리소스를 삭제하세요.
프로젝트 삭제
비용이 청구되지 않도록 하는 가장 쉬운 방법은 튜토리얼에서 만든 프로젝트를 삭제하는 것입니다.