はじめに
前回は、ConfigMapを使って、機密ではない設定値をコンテナイメージから分離する仕組みを学びました。
ConfigMap
├── RAILS_ENV=production
├── LOG_LEVEL=info
└── REDIS_HOST=redis-service
↓
Pod
↓
Container
しかし、アプリケーションが必要とする値は、公開して問題のない設定だけではありません。
たとえば、次のような値も必要です。
データベースのパスワード
外部APIのトークン
RailsのSECRET_KEY_BASE
TLSの秘密鍵
これらをConfigMapへ保存したり、Dockerイメージへ直接書いたりすると、機密情報が漏れる危険があります。
そこで登場するのが Secret(シークレット) です。
この記事では、次の内容を整理します。
- Secretとは何か
- ConfigMapとの違い
-
dataとstringDataの違い - Base64と暗号化の違い
- Secretを環境変数として渡す方法
- Secretをファイルとしてマウントする方法
- Secretを更新したときの反映方法
- SecretをGitへ保存してはいけない理由
- RBACやNamespaceとの関係
- EKSにおけるKMS暗号化
- AWS Secrets Managerとの違い
結論
Secretとは、パスワード・トークン・秘密鍵などの機密情報を、コンテナイメージやPodの定義から分離して扱うKubernetesリソースです。
Secret
├── DATABASE_PASSWORD
├── SECRET_KEY_BASE
└── API_TOKEN
↓ 参照
DeploymentのPodテンプレート
↓
Pod
↓
Containerの環境変数・ファイル
ただし、Secretを作成しただけで、機密情報が完全に安全になるわけではありません。
Secret
= 機密情報をConfigMapやPod定義から分離する仕組み
RBAC
= 誰がSecretを読み取れるか制限する仕組み
保存時の暗号化
= Kubernetes APIの保存先でデータを保護する仕組み
Secrets Managerなど
= Secretの保管・取得・ローテーションを管理する外部サービス
Secretは、これらを組み合わせて安全に運用します。
なぜDockerイメージへ機密情報を入れないのか
たとえば、Dockerfileへパスワードを書いたとします。
ENV DATABASE_PASSWORD=example-password
この値は、作成したコンテナイメージやイメージ履歴から読み取られる可能性があります。
Dockerイメージ
└── DATABASE_PASSWORDが埋め込まれる
↓
イメージを取得できる人から見える可能性がある
Gitへコミットした後に行を削除しても、過去の履歴へ残る可能性があります。
Secretを使うと、アプリケーションのイメージと機密情報を分けられます。
Dockerイメージ
└── アプリケーション本体
Secret
└── 環境ごとの機密情報
同じDockerイメージを使いながら、開発・ステージング・本番で異なる機密情報を渡せます。
Secretは何を保存するのか
Secretは、主に次のような機密情報を保存します。
- データベースのユーザー名・パスワード
- 外部サービスのAPIトークン
- Railsの
SECRET_KEY_BASE - OAuthのクライアントシークレット
- TLS証明書と秘密鍵
- コンテナレジストリの認証情報
- SSHの秘密鍵
機密性のない値はConfigMapへ保存します。
| 値 | 保存先の例 |
|---|---|
LOG_LEVEL=info |
ConfigMap |
RAILS_ENV=production |
ConfigMap |
REDIS_HOST=redis-service |
ConfigMap |
DATABASE_PASSWORD=... |
Secret |
SECRET_KEY_BASE=... |
Secret |
API_TOKEN=... |
Secret |
ConfigMapとSecretの違い
ConfigMapとSecretは、どちらも値をPodへ渡すために利用できます。
大きな違いは、保存する情報の性質です。
| 項目 | ConfigMap | Secret |
|---|---|---|
| 主な用途 | 機密ではない設定 | 機密情報 |
| 値の例 | ログレベル、ホスト名 | パスワード、トークン |
| 環境変数として渡す | できる | できる |
| ファイルとして渡す | できる | できる |
| 主な参照方法 | configMapRef |
secretRef |
| アクセス制御 | RBACで制御 | RBACでより厳格に制御すべき |
ConfigMap
└── 見られても問題のない設定
Secret
└── 閲覧できる人を制限すべき機密情報
SecretはConfigMapより機密情報向けに設計されていますが、名前だけで自動的に万全な保護が行われるわけではありません。
Secret・Deployment・Podの関係
Secret自体がコンテナへ値を送り込むわけではありません。
DeploymentのPodテンプレートからSecretを参照します。
Secret
機密情報を保持する
↓ 名前で参照
Deployment
Podテンプレートへ参照方法を書く
↓ Podを作成
Pod
↓
Container
環境変数・ファイルとして利用する
ConfigMapと同様に、同じNamespace内の複数のPodから同じSecretを参照できます。
┌→ Rails Deployment → Rails Pod
application-secret ┤
└→ Sidekiq Deployment → Sidekiq Pod
Secretを定義するYAML
Secretの基本的なYAMLは次のようになります。
apiVersion: v1
kind: Secret
metadata:
name: application-secret
type: Opaque
data:
DATABASE_PASSWORD: ZXhhbXBsZS1wYXNzd29yZA==
SECRET_KEY_BASE: ZXhhbXBsZS1zZWNyZXQta2V5LWJhc2U=
dataへ記述する値は、Base64形式へ変換する必要があります。
なお、この記事の値は説明用のダミーです。実際の認証情報ではありません。
YAMLの各項目
apiVersionとkind
apiVersion: v1
kind: Secret
| 項目 | 意味 |
|---|---|
apiVersion: v1 |
Secretを扱うKubernetes APIのバージョン |
kind: Secret |
作成するリソースがSecretであることを示す |
metadata.name
metadata:
name: application-secret
Secretの名前です。
DeploymentやPodは、この名前を使ってSecretを参照します。
type
type: Opaque
Secretの用途を表します。
任意のキーと値を保存する一般的なSecretでは、Opaqueを使用します。
代表的なSecret Typeは次のとおりです。
| Type | 用途 |
|---|---|
Opaque |
一般的な機密情報 |
kubernetes.io/tls |
TLS証明書と秘密鍵 |
kubernetes.io/dockerconfigjson |
コンテナレジストリの認証情報 |
kubernetes.io/basic-auth |
Basic認証のユーザー名・パスワード |
kubernetes.io/ssh-auth |
SSH秘密鍵 |
用途に合ったTypeを使うことで、必要なキーが存在するかKubernetesが確認できる場合があります。
dataとstringDataの違い
Secretへ値を書く方法には、主にdataとstringDataがあります。
data
dataへ書く値は、Base64形式へ変換します。
data:
DATABASE_PASSWORD: ZXhhbXBsZS1wYXNzd29yZA==
文字列をBase64へ変換する例です。
printf '%s' 'example-password' | base64
出力例です。
ZXhhbXBsZS1wYXNzd29yZA==
改行が値へ混ざらないように、echoではなくprintfを使うと分かりやすいです。
元の値へ戻すことも簡単です。
printf '%s' 'ZXhhbXBsZS1wYXNzd29yZA==' | base64 --decode
stringData
stringDataを使うと、Base64へ変換する前の文字列を記述できます。
apiVersion: v1
kind: Secret
metadata:
name: application-secret
type: Opaque
stringData:
DATABASE_PASSWORD: example-password
SECRET_KEY_BASE: example-secret-key-base
Kubernetes APIが受け取った後、値はSecretのdataとして扱われます。
| フィールド | YAMLへ書く値 |
|---|---|
data |
Base64形式の値 |
stringData |
通常の文字列 |
ただし、stringDataへ平文で書けば、YAMLファイル上ではそのまま読めます。
SecretのYAMLをGitへコミットしてよいという意味ではありません。
また、stringDataはServer-Side Applyと相性がよくないという注意点があります。
Base64は暗号化ではない
ここがSecretを理解するうえで最も重要です。
Base64は、データを文字列として表現するためのエンコードです。
暗号化ではありません。
example-password
↓ Base64エンコード
ZXhhbXBsZS1wYXNzd29yZA==
↓ Base64デコード
example-password
鍵を知らなくても、誰でも元に戻せます。
したがって、次の理解は間違いです。
× Base64に変換したから中身を読めない
× Secretに入れたからYAMLを公開してよい
正しくは次のとおりです。
Base64
= バイナリーデータを文字列として表現する形式
暗号化
= 鍵を持つ人だけが元の値を読めるようにする仕組み
Secretの安全性は、保存時の暗号化・通信の暗号化・RBAC・運用方法などによって決まります。
Secretを作成する
YAMLをsecret.yamlとして保存した場合、次のコマンドで作成・更新できます。
kubectl apply -f secret.yaml
一覧を確認します。
kubectl get secret
Secretの詳細を確認します。
kubectl describe secret application-secret
describeでは、通常、値そのものではなく各データのサイズが表示されます。
Data
====
DATABASE_PASSWORD: 16 bytes
SECRET_KEY_BASE: 23 bytes
一方、次のコマンドではBase64形式の値を取得できます。
kubectl get secret application-secret -o yaml
Secretを読み取る権限を持つ人は、Base64をデコードして元の値を確認できます。
そのため、kubectl get secretの権限自体を制限する必要があります。
コマンドからSecretを作成する
実際の値をYAMLへ直接書かず、コマンドからSecretを作成することもできます。
値を直接指定する
kubectl create secret generic application-secret \
--from-literal=DATABASE_PASSWORD='example-password' \
--from-literal=SECRET_KEY_BASE='example-secret-key-base'
ファイルから作成する
kubectl create secret generic tls-private-key \
--from-file=private.key
この場合、通常はファイル名がキー、ファイルの中身が値になります。
ただし、シェルの履歴・CIのログ・プロセス情報などへ値が残らないように注意が必要です。
本番運用では、外部のSecret管理サービスや、安全なCI/CDのSecret機能から値を渡す方法も検討します。
コンテナへ渡す方法
Secretの代表的な渡し方は次の3つです。
| 方法 | 向いている用途 |
|---|---|
envFrom.secretRef |
複数のキーをまとめて環境変数にする |
env.valueFrom.secretKeyRef |
必要なキーだけを環境変数にする |
| Volume | 秘密鍵や証明書などをファイルとして渡す |
それぞれ確認します。
方法1:すべての値を環境変数として渡す
envFromを使うと、Secretの値をまとめて環境変数へ渡せます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: rails
spec:
replicas: 3
selector:
matchLabels:
app: rails
template:
metadata:
labels:
app: rails
spec:
containers:
- name: rails
image: example/rails-app:1.0
envFrom:
- secretRef:
name: application-secret
Secretに次のキーがあるとします。
DATABASE_PASSWORD
SECRET_KEY_BASE
コンテナ内では、同じ名前の環境変数として利用できます。
DATABASE_PASSWORD=...
SECRET_KEY_BASE=...
Railsからは、たとえば次のように取得します。
ENV.fetch("SECRET_KEY_BASE")
方法2:必要な値だけを環境変数として渡す
secretKeyRefを使うと、Secretの特定のキーだけを参照できます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: rails
spec:
replicas: 3
selector:
matchLabels:
app: rails
template:
metadata:
labels:
app: rails
spec:
containers:
- name: rails
image: example/rails-app:1.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: application-secret
key: DATABASE_PASSWORD
この設定では、SecretのDATABASE_PASSWORDを、コンテナ内のDB_PASSWORDとして渡します。
Secret
DATABASE_PASSWORD=...
↓
Container
DB_PASSWORD=...
必要な値だけを渡せるため、コンテナが参照できるSecretを最小限にできます。
方法3:ファイルとしてマウントする
Secretは、VolumeとしてPodへマウントできます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: tls-secret-volume
mountPath: /etc/nginx/tls
readOnly: true
volumes:
- name: tls-secret-volume
secret:
secretName: tls-secret
Secretに次のキーがある場合を考えます。
tls.crt
tls.key
コンテナ内には、次のファイルとしてマウントされます。
/etc/nginx/tls/tls.crt
/etc/nginx/tls/tls.key
関係は次のとおりです。
Secretのキー
tls.key
↓ ファイル名になる
/etc/nginx/tls/tls.key
↑
Secretの値がファイル内容になる
秘密鍵や証明書など、アプリケーションがファイルとして読むデータに向いています。
環境変数とファイルのどちらを使うのか
| 観点 | 環境変数 | Volumeによるファイル |
|---|---|---|
| 向いている値 | パスワード、短いトークン | 証明書、秘密鍵、設定ファイル |
| アプリの読み方 |
ENVなど |
ファイルを開く |
| Secret更新 | 起動済みコンテナへ自動反映されない | 最終的にマウントへ反映される |
| 注意点 | デバッグ出力や子プロセスへ漏らさない | ファイル権限・読取対象を制限する |
どちらを選んでも、アプリケーションのログやエラーメッセージへ値を出力しないことが重要です。
Secretを更新するとどうなるのか
Secretも、利用方法によって更新の反映方法が異なります。
環境変数として利用している場合
Secretを更新しても、すでに起動しているコンテナの環境変数は変わりません。
Secretを更新
↓
起動済みContainerの環境変数
× そのまま
新しい値を使うには、Podを再作成します。
kubectl rollout restart deployment/rails
ローリングアップデートの状態を確認します。
kubectl rollout status deployment/rails
Volumeとして利用している場合
VolumeとしてマウントしたSecretは、Secretの更新後、時間を置いてPod内のファイルにも反映されます。
Secretを更新
↓
kubeletが変更を検知
↓
マウントされたファイルを更新
ただし、即時反映ではありません。
また、ファイルが変わっても、アプリケーションが自動で読み直すとは限りません。
アプリケーションが再読み込みに対応していなければ、Podの再作成が必要です。
subPathを使ってSecretの一部だけをマウントした場合、更新は自動反映されません。
Secretの変更でDeploymentは自動再起動しない
SecretとDeploymentは別のKubernetesリソースです。
Secretだけを更新しても、DeploymentのPodテンプレートは変わりません。
そのため、Deploymentは自動的に新しいPodを作りません。
Secretだけ変更
↓
DeploymentのPodテンプレートは同じ
↓
ローリングアップデートは始まらない
環境変数として利用している場合は、次のような運用を検討します。
-
kubectl rollout restartを実行する - Secret名へバージョンを付けて参照先を切り替える
- Secret変更を検知してDeploymentを再起動する仕組みを導入する
- 外部Secret管理サービスとローテーション方法を設計する
SecretとNamespace
SecretはNamespaceに所属するリソースです。
Podは、同じNamespaceにあるSecretを参照します。
Namespace: production
├── Secret: application-secret
└── Pod: rails-xxxxx
○ 参照できる
Namespace: staging
└── Pod: rails-yyyyy
× productionのSecretは直接参照できない
環境ごとにNamespaceを分ける場合は、それぞれのNamespaceへSecretを用意します。
kubectl create secret generic application-secret \
--from-literal=DATABASE_PASSWORD='example-password' \
-n production
存在しないSecretを参照するとどうなるのか
Podが必須のSecretを参照しているのに、そのSecretが存在しない場合、Podは正常に起動できません。
Deployment
└── application-secretを参照
application-secretが存在しない
↓
Podの起動に失敗・待機
まずSecretを作成し、その後にDeploymentを作成するのが基本です。
意図的にSecretがなくても起動させたい場合は、optional: trueを指定できます。
envFrom:
- secretRef:
name: optional-secret
optional: true
ただし、認証情報などの必須値をoptionalにすると、アプリケーション起動後に別のエラーが起きる可能性があります。
immutableなSecret
変更させたくないSecretには、immutable: trueを指定できます。
apiVersion: v1
kind: Secret
metadata:
name: application-secret-v1
type: Opaque
immutable: true
stringData:
DATABASE_PASSWORD: example-password
immutableなSecretは、作成後にdataやstringDataの内容を変更できません。
変更が必要な場合は、新しい名前のSecretを作成して参照先を切り替えます。
application-secret-v1
↓ 新しい値を作成
application-secret-v2
誤更新を防ぎたい場合や、Secretをバージョン単位で切り替えたい場合に利用できます。
SecretをGitへコミットしてはいけない
次のYAMLは、Base64になっていても機密情報を含んでいます。
data:
DATABASE_PASSWORD: ZXhhbXBsZS1wYXNzd29yZA==
Base64は簡単に元へ戻せるため、このファイルをGitへコミットすると機密情報も共有されます。
Private Repositoryでも、次のようなリスクがあります。
- 閲覧できるメンバーが増える
- 誤ってRepositoryが公開される
- CIのログへ表示される
- ForkやCloneへ値が残る
- 削除後もGit履歴へ残る
本番の認証情報を含むSecret YAMLを、平文やBase64のままGitへ保存しないようにします。
Gitで宣言的に管理したい場合は、暗号化されたSecretマニフェストや外部Secret管理サービスを利用する仕組みを検討します。
Secretを扱うときのRBAC
Secretを取得できるユーザーやServiceAccountは、値を読み取れます。
そのため、RBACでアクセス権を最小限にします。
開発者全員
└── すべてのSecretを取得できる
× 権限が広すぎる
必要な運用担当者・ServiceAccount
└── 必要なNamespace・Secretだけ取得できる
○ 最小権限
特に、Secretに対する次の権限は慎重に付与します。
getlistwatchcreateupdatepatchdelete
Podを作成できる権限にも注意が必要です。
Secretを参照するPodを自由に作れる人は、そのPodを通してSecretの値を取得できる可能性があります。
複数コンテナがあるPodでの注意点
1つのPodに複数のコンテナがある場合でも、Secretが必要なコンテナだけへ渡します。
Pod
├── Rails Container
│ └── DATABASE_PASSWORDが必要
│
└── Metrics Container
└── DATABASE_PASSWORDは不要
不要なコンテナへenvFromやvolumeMountsを設定しないことで、Secretへアクセスできる範囲を狭くできます。
Secretのサイズ上限
1つのSecretへ保存できるデータは1MiBまでです。
Secretは大きなファイルを保管するストレージではありません。
パスワード・トークン・鍵
→ Secretに向いている
動画・画像・大きなバックアップ
→ Secretに向いていない
大きなデータには、用途に合ったストレージを使用します。
Kubernetes Secretは保存時に暗号化されるのか
一般的なKubernetesでは、Secretを作成しただけで、保存先のetcdに必ず暗号化されるとは限りません。
Kubernetesの公式ドキュメントでは、Secretはデフォルトではetcdへ暗号化されずに保存されるため、保存時の暗号化を構成することが推奨されています。
kubectl
↓ TLSで通信
Kubernetes API Server
↓
etcd
Secretを保存
ここで分けて考える必要があります。
| 保護する場面 | 仕組みの例 |
|---|---|
| 通信中 | TLS |
| Kubernetes APIの保存先 | Encryption at Rest・Envelope Encryption |
| 読み取れる主体 | RBAC |
| Pod内での利用 | 環境変数・Volume・アプリの実装 |
Secretは、このすべてを自動的に解決するものではありません。
EKSではどう保存されるのか
Amazon EKSでは、Kubernetesのバージョンによって扱いが異なります。
AWSの現在のドキュメントでは、Kubernetes 1.28以降のEKSクラスターは、Kubernetes APIに保存されるすべてのデータをデフォルトでEnvelope Encryptionにより保護します。
Secretだけでなく、ConfigMapなどのAPIデータも対象です。
Kubernetes APIデータ
↓ データ暗号化キーで暗号化
暗号化されたデータ
↓
AWS KMSでキーを保護
これをEnvelope Encryptionと呼びます。
AWS所有のKMSキーが標準で使われ、要件に応じてカスタマーマネージドKMSキーを利用できます。
ただし、この暗号化はKubernetes APIへ保存されたデータを保護するものです。
Node上のデータやEBS Volume内のデータまで、同じ仕組みで自動的に保護するわけではありません。
利用中のEKSバージョンと暗号化設定を確認することが重要です。
Kubernetes SecretとAWS Secrets Managerの違い
EKSでは、Kubernetes Secretの代わりに、または組み合わせてAWS Secrets Managerを利用できます。
| 項目 | Kubernetes Secret | AWS Secrets Manager |
|---|---|---|
| 管理場所 | Kubernetes API | AWSのマネージドサービス |
| Podからの利用 | Secret参照 | CSI Driverなどで取得 |
| アクセス制御 | Kubernetes RBAC | AWS IAM |
| 自動ローテーション | 別途仕組みが必要 | 対応サービスでは設定可能 |
| AWS監査との連携 | Kubernetes側で設計 | CloudTrailなどと連携しやすい |
AWS Secrets Managerは、Secretの保管・アクセス制御・ローテーションをAWS側で管理したい場合に向いています。
EKS PodからAWS Secrets Managerを使う流れ
AWS Secrets and Configuration ProviderとSecrets Store CSI Driverを使うと、Secrets Managerの値をEKS Podへファイルとしてマウントできます。
AWS Secrets Manager
↓ IAMでアクセス許可
AWS Secrets and Configuration Provider
↓
Secrets Store CSI Driver
↓
EKS Pod内のVolume
↓
Containerがファイルを読む
Pod IdentityやIRSAを利用すると、Pod単位で必要なAWS権限を付与できます。
Rails Pod
└── application/production/* のSecretだけ取得できる
別のPod
└── 取得できない
これにより、Node全体へ広いIAM権限を与えずに、必要なPodだけがSecretへアクセスできます。
AWS Secrets Managerの自動ローテーションとCSI Driverのローテーション機能を組み合わせる方法もあります。
なお、AWS公式ドキュメントでは、AWS Secrets and Configuration Providerによるこの方式はEKSのFargate Nodeではサポートされていないとされています。
Kubernetes SecretとSecrets Managerのどちらを使うのか
一律にどちらかだけが正解というわけではありません。
| 状況 | 選択肢の例 |
|---|---|
| Kubernetes内だけで完結する小規模な構成 | Kubernetes Secret |
| AWSサービスと認証情報を共有する | AWS Secrets Manager |
| 自動ローテーションが必要 | AWS Secrets Managerを検討 |
| Kubernetes標準の可搬性を重視する | Kubernetes Secret |
| IAMでPod単位のアクセスを管理したい | Secrets Manager+Pod Identityなど |
実際には、セキュリティ要件・運用負荷・既存の仕組み・コストを含めて決めます。
重要なのは、Secretをどこへ置くかだけでなく、次の流れを設計することです。
作成
↓
保存
↓
配布
↓
利用
↓
更新・ローテーション
↓
失効・削除
RailsとSidekiqで同じSecretを使う例
RailsとSidekiqを別Deploymentで動かす場合を考えます。
application-secret
├── DATABASE_PASSWORD
└── SECRET_KEY_BASE
↓
├── Rails Deployment
│ └── Rails Pod
│
└── Sidekiq Deployment
└── Sidekiq Pod
Rails Deploymentでは次のように参照します。
containers:
- name: rails
image: example/rails-app:1.0
envFrom:
- secretRef:
name: application-secret
Sidekiq Deploymentでも同じSecretを参照します。
containers:
- name: sidekiq
image: example/rails-app:1.0
command: ["bundle", "exec", "sidekiq"]
envFrom:
- secretRef:
name: application-secret
同じDockerイメージとSecretを利用しながら、起動コマンドを変えられます。
Secretを環境変数として渡しているため、値を更新した場合はRailsとSidekiqの両方のPodを再作成します。
kubectl rollout restart deployment/rails
kubectl rollout restart deployment/sidekiq
ConfigMapとSecretを同時に使う例
実際のアプリケーションでは、ConfigMapとSecretを一緒に利用することが多くあります。
app-config
├── RAILS_ENV=production
├── LOG_LEVEL=info
└── REDIS_HOST=redis-service
application-secret
├── DATABASE_PASSWORD=...
└── SECRET_KEY_BASE=...
Deploymentでは、両方を参照できます。
containers:
- name: rails
image: example/rails-app:1.0
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: application-secret
コンテナから見ると、どちらも環境変数として利用できます。
ただし、Kubernetes上では公開設定と機密情報を別リソースとして管理できます。
Secretが渡されたか確認するときの注意
Pod名を確認します。
kubectl get pods
環境変数名が存在するかだけを確認する場合でも、値を画面やログへ出さないように注意します。
次のようなコマンドは値そのものを表示します。
kubectl exec rails-xxxxx -- printenv DATABASE_PASSWORD
共有画面・CIログ・シェル履歴などで実行すると、Secretが漏れる可能性があります。
トラブルシューティングでは、まず値を表示せずに次を確認します。
kubectl describe pod rails-xxxxx
確認する項目は次のとおりです。
- Secretが存在するか
- SecretとPodが同じNamespaceにあるか
- Secret名が一致しているか
- 参照しているキーが存在するか
- PodのServiceAccountに必要な権限があるか
- YAMLのインデントが正しいか
アプリケーション側でも、機密値をエラーメッセージへ含めないようにします。
よくある勘違い
Secretへ入れれば自動的に暗号化される
Base64は暗号化ではありません。
一般的なKubernetesでは、保存時の暗号化設定も確認する必要があります。
Base64だからGitへコミットしてよい
誰でも簡単にデコードできます。
機密情報を含むSecret YAMLを、そのままGitへ保存してはいけません。
Secretを作れば自動的に全Podへ渡される
自動では渡されません。
DeploymentやPodのYAMLから明示的に参照します。
Secretを更新すれば環境変数も変わる
起動済みコンテナの環境変数は変わりません。
新しい値を反映するにはPodの再作成が必要です。
Secretを更新すればDeploymentも再起動する
Secretの更新だけではDeploymentは自動再起動しません。
Secretを読める人は値を読めない
Secretを取得できる人は、Base64をデコードして元の値を読めます。
RBACで読み取り権限を制限します。
Secretを環境変数にすれば絶対に漏れない
アプリケーションのログ、エラー出力、デバッグ画面、子プロセスなどから漏れる可能性があります。
アプリケーション側の扱いも重要です。
Secretを安全に扱うためのチェックリスト
- SecretのYAMLを平文・Base64のままGitへ保存しない
- SecretをDockerfileやコンテナイメージへ埋め込まない
- RBACは最小権限にする
- 不要なコンテナへSecretを渡さない
- Namespaceを使って環境を分離する
- アプリケーションログへ機密値を出さない
- CI/CDのログへ機密値を出さない
- 保存時の暗号化設定を確認する
- 定期的なローテーション方法を決める
- 漏えい時に失効・再発行できるようにする
- EKSではSecrets ManagerやPod Identityも検討する
これまでの流れへSecretを加える
ここまで学んだリソースを並べると、次のようになります。
Node
└── Podを動かすサーバー
↑
Deployment
└── Podの数と更新を管理する
↓
Pod
└── Containerを動かす単位
↑
Service
└── 変化するPod群へ安定した接続先を提供する
↑
Ingress
└── 外部のHTTP・HTTPS通信をServiceへ振り分ける
ConfigMap
└── 機密ではない設定値をContainerへ渡す
Secret
└── パスワードやトークンなどをContainerへ渡す
通信と設定を分けて考えると理解しやすくなります。
通信の流れ
Ingress → Service → Pod → Container
設定の流れ
ConfigMap → Pod → Container
機密情報の流れ
Secret → Pod → Container
まとめ
Secretは、パスワード・APIトークン・秘密鍵などの機密情報を、コンテナイメージやPod定義から分離して扱うKubernetesリソースです。
Secret
= 機密情報を保持するKubernetesリソース
Deployment・Pod
= Secretの参照方法を定義する
Container
= 環境変数やファイルとして値を利用する
重要な点は次のとおりです。
- 機密ではない設定はConfigMap、機密情報はSecretへ分ける
-
dataの値はBase64形式で記述する -
stringDataには通常の文字列を記述できる - Base64は暗号化ではない
-
secretRefで複数の値を環境変数として渡せる -
secretKeyRefで特定の値だけを参照できる - Volumeを使うと秘密鍵などをファイルとして渡せる
- 環境変数はSecret更新だけでは変わらない
- Secretの更新だけではDeploymentは再起動しない
- Secret YAMLをそのままGitへ保存しない
- RBACで読み取れる人やPodを制限する
- EKS 1.28以降ではKubernetes APIデータが標準でEnvelope Encryptionの対象になる
- EKSではAWS Secrets ManagerとCSI Driverを利用する構成もある
一言でまとめると、次のようになります。
Secretは機密情報をアプリケーションから分離する仕組みですが、暗号化・権限・配布・ローテーションまで設計して初めて安全に運用できます。
次は、Podが再作成されてもデータを残すための Volume・PersistentVolume・PersistentVolumeClaim について整理します。