概要
KMSを利用して、認証情報などの秘密を暗号化・復号化する方法を紹介します
サンプルコードと環境
サンプルコード:
「GCP で Google Calendar を CalDAV サーバに複製する」記事で紹介した バッチスクリプトのコード を例にとります。コードを動かしたい人は、この記事よりも readme.md 見てもらうと早いです。
- (前記事)対策前のコード: https://github.com/coleyon/gcalapi-caldav-sample/tree/master
- (今記事)対策後のコード: https://github.com/coleyon/gcalapi-caldav-sample/tree/kms-enc-dec (Diff)
対策前のコードでは Calendar API を叩くためにGCPサービスアカウントの鍵 credential.json
ファイルを認証情報として利用しますが、暗号化せずコードに同梱しています。対策後のコードでは、KMSを利用して鍵 credential.json
の内容を暗号化し、かつ暗号化した鍵をコードに含めない(Cloud Functions実行時に環境変数で与える)ことでセキュリティを向上しています。
環境:
- 生JSON鍵を得てKMSで対称暗号化し、バイナリ暗号鍵を得る
- バイナリ暗号鍵をASCII化し、Cloud Functions の環境変数にセットする
- コードはgit、暗号鍵は環境変数をソースとして、Cloud Functions上にデプロイする
- Cloud Functions上のスクリプトは、暗号鍵をKMS APIで復号化する
GKE KeyRingを作る
暗号鍵メニューから、キーリングを適当な名前で作ります。今回は、今後も増えるだろうサービスアカウントのクレデンシャルをたくさんぶら下げるためのキーリング名にしました。
GKE Keyを作る
キーリングにぶら下げるキーを作ります。Calendar APIアクセス用サービスアカウントのJSON形式のクレデンシャルである事を端的に示すキー名で作りました。目的は対称暗号化/復号化
です。
サービスアカウントは強い権限(プロジェクトの管理者)を持たせる事が多いと思いますが、ローテーションを自動でかけて同じ暗号を使い続けないように設定する事もできます。
秘密を暗号化する
作業に先立って、生クレデンシャルJSONファイルは、非公開のStorage private-bucket
を介してアップロードします。
暗号化はCloudShellで作業をします。サービスアカウントのJSON形式生クレデンシャルcredentials.json
を暗号化して、バイナリファイルcredentials.json.enc
を得ます。
$ gsutil cp gs://private-bucket/credentials.json ./
$ gcloud kms encrypt \
--plaintext-file=./credentials.json \
--ciphertext-file=credentials.json.enc \
--location=global \
--keyring=service-account-credentials \
--key=gcal-api-json-cred \
&& rm ./credentials.json
暗号化文字列を得る
アプリケーションで取り扱いやすい様にPythonでcredentials.json.enc
を文字列に変換します。
$ python
>>> import base64, io
>>> with io.open('credentials.json.enc', 'rb') as f:
>>> base64.b64encode(f.read()).decode('ascii')
QOsAErqw....bI= # 暗号化されたクレデンシャル文字列
[1]+ Stopped python
$ exit
logout
There are stopped jobs.
$
秘密を復号化する
次のようなコードでもって、復号化して使います。
import json
from base64 import b64decode
from google.oauth2 import service_account
from googleapiclient.discovery import build
def decrypt_credectial():
kms_client = build('cloudkms', 'v1')
key_path = 'projects/{pj}/locations/{loc}/keyRings/{key_ring}/cryptoKeys/{key}'.format(
pj='GoogleのプロジェクトID',
loc='KMSキーリングのロケーション。本例だとglobal',
key_ring='KMSキーリング名',
key='KMSキー名')
crypto_keys = kms_client.projects().locations().keyRings().cryptoKeys()
kms_request = crypto_keys.decrypt(name=key_path, body={'ciphertext': '暗号化クレデンシャル'})
kms_response = kms_request.execute()
return json.loads(b64decode(kms_response['plaintext'].encode('ascii')))
creds = service_account.Credentials.from_service_account_info(
decrypt_credectial(),
scopes=[
'https://www.googleapis.com/auth/calendar.events.readonly',
'https://www.googleapis.com/auth/calendar.readonly'
]
).with_subject('username@gsuite-domain')
前記事のコードと比べてこのような違いがあります。生クレデンシャルを文字列やファイルとしてソースコードに含める必要が無くなって、安心感が出ました。