GCP

Cloud CDN 으로 GCS(Google Cloud Storage) 오브젝트 호스팅

misankim 2023. 5. 2. 22:08

Cloud CDN 으로 GCS(Google Cloud Storage) 오브젝트 호스팅


GCS 정적 웹사이트 호스팅
https://cloud.google.com/storage/docs/hosting-static-website?hl=ko 

백엔드 버킷으로 Cloud CDN 설정
https://cloud.google.com/cdn/docs/setting-up-cdn-with-bucket?hl=ko 

서명된 URL 사용
https://cloud.google.com/cdn/docs/using-signed-urls?hl=ko 



# 오브젝트 스토리지 + CDN

AWS 는 S3 + CloudFront 로 정적 웹사이트 혹은 오브젝트 호스팅에 CDN을 적용했다면, 
GCP 는 AWS 와 달리 GCS + LB + Cloud CDN 을 적용해야 정적 웹사이트 혹은 오브젝트 호스팅에 CDN 적용 가능

로드밸런서 콘솔에서 External HTTP LB 생성 후 기존 GCS 버킷을 포함하는 백엔드 버킷 생성 후 lb 의 백엔드로 지정
-> 백엔드 버킷 생성 시 CDN 적용에 대한 설정 추가


# 버킷에 대한 퍼블릭 액세스 필수 여부

https://cloud.google.com/cdn/docs/setting-up-cdn-with-bucket?hl=ko#making_your_bucket_public 

다음은 Cloud Storage 버킷을 공개하는 방법의 대안입니다.
1. 개별 Cloud Storage 버킷을 객체를 공개적으로 읽을 수 있도록 설정합니다. 이 방법은 사용하지 않는 것이 좋습니다.
2. 서명된 URL을 사용합니다.(서명된 쿠키도 가능)

-> 현재 환경에서는 버킷을 퍼블릭 액세스 가능하도록 설정이 불가능하니 서명된 url 을 활용하여 오브젝트를 호스팅
-> 서명된 url 쿼리스트링, 서명된 쿠키를 포함하여 호출해야하기 때문에 웹사이트 호스팅으로는 부적합


# 서명된 URL, 서명된 쿠키를 위한 키 생성

백엔드 버킷 생성 시 "제한된 콘텐츠" 항목에서 "서명된 URL 및 서명된 쿠키를 사용하여 액세스 제한" 선택
-> 서명 키 추가 버튼을 눌러 새 서명키를 생성
-> 키 생성 방법 - 자동 생성
-> 서명 키의 이름과 키 값은 이후 서명된 url/쿠키 생성 시 필요하기 때문에 복사해둠

백엔드 버킷의 서명 키 이름 확인

gcloud compute backend-buckets describe premisan-test-bucket-backend




# GCS 버킷 권한 설정

서명된 URL/쿠키를 통해 버킷의 컨텐츠 액세스가 가능하도록 버킷의 권한을 설정해줌
최초 서명 키 생성 시 Cloud CDN 이 소유하는 서비스 계정이 생성됨(자신의 프로젝트 IAM 서비스 계정에는 보이지 않음)

프로젝트 넘버 확인

gcloud projects describe premisan-test



gsutil iam ch \
  serviceAccount:service-프로젝트_넘버@cloud-cdn-fill.iam.gserviceaccount.com:objectViewer \
  gs://버킷_이름

예시
gsutil iam ch \
  serviceAccount:service-512340712340@cloud-cdn-fill.iam.gserviceaccount.com:objectViewer \
  gs://premisan-test-bucket-123



명령어 수행하면 버킷의 권한 메뉴에 아래와 같이 추가됨

주체 - service-512340712340@cloud-cdn-fill.iam.gserviceaccount.com
역할 - 저장소 개체 뷰어




# 커맨드로 서명된 url 생성

gcloud compute sign-url \
  "URL" \
  --key-name KEY_NAME \
  --key-file KEY_FILE_NAME \
  --expires-in TIME_UNTIL_EXPIRATION \

예시
-> 서명 키 값을 my.key 이라는 이름의 파일로 저장한 경우
❯ gcloud compute sign-url \
  "http://35.190.125.14/sky.jpeg" \
  --key-name premisan-test-cdn-key \
  --key-file my.key \
  --expires-in 5m



출력

signedUrl: http://35.190.125.14/sky.jpeg?Expires=1648541046&KeyName=premisan-test-cdn-key&Signature=W1kFIU7-eQMVs-_DgdHTioLaM7I=




# 서명된 URL, 서명된 쿠키를 생성하는 python 스크립트

소스 코드 원본
https://github.com/GoogleCloudPlatform/python-docs-samples/blob/HEAD/cdn/snippets.py

vim signed.py

import argparse
import base64
import datetime
import hashlib
import hmac

from six.moves import urllib

def sign_url(url, key_name, base64_key, expiration_time):
    stripped_url = url.strip()
    parsed_url = urllib.parse.urlsplit(stripped_url)
    query_params = urllib.parse.parse_qs(
        parsed_url.query, keep_blank_values=True)
    epoch = datetime.datetime.utcfromtimestamp(0)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    url_pattern = u'{url}{separator}Expires={expires}&KeyName={key_name}'

    url_to_sign = url_pattern.format(
            url=stripped_url,
            separator='&' if query_params else '?',
            expires=expiration_timestamp,
            key_name=key_name)

    digest = hmac.new(
        decoded_key, url_to_sign.encode('utf-8'), hashlib.sha1).digest()
    signature = base64.urlsafe_b64encode(digest).decode('utf-8')

    signed_url = u'{url}&Signature={signature}'.format(
            url=url_to_sign, signature=signature)

    print(signed_url)

def sign_url_prefix(url, url_prefix, key_name, base64_key, expiration_time):
    stripped_url = url.strip()
    parsed_url = urllib.parse.urlsplit(stripped_url)
    query_params = urllib.parse.parse_qs(
        parsed_url.query, keep_blank_values=True)
    encoded_url_prefix = base64.urlsafe_b64encode(
            url_prefix.strip().encode('utf-8')).decode('utf-8')
    epoch = datetime.datetime.utcfromtimestamp(0)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    policy_pattern = u'URLPrefix={encoded_url_prefix}&Expires={expires}&KeyName={key_name}'
    policy = policy_pattern.format(
            encoded_url_prefix=encoded_url_prefix,
            expires=expiration_timestamp,
            key_name=key_name)

    digest = hmac.new(
            decoded_key, policy.encode('utf-8'), hashlib.sha1).digest()
    signature = base64.urlsafe_b64encode(digest).decode('utf-8')

    signed_url = u'{url}{separator}{policy}&Signature={signature}'.format(
            url=stripped_url,
            separator='&' if query_params else '?',
            policy=policy,
            signature=signature)

    print(signed_url)

def sign_cookie(url_prefix, key_name, base64_key, expiration_time):
    encoded_url_prefix = base64.urlsafe_b64encode(
            url_prefix.strip().encode('utf-8')).decode('utf-8')
    epoch = datetime.datetime.utcfromtimestamp(0)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    policy_pattern = u'URLPrefix={encoded_url_prefix}:Expires={expires}:KeyName={key_name}'
    policy = policy_pattern.format(
            encoded_url_prefix=encoded_url_prefix,
            expires=expiration_timestamp,
            key_name=key_name)

    digest = hmac.new(
            decoded_key, policy.encode('utf-8'), hashlib.sha1).digest()
    signature = base64.urlsafe_b64encode(digest).decode('utf-8')

    signed_policy = u'Cloud-CDN-Cookie={policy}:Signature={signature}'.format(
            policy=policy, signature=signature)
    print(signed_policy)

url_prefix = 'http://35.190.125.14/' # 액세스를 허용할 경로를 도메인 포함하여 지정
key_name = 'premisan-test-cdn-key' # 서명 키의 이름
base64_key = 'b00yH-00Op00p9_4X00aiQ==' # 서명 키의 값
expiration_time = datetime.datetime(9999, 12, 31, 00, 00, 00, 000000) # 서명된 url 의 만료 시간 지정(UTC 기준)

print("sign url is")
sign_url_prefix(url_prefix, url_prefix, key_name, base64_key, expiration_time)
print("")
print("sign cookie is")
sign_cookie(url_prefix, key_name, base64_key, expiration_time)




출력 예시

❯ python3 signed.py
sign url is
http://35.190.125.14/?URLPrefix=aHR0cDovLzM1LjE5MC4xMjUuMTQv&Expires=253402214400&KeyName=premisan-test-cdn-key&Signature=ZXNyTmHIGAl-gRiT4jyWUjfJCMc=

sign cookie is
Cloud-CDN-Cookie=URLPrefix=aHR0cDovLzM1LjE5MC4xMjUuMTQv:Expires=253402214400:KeyName=premisan-test-cdn-key:Signature=EYpU1Sab29_5ZPSQ0Zepyn9Cg2Q=




# 서명된 쿠키 사용

https://cloud.google.com/cdn/docs/using-signed-cookies?hl=ko 

서명된 url 과 동일한 스크립트로 서명된 쿠키 생성

출력된 서명된 쿠키

Cloud-CDN-Cookie=URLPrefix=aHR0cDovLzM1LjE5MC4xMjUuMTQv:Expires=253402214400:KeyName=premisan-test-cdn-key:Signature=EYpU1Sab29_5ZPSQ0Zepyn9Cg2Q=



호출 테스트

curl -v -H 'Cookie: Cloud-CDN-Cookie=URLPrefix=aHR0cDovLzM1LjE5MC4xMjUuMTQv:Expires=253402214400:KeyName=premisan-test-cdn-key:Signature=EYpU1Sab29_5ZPSQ0Zepyn9Cg2Q=' \
http://35.190.125.14/image/sky.jpeg  > /dev/null



서명된 쿠키 없이 호출 시

curl -v http://35.190.125.14/image/sky.jpeg  > /dev/null