omohayui blog

おも‐はゆ・い【面映ゆい】[形][文]おもはゆ・し[ク]《顔を合わせるとまばゆく感じられる意》きまりが悪い。てれくさい。

Cloud KMS で暗号化

Cloud KMS とは

cloud.google.com

Cloud KMS は GCP で提供されている鍵管理サービスです。(Cloud Key Management Service )
GAE上で秘匿情報を管理したいときに Datastoreにそのまま入れるのも...
secret.yaml を作ってリポジトリ管理外にして app.yaml で include してというのも...
と思っていたら、Cloud KMS がとっくにSLA適用されていて結構使っている人が多いので触ってみることにしました。

Cloud KMS のオブジェクト階層

アクセス制御を管理するために設計された階層構造に、CryptKey(暗号鍵)を格納します。

  • Project - プロジェクト
  • Location - ロケーション
  • KeyRing - キーリング
  • Key - 鍵
  • Key Version - 鍵バージョン

この構造のリソース (KeyRing, CryptKey) へのアクセスは、Cloud IAM で制御できます。

手順

※ 注意点

  • KeyRing と Key は無効化はできますが削除ができないので、自分みたいに適当な KeyRing を作成してしまうとゴミが残ってしまうので気をつけましょう...

Cloud KMS API を有効にする

KeyRing を作成する

  • GCP Console > Security > Cryptographic Keys を開く
  • [Create Key Ring] をクリック
  • Key ring name を入力し Key ring location を選択して [Create]
    • KeyRing は、Key をグルーピングする粒度の名前
    • KeyRing Location は基本 global で良いらしいが、公式ドキュメント を見るとパフォーマンスを考慮するなら特定region(サービスを提供する地域)を選択せよとのこと

f:id:omohayui:20181021200508p:plain:w500

Key を作成

  • KeyRing を作成したら [Create Key] クリック
  • Key Name、Purpose、Rotation 等を入力して [Create]
    • Rotation は、新しく Key Version が作成されるスケジュールの設定
    • Rotation を設定しても古い Version の Key が使えなくなるわけではない
    • 古い Key Version を無効化するには、 破棄のスケジュール登録 が別途必要になる

作成フォーム

f:id:omohayui:20181021211257p:plain:w500

作成後

f:id:omohayui:20181021200157p:plain:w600

Key を使って暗号化/復号化

Cloud KMS のクラインアントライブラリ には C#/Go/Java/Node.js/PHP/Python/Ruby が用意されてます。

Goで書くとこんなイメージ

base64エンコード/デコードする必要があるくらいです。

import (
    "encoding/base64"
    "fmt"

    "golang.org/x/net/context"
    "golang.org/x/oauth2/google"
    "google.golang.org/api/cloudkms/v1"
)

// KMSClient is a client to access Cloud KMS
type KMSClient struct {
    keypath string
    service *cloudkms.ProjectsLocationsKeyRingsCryptoKeysService
}

// KMSOptions means the options for constructor
type KMSOptions struct {
    ProjectID, Location, KeyRing, CryptoKey string
}

// NewKMSClient makes KMS client
func NewKMSClient(ctx context.Context, opt *KMSOptions) (*KMSClient, error) {
    client, err := google.DefaultClient(ctx, cloudkms.CloudPlatformScope)
    if err != nil {
        return nil, fmt.Errorf("failed DefaultClient. error: %v", err)
    }
    cloudkmsService, err := cloudkms.New(client)
    if err != nil {
        return nil, fmt.Errorf("failed cloudkms.New. error: %v", err)
    }

    keyPath := fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s",
        opt.ProjectID, opt.Location, opt.KeyRing, opt.CryptoKey)

    return &KMSClient{
        keypath: keyPath,
        service: cloudkmsService.Projects.Locations.KeyRings.CryptoKeys,
    }, nil
}

// Encrypt encrypts plaintext
func (k *KMSClient) Encrypt(plaintext string) (string, error) {
    resp, err := k.service.Encrypt(k.keypath, &cloudkms.EncryptRequest{
        Plaintext: base64.StdEncoding.EncodeToString([]byte(plaintext)),
    }).Do()
    if err != nil {
        return "", fmt.Errorf("failed to encrypt. error: %v", err)
    }
    return resp.Ciphertext, nil
}

// Decrypt decrypts ciphertext
func (k *KMSClient) Decrypt(ciphertext string) (string, error) {
    resp, err := k.service.Decrypt(k.keypath, &cloudkms.DecryptRequest{
        Ciphertext: ciphertext,
    }).Do()
    if err != nil {
        return "", fmt.Errorf("failed to ecrypt. error: %v", err)
    }
    text, err := base64.StdEncoding.DecodeString(resp.Plaintext)
    if err != nil {
        return "", fmt.Errorf("failed base64 decode. error: %v", err)
    }
    return string(text), nil
}