本記事
本記事はCloud Key Management Service(KMS)を使うことによってSecretをセキュアにアプリケーションに保存するを解説します。
また、本記事ではSecretをGitHub内などで管理するための手法も解説します。
アジェンダ
本日のアジェンダは以下の通りです。
- 本記事
- アジェンダ
- 環境変数について
- 問題
- 解決策
- GitHubなどでSecretを管理
- (付録) Base64の役割
- まとめ
環境変数について
Cloud Function
環境変数を渡す
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)
1. 環境変数を渡す
1.1 コンテナにenv
フィールドで渡す
以下のようにKey-Valueでspec.containers.env
で渡せます。
spec: containers: - name: hoge image: hoge:latest env: - name: DB_NAME value: hoge
2. Secretを渡す
まず、Secretの使い方としては以下の通りです。
以下のようにマニフェストファイルを書きます。
マニフェストファイルから生成する場合はスキーマレスで、利便性の高いtype: Opaque
にします。
.data
に列挙していきます。
apiVersion: v1 kind: Secret metadata: name: hoge-secret type: Opaque data: username: cm3cero= password: 99hah3gpreuoa
2.1 env
でコンテナに渡すときsecretKeyRef
で参照
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
2.2 envFrom
でコンテナに渡すときsecretRef
でまとめて参照
これはKey一つ一つ定義しないといけないので、ファイル全体から構築する方法があります。secretKeyRef
からsecretRef
に変えることでできます。
「一つずつはenv
を使って、ファイルとしてマウントするものはenvFrom
を使う」と覚えるといいです。
spec: containers: - name: hoge image: hoge:latest envFrom: - secretRef: name: hoge-secret
また、複数のファイルから開くこともあるのでenvFrom[].prefix
でプレフィックスをつけることができます。
2.3 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
以下にあるだけなのでアプリケーションから呼び出さないといけません。
2.4 ファイルを渡す
環境変数を.env
としてファイルに設定する機会はあると思います。そのようなときに、どのように渡すかを設定します。
ファイルとして渡したい場合はstringData
を使うとcreate
やapply
のときにエンコードをしてくれます。
apiVersion: v1 kind: Secret metadata: name: mysecret type: Opaque stringData: config.yaml: |- apiUrl: "https://my.api.com/api/v1" username: user password: hogehoge
もちろん自分でconfig.yaml
をエンコードして.data
にしてもよいですが**同じフィールドがdata
とstringData
で定義されている場合はstringData
が優先されるので注意が必要です。等価のコマンドは以下のようになります。
kubectl create secret generic mysecret --from-file=config.yaml=config.yaml
また、以下のように直接、対象ファイルを貼り付けてもいいです。
apiVersion: v1 kind: Secret metadata: name: devdojo-tokucha-association-api data: gcloud_service_key: <base64string>
このときは、
volumeMounts: - name: secret-dir mountPath: "/etc/secr volumes: - name: secret-dir secret: secretName: devdojo-tokucha-association-api items: - key: gcloud_service_key path: gcloud-service-key.json
のようにマウントします。
ConfigMapなら、以下のようにします。
kubectl create configMap myConfig --from-file=configure-pod-container/configmap/
以下のように設定をします。マウントの方法としては他と同じです。
apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: mypod image: redis volumeMounts: - name: foo mountPath: /etc/foo readOnly: true volumes: - name: foo secret: secretName: mysecret items: - key: username path: my-group/my-username
例えば.env
を/
以下に置きたいときは以下のようにしないといけません。
mountPath: /.env subPath: .env
subPath
をつけないと.env/.env
のしてマウントされてしまいます。
または以下の通りでvolumes[].secret.secretName
として指定することもできます。/etc/foo/config.yml
としてマウントされます。
volumns: - name: config-volumn secret: secretName: hoge-secret
3. ConfigMapとしてマウントする
以下のようにマニフェストファイルを定義します。
apiVersion: v1 kind: ConfigMap metadata: name: hoge-config data: username: Hoge password: keisuke
3.1 コンテナにenvFrom
フィールドconfigMapRef
で渡す
... envFrom: - configMapRef: name: hoge-config
3.2 Volumeとしてまとめてマウントする
そして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
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
実行時に渡す
以下のようにKey-Valueで渡すことができます。
docker run hoge -e DB_PASSWORD='foo'
Build値のときに書き込む(Dockerfile)
以下の様に環境変数を書き込むことができます。
ENV <key> <value> ENV <key>=<value> ...
環境変数を取得する
以下のコマンドで環境変数を取得できます。
docker run -it hoge env
問題
上記の「環境変数を取得」などの項目でもあるように、どれも平文で保存されているため、アクセスできれば取得ができてしまいます。
解決策
Google Cloud Platformが提供しているCloud Key Management Serviceを使うことによって暗号化・復号化することができます。
流れとしては以下のような感じです。
Client: plain text -> KMS encrypt -> base64 encode -> 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も教える必要があります。
復号化
以下の様にして復号します。Shellで行う場合は、以下の通りです。
echo "[先程の結果]" | base64 -D | gcloud kms decrypt --location=asia-northeast1 --keyring=mercari-kot-agent-keyring --key=mercari-kot-agent-key --ciphertext-file=- --plaintext-file=-
まず、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 -> KMS encrypt -> base64 encode Application: plain text <- KMS decrypt <- base64 decode <-
これはGithubに保存するときも同様のことができて、89gpehurc=
をアップして、あとは各開発者がKeyとKeyringを使って各デベロッパーが復号化して手元で使うこともできます。
どんなSecretでも使うことができるので便利です。
(付録) Base64の役割
実際は暗号方式のための前処理だと考えることができる。
詳しく知りたい人は以下のリンクを参照してください。
まとめ
- 環境変数はSecretや機密性の高い情報を扱うために設定するものではない。
- アプリケーション側でCloud Key Management Serviceを使うことによりセキュアに管理できる。
- この手法はGitHubで管理したり、SlackでSecretを投げる際も使える方法である。