シークレットを適切に管理する

  • データベース接続文字列
  • 外部APIを呼び出すための認証キー
  • データの暗号鍵
  • 各種証明書

これらの情報が外部に漏洩することで、データベースへの不正アクセスによる情報流出やAPIの不正利用、サーバの乗っ取りなどビジネスに大きな損失が生じるリスクがあります。

例えば、フロントアプリがバックエンドのデータベースサーバにアクセスするときに使用するシークレットをDockerfileのCOPY命令を使ってコンテナに追加し、その後RUN命令で削除するDockerfileを作成したとします。このやり方であればファイルを削除しているので、一見安全そうにも思えます。


FROM gcr.io/distroless/static
COPY secret /root/bad-secret
RUN rm -f /root/bad-secret

しかし、DockerイメージはDockerfileの命令ごとにレイヤごとにファイルシステムの差分を計算し、イメージレイヤを作成します。そのためdocker saveコマンドでイメージをアーカイブとして出力することで削除したはずのファイルが参照できてしまうため注意してください。


docker build -t bad-image .
docker save bad-image | tar xv

tar xvf xxxxxxxxxxxx/layer.tar
cat root/bad-secret

なお、DockerfileのCOPY命令とADD命令はどちらもローカルマシンからコンテナにファイルを追加する命令ですが、ADD命令はリモートのファイルがダウンロードできるという違いがあります。そのため、ADD命令ではインターネット上から悪意のあるファイルがコンテナイメージに追加されるリスクがあります。いずれにせよ、コンテナイメージ内でシークレットを管理するのではなく、外部から参照できるような実装にしておくことが重要です。

Kubernetesでコンテナアプリケーションを動かす場合、機密情報はSecretsリソースを使って管理します。KubernetesのSecretsリソースは以下の組込み型を提供します。

タイプ 利用例
Opaque ユーザーのデータ
kubernetes.io/service-account-token Service Account トークン
kubernetes.io/dockercfg ~/.dockercfg
kubernetes.io/dockerconfigjson ~/.docker/config.json
kubernetes.io/basic-auth Basic認証のためのクレデンシャル
kubernetes.io/ssh-auth SSH認証のためのクレデンシャル
kubernetes.io/tls TLSクライアント/サーバのデータ
bootstrap.kubernetes.io/token Bootstrapトークン

例えば、ユーザー名とパスワードを設定したSecretsリソースを作成するには次のマニュフェストを作成します。その際、「username」と「password」は元の値をBase64エンコードした値を設定します。


apiVersion: v1
kind: Secret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

PodからSecretsリソースをファイルとして利用するには、次のマニュフェストを作成します。ファイルのパーミッションはデフォルトで「0644」になります。「.spec.volumes[].secret.defaultMode」でSecretボリューム全体のデフォルトモードを指定し、必要に応じてキー単位で上書きもできます。


apiVersion: v1
kind: Pod
...
spec:
  containers:
  - name: mypod
    image: xxxx
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      defaultMode: 0400

また、PodでSecretsを環境変数として利用するときは、次のようなマニュフェストを作成します。


apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
  restartPolicy: Never

ここで1つ注意しておきたいのは、Secretsマニュフェストで指定した「username」と「password」は一見意味のない値に見えますが、これは元の値をBase64エンコードしただけであるため、簡単にデータを読み出すことができるという点です。

AzureにはAzure Key Vault という秘匿情報を扱うためのマネージドサービスがあります。Key Vault に格納した秘匿情報は暗号化して保存され、秘匿情報に対するアクセス許可を細やかに設定できます。格納された秘匿情報は Azure によって業界標準のアルゴリズムとキーの長さを使用して自動的に保護されます。そのため、保管時の暗号化について開発者が意識する必要はありません。

AKSはシークレット ストアCSI ドライバと呼ばれるしくみを通じ、PodからAzure Key Vault上の秘匿情報にアクセスできます。

Azure Kubernetes ServiceとKey Vaultの詳細については、第6回の「Kubernetes クラスタの保護」を参照してください。