Kekeの日記

エンジニア、読書なんでも

Cloud Functions, Kubernetes, DockerなどにセキュアにSecretを渡す時に使えるCloud Key Management Service

f:id:bobchan1915:20181002032329p:plain

本記事

本記事はCloud Key Management Service(KMS)を使うことによってSecretをセキュアにアプリケーションに保存するを解説します。

また、本記事ではSecretをGitHub内などで管理するための手法も解説します。

環境変数について

Cloud Function

https://www.totalsolution.biz/cms/wp-content/uploads/2016/03/gcf_email_img.png

環境変数を渡す

Cloud Functionでは以下のように環境変数を渡すことができます。

Key valueで渡す

gcloud beta functions deploy FUNCTION_NAME --set-env-vars FOO=bar FLAGS...

ファイルで渡す

gcloud beta functions deploy FUNCTION_NAME --env-vars-file .env.yaml FLAGS...

環境変数を更新する

gcloud beta functions deploy FUNCTION_NAME --update-env-vars FOO=bar

環境変数を取得する

gcloud functions call envvars

Kubernetes(GKE)

https://www.publickey1.jp/2017/gke01.gif

直接環境変数をコンテナに渡す

以下のようにKey-Valueでspec.containers.envで渡せます。

spec:
    containers:
    - name: hoge
       image: hoge:latest
       env:
       - name: DB_NAME
          value: hoge

Secretを設定する

以下のようにマニフェストファイルを書きます。 マニフェストファイルから生成する場合はスキーマレスで、利便性の高いtype: Opaqueにします。

.dataに列挙していきます。

apiVersion: v1
kind: Secret
metadata:
    name: hoge-secret
type: Opaque
data: 
    username: cm3cero=
    password: 99hah3gpreuoa

使う方法は以下の通りです。valueFrom.secretKeyRefとしてSecretからとります。

  • valueFrom.secretKeyRef.nameどのSecretかを指定します
  • valueFrom.secretKeyRef.keyそのSecretのどのKeyのValueを取るのかを指定します。
spec:
    containers:
    - name: hoge
       image: hoge:latest
       env:
       - name: DB_NAME
          valueFrom:
              secretKeyRef:
                  name: hoge-secret
                  key: username

これはKey一つ一つ定義しないといけないので、ファイル全体から構築する方法があります。secretKeyRefからsecretRefに変えることでできます。

「一つずつはenvを使って、ファイルとしてマウントするものはenvFromを使う」と覚えるといいです。

spec:
    containers:
    - name: hoge
       image: hoge:latest
       envFrom:
       - secretRef:
              name: hoge-secret

また、複数のファイルから開くこともあるのでenvFrom[].prefixでプレフィックスをつけることができます。

ConfigMapとしてマウントする

以下のようにマニフェストファイルを定義します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: hoge-config
data:
    username: Hoge
    password: keisuke

そしてvolumnとしてPodのマニフェストファイルに定義します。

volumes:
    - name: config-volume
      configMap:
        name: hoge-config

これを使ってマウントします。

volumeMounts:
        - name: hoge-volume
          mountPath: /etc/config

全体としては以下のようになります。

apiVersion: v1
kind: Pod
metadata:
  name: pod-practice-config
spec:
  containers:
    - name: hoge-container
      image: busybox
      command: ["sleep", "3600"]
      volumeMounts:
        - name: hoge-volume
          mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: hoge-config

SecretをVolumnとしてマウントをする

少し重複していますがファイルなどでシークレットを管理している場合、マウントして使うこともできます。

以下の様にマウントすることもできます。

volumnMounts.mountPathで**どこにマウントするかを指定します。

spec:
    containers:
    - name: secret-container
       image: nginx:latest
       volumnMounts:
       - name: config-mount
          mountPath: /config
    volumns:
    - name: config-volumn
       secret:
            secretName: hoge-secret
            items:
            - key: username
               path: username.txt

ここでsecretNameでどのSecretを使うかを指定しています。ここではitemsとして指定しています。

また、全体としてマウントするならば

spec:
    containers:
    - name: secret-container
       image: nginx:latest
       volumnMounts:
       - name: config-mount
          mountPath: /config
    volumns:
    - name: config-volumn
       secret:
            secretName: hoge-secret

で渡すことができます。もちろん/config以下にあるだけなのでアプリケーションから呼び出さないといけません。

Envの取得

以下のようにPodにアクセスすれば環境変数を取得することができます。

kubectl exec -it hoge-pod env ; echo 

Secretの取得

以下のようにするとSecretを取得することができます。

kubectl get secret --namespace default [Deploy名] -o jsonpath="{.data.password}" | base64 --decode ; echo

Docker

https://www.docker.com/sites/default/files/social/docker_twitter_share_new.png?4362984378

実行時に渡す

以下のようにKey-Valueで渡すことができます。

docker run hoge -e DB_PASSWORD='foo'

Build値のときに書き込む(Dockerfile)

以下の様に環境変数を書き込むことができます。

ENV <key> <value>
ENV <key>=<value> ...

環境変数を取得する

以下のコマンドで環境変数を取得できます。

docker run -it hoge env

問題

上記の「環境変数を取得」などの項目でもあるように、どれも平文で保存されているため、アクセスできれば取得ができてしまいます。

解決策

https://cloud.google.com/kms/images/resources2.png?authuser=3&hl=ja

Google Cloud Platformが提供しているCloud Key Management Serviceを使うことによって暗号化・復号化することができます。

流れとしては以下のような感じです。

Client:            plain text -> base64 encode -> KMS encrypt -> 89gpehurc=
Application:       plain text <- KMS decrypt   <- base64 decode <-

使い方

暗号化

以下のようにして、Keyringから作成したKeyを元に暗号化をします。

echo [実際のパスワードなど] | gcloud kms encrypt  --location=global --keyring=[KEYRING_NAME] --key=[KEY_NAME] --ciphertext-file=- --plaintext-file=- | base64

この値をenvなり、Secretで渡します。

また、同時に鍵リソースIDも教える必要があります。

復号化

以下の様にして復号します。

まず、base64 decodeをしたあとに以下の様にAPIを使います。

func main() {
        projectID := "your-project-id"
        // Location of the key rings.
        locationID := "global"

        // Authorize the client using Application Default Credentials.
        // See https://g.co/dv/identity/protocols/application-default-credentials
        ctx := context.Background()
        client, err := google.DefaultClient(ctx, cloudkms.CloudPlatformScope)
        if err != nil {
                log.Fatal(err)
        }

        // Create the KMS client.
        kmsService, err := cloudkms.New(client)
        if err != nil {
                log.Fatal(err)
        }

        // The resource name of the key rings.
        parentName := fmt.Sprintf("projects/%s/locations/%s", projectID, locationID)

        // Make the RPC call.
        response, err := kmsService.Projects.Locations.KeyRings.List(parentName).Do()
        if err != nil {
                log.Fatalf("Failed to list key rings: %v", err)
        }

        // Print the returned key rings.
        for _, keyRing := range response.KeyRings {
                fmt.Printf("KeyRing: %q\n", keyRing.Name)
        }
}

APIはdecryptを使うのでjsonドキュメントを参考にします。

"decrypt": {
                      "description": "Decrypts data that was protected by Encrypt. The CryptoKey.purpose\nmust be ENCRYPT_DECRYPT.",
                      "flatPath": "v1/projects/{projectsId}/locations/{locationsId}/keyRings/{keyRingsId}/cryptoKeys/{cryptoKeysId}:decrypt",
                      "httpMethod": "POST",
                      "id": "cloudkms.projects.locations.keyRings.cryptoKeys.decrypt",
                      "parameterOrder": [
                        "name"
                      ],
                      "parameters": {
                        "name": {
                          "description": "Required. The resource name of the CryptoKey to use for decryption.\nThe server will choose the appropriate version.",
                          "location": "path",
                          "pattern": "^projects/[^/]+/locations/[^/]+/keyRings/[^/]+/cryptoKeys/[^/]+$",
                          "required": true,
                          "type": "string"
                        }
                      },
                      "path": "v1/{+name}:decrypt",
                      "request": {
                        "$ref": "DecryptRequest"
                      },
                      "response": {
                        "$ref": "DecryptResponse"
                      },
                      "scopes": [
                        "https://www.googleapis.com/auth/cloud-platform"
                      ]
                    },

GitHubなどでSecretを管理

再度掲載しますが、以下のような流れで保存しています。

Client:            plain text -> base64 encode -> KMS encrypt -> 89gpehurc=
Application:       plain text <- KMS decrypt   <- base64 decode <-

これはGithubに保存するときも同様のことができて、89gpehurc=をアップして、あとは各開発者がKeyとKeyringを使って各デベロッパーが復号化して手元で使うこともできます。

どんなSecretでも使うことができるので便利です。

(付録) Base64の役割

実際は暗号方式のための前処理だと考えることができる。

詳しく知りたい人は以下のリンクを参照してください。

Base64 - Wikipedia

まとめ

  • 環境変数はSecretや機密性の高い情報を扱うために設定するものではない
  • アプリケーション側でCloud Key Management Serviceを使うことによりセキュアに管理できる
  • この手法はGitHubで管理したり、SlackでSecretを投げる際も使える方法である