kubeseal について
kubeseal は、Kubernetes Secret を Bitnami SealedSecrets に暗号化するスキルです。クラスタの認証情報を扱う開発者向けに、平文や秘密鍵の保護を重視した封印と更新を支援します。
主な機能
- 01Secret を SealedSecret に封印
- 02ローカル証明書でオフライン暗号化
- 03既存 SealedSecret のキー更新
- 04--raw による単一値の暗号化
- 05平文と秘密鍵の安全な取り扱い
活用シーン
- 01アプリの認証情報を Git に安全に保存
- 02SealedSecret の一部キーを更新
- 03認証情報の変更後に Secret を再封印
MCP 対応
Kubernetes MCP からコントローラーの公開証明書を取得し、クラスタ内の復号済み Secret を読み取って封印や再封印に利用します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mmontes11/ skills --skill kubesealインストール後、Claude Code で/kubesealを入力して読み込まれたか確認します。
詳細
Kubeseal:Kubernetes Secret のシールと再シール
概要
Kubernetes Secret を Bitnami SealedSecret にシールし、Git に安全に保存できるようにします。SealedSecret は非対称暗号化されています。誰でも暗号化(シール)できますが、復号(シール解除)できるのはクラスター内の sealed-secrets コントローラーだけです。
kubesealv0.38.4 で検証済みです。フラグ名は最近のリリース間で安定していますが、コマンドの動作が予期しない場合はkubeseal --helpを実行してください。
**重要なポイント:**公開証明書(tls.crt)は秘密ではありません。公開鍵であり、安全に共有でき、コミットすることもできます。機密情報なのはコントローラーの秘密鍵(tls.key)だけです。証明書が手元にあれば、シール処理は完全にローカルでオフラインで行えます。シールにクラスターへのアクセスは必要ありません。
重要なセキュリティルール
認証情報を絶対に漏えいさせない
- 平文の認証情報を標準出力、会話の出力、または読み取り可能な媒体に絶対に出力しないでください。
- 平文の Secret をリポジトリ内、またはバージョン管理対象の場所にあるファイルへ絶対に書き込まないでください。
- **Secret の値を絶対にエコー表示またはログ出力しないでください。**変数を使い、直接パイプで渡してください。
- 平文の Secret をコミットメッセージ、PR の説明、またはコメントに絶対に含めないでください。
- 平文の Secret を含む一時ファイルは、使用後すぐに必ず上書きするか、ゼロクリアしてください。
- **シール鍵の情報を絶対に漏えいさせないでください。**秘密鍵(
tls.key)を出力、コミット、共有してはいけません。シールに必要なのは公開鍵(tls.crt)だけです。
一時ファイルの取り扱い
**平文を名前付きファイルとしてディスクに書き込まない方法を優先してください。**以下の「1. 新しい SealedSecret」にある標準入力のヒアドキュメント、または「単一の値を暗号化する(--raw)」の方法を使ってください。一時ファイルが避けられない場合:
- Secret の一時ファイルは、内容がわかる名前を付けて
/tmp/に書き込んでください。 - シール後、一時ファイルの内容を空または無意味なデータで上書きしてください。
- 権限ルールによりブロックされる可能性があるため、一時ファイルを
rmで削除しないでください。代わりに上書きしてください:echo "" > /tmp/secret-temp.yaml - 一時ファイルをリポジトリに絶対にコミットしないでください。
注:公開証明書(
tls.crt/.pem)は機密情報ではなく、サニタイズする必要もありません。証明書を消去してしまうと、再取得が必要になるだけです。上書きするのは、平文の Secret の値または秘密鍵を含むファイルだけです。
前提条件
kubesealCLI がインストールされ、使用可能であること- 公開鍵証明書(
tls.crt)。下記の「証明書の取得」に記載された方法でのみ取得すること - Kubernetes MCP 経由で Kubernetes クラスターにアクセスできること(証明書の代替取得方法、および既存の Secret の値の取得に必要)
証明書の取得
**このセクションの指示を必ず守ってください。証明書の取得元は、次の2つのいずれかに限り、記載順に試してください。**3つ目の方法を独自に試してはいけません。
取得元1(推奨)— プロジェクトの certs/ フォルダー
まずリポジトリ内で証明書を探してください。シール処理はオフラインで行えます。リポジトリに証明書が含まれていれば、クラスターへのアクセスは一切不要です。
# From the project root; typical names: tls.crt, sealed-secrets-cert.pem, sealed-secrets.crt
ls certs/
# If certs/ is not at the root, locate it:
find . -type d -name certs -not -path '*/.git/*'
見つかったファイルを CERT に設定し、使用前に検証してください(「証明書を必ず検証する」を参照)。
CERT=certs/tls.crt
certs/ に候補が複数ある場合は、それぞれを検証し、リポジトリ内の既存の SealedSecret を検証できるものを優先してください。
ソース 2(フォールバック)— Kubernetes MCP 経由でクラスターから取得
certs/ に有効な証明書がない場合に限ります。Kubernetes MCP ツールを使って、コントローラーが使用中の鍵 Secret を読み取ります。
手順 1. 次の内容で mcp__kubernetes__resources_list を呼び出します。
apiVersion: v1、kind: SecretlabelSelector: sealedsecrets.bitnami.com/sealed-secrets-key=activenamespace:コントローラーが実行されている場所(通常はkube-systemまたはsealed-secrets)。すべての名前空間を検索する場合は省略します
active な鍵 Secret が複数返された場合は、metadata.creationTimestamp が最も新しいものを選びます。それが、コントローラーが現在シールに使用している鍵です。
手順 2. その Secret の data["tls.crt"] を取得し、Base64 デコードしてローカルファイルに保存します。
printf '%s' '<tls.crt base64 from the MCP response>' | base64 -d > /tmp/sealed-secrets-cert.pem
CERT=/tmp/sealed-secrets-cert.pem
手順 3. 使用する前に検証します(次のセクションを参照)。
MCP からそのような Secret が返されない場合、MCP が接続しているクラスターにコントローラーはインストールされていません。作業を中止してユーザーに伝えてください。別の方法に切り替えてはいけません。
危険 — その Secret にはコントローラーの秘密鍵である
tls.keyも含まれています。 読み取り、使用するのはtls.crtフィールドのみです。tls.keyをデコード、表示、出力、書き込み、コミットしてはいけません。MCP の生のレスポンスをどこにも貼り付けないでください。
証明書は必ず検証する
ファイルが存在するだけでは、証明書だとは限りません。 リダイレクトによってエラーテキストが証明書のようなファイルに保存されることがあり、その不正なデータを使ってシールすると、平文が失われてからかなり後になって illegal base64 data at input byte N または no key could decrypt secret というエラーが発生する SealedSecret が生成されます。
シールする前に毎回、これを実行します。失敗した場合は作業を中止し、シールせずに次の取得元に切り替えます。
openssl x509 -in "$CERT" -noout -subject -dates || echo "NOT A VALID CERT — do not seal with this file"
certs/ のファイルが検証に失敗した場合は、ユーザーに報告してソース 2 に進みます。推測で「修正」してはいけません。
正しい鍵であることを確認する(推奨)
証明書が有効でも、誤った鍵やローテーション済みの鍵である可能性があります。リポジトリに動作中の SealedSecrets がすでにある場合は、既知の正常な鍵ペアと公開鍵を比較するか、新たにシールしたテスト値をラウンドトリップして、証明書がそれらのシールに使われた鍵と一致することを確認します。リポジトリに秘密鍵も含まれている場合は、それに頼るのではなく、セキュリティ上の問題として記録します(「注意点」を参照)。
ワークフロー
1. 新しい SealedSecret
推奨 — stdin 経由でシールする(平文ファイルをディスクに作成しない):
シール済みの出力は、そのままリポジトリに書き込んでも安全です。機密情報なのは入力のみであり、ヒアドキュメント経由でパイプすれば、ファイルシステムに一切保存されません。
kubeseal --cert "$CERT" -o yaml -f /dev/stdin > path/to/repo/sealedsecret.yaml <<'EOF'
apiVersion: v1
kind: Secret
metadata:
name: my-secret
namespace: my-namespace
type: Opaque
stringData:
key1: "value1"
key2: "value2"
EOF
代替 — 一時ファイルを使う(平文を事前に確認・編集する必要がある場合):
# Write secret to temp file (NEVER to repo)
cat > /tmp/my-secret.yaml <<'EOF'
apiVersion: v1
kind: Secret
metadata:
name: my-secret
namespace: my-namespace
type: Opaque
stringData:
key1: "value1"
key2: "value2"
EOF
# Seal and output YAML (sealed output is safe to write straight to the repo)
kubeseal --cert "$CERT" --format yaml -f /tmp/my-secret.yaml > path/to/repo/sealedsecret.yaml
# SANITIZE the plaintext temp file immediately (the sealed output is not sensitive)
echo "" > /tmp/my-secret.yaml
2. 既存の SealedSecret を再シール/更新する(フィールドを保持)
これは再シールの依頼に対する手順です。ユーザーは通常、SealedSecret のシール解除に失敗する(
illegal base64 data at input byte N、no key could decrypt secret)場合や、認証情報が変更された場合に、既存の SealedSecret の再シールを依頼します。再シールではspec.encryptedDataをその場で書き換えます。ユーザーから別途指示がない限り、metadata、spec.template(ラベル、アノテーション、type)、名前、名前空間は元のものとバイト単位で一致させ、周辺ファイルの規約にも合わせてください。
SealedSecret の一部のキーを更新する場合(例:データベースのパスワードはそのままに、S3 の認証情報を変更する場合)、選択肢は 2 つあります。
--rawを使って個別のキーを更新する(encryptedDataに貼り付ける)か、--merge-intoを使います。変更するキーの値だけが必要です。- Secret 全体を再シールします。クラスターから取得したすべてのキーの平文が必要です。
手順 1:クラスターから既存の Secret を取得する
復号された Secret を読み取るには、mcp__kubernetes__resources_get MCP ツールを優先してください。SealedSecret コントローラーは、クラスター内の通常の Secret(apiVersion: v1、kind: Secret に加え、名前と名前空間)に自動的に復号します。
または、kubectl 経由で取得します。
kubectl get secret <name> -n <namespace> -o jsonpath='{.data}'
ステップ 2:値を復号して更新後の値で再シールする
危険 — 秘密の値を YAML に埋め込まないでください。
"、:、\n、先頭の空白、または$を含む値は引用符の扱いを壊し、シークレットを破損させるか、kubesealがerror: no secrets foundで失敗する原因になります。これは生成されたパスワードや S3 キーでよく起こります。password: "$EXISTING_PASS"のような YAML を絶対に作成しないでください。
安全な方法 — --raw を使って各キーを個別にシールし、その後マージします。 --raw は stdin から値を生バイトとして読み取るため(YAML の引用符処理は関与しません)、特殊文字も正しく処理されます。デフォルトの strict スコープでは、--name と --namespace を渡す必要があり、これらは対象の SealedSecret と一致していなければなりません。
NS=my-namespace
NAME=my-secret
# Decode a value you want to PRESERVE (capture in variable, NEVER echo)
EXISTING_PASS=$(printf '%s' '<base64value>' | base64 -d)
# Re-seal the preserved value and the updated value straight into the repo file.
# printf '%s' avoids adding a trailing newline to the secret.
printf '%s' "$EXISTING_PASS" | kubeseal --cert "$CERT" \
--raw --namespace "$NS" --name "$NAME" --from-file=/dev/stdin
# -> paste the output under spec.encryptedData.password in path/to/repo/sealedsecret.yaml
printf '%s' 'new-value' | kubeseal --cert "$CERT" \
--raw --namespace "$NS" --name "$NAME" --from-file=/dev/stdin
# -> paste the output under spec.encryptedData.access-key-id
または、--merge-into(以下を参照)を使って、ほかのキーに触れずに個々のキーをその場で更新できます。
完全な Secret オブジェクトを作成する必要がある場合(例:新しい SealedSecret を一から作成する場合)は、引用符の問題を避けるため、stringData: ではなく data: フィールドに値を Base64 エンコードして指定してください。
cat > /tmp/updated-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
name: $NAME
namespace: $NS
type: Opaque
data:
access-key-id: $(printf '%s' 'new-value' | base64 -w0)
password: $(printf '%s' "$EXISTING_PASS" | base64 -w0)
EOF
kubeseal --cert "$CERT" --format yaml -f /tmp/updated-secret.yaml > path/to/repo/sealedsecret.yaml
# SANITIZE the plaintext temp file
echo "" > /tmp/updated-secret.yaml
3. 公開鍵を取得する
上記の**「証明書の取得」**を参照してください。まず certs/ を確認し、次に Kubernetes MCP を確認します。それ以外の取得元は許可されていません。また、シールする前に openssl x509 で証明書を検証する必要があります。
kubeseal コマンドリファレンス
よく使うフラグ
| フラグ | 用途 |
|---|---|
--cert <file> | 暗号化用の公開鍵ファイル。必ず明示的に指定してください。省略すると kubeseal がコントローラーを自動検出します。ローカルファイルのみを使用し、URL は使用しないでください |
-o, --format yaml|json | 出力形式(デフォルト:json) |
-f, --secret-file <file> | 入力する Secret YAML ファイル(パイプで渡す場合は /dev/stdin を使用) |
-n, --namespace <ns> | シール対象のシークレットの名前空間スコープ(コントローラーが配置されている場所ではありません) |
--scope strict|namespace-wide|cluster-wide | シール済みシークレットのスコープ(デフォルト:strict) |
--merge-into <file> | シール済みのキーを既存の SealedSecret ファイルにマージします(インプレース) |
--raw | --from-file から単一の生の値を暗号化します。--scope が必要です。また、strict スコープでは --name と --namespace も必要です |
--from-file <file> | (--raw と併用)ファイルから値を読み込みます。パイプの場合は /dev/stdin を使用します |
--name <name> | シール済みシークレットの名前(strict スコープで --raw と併用する場合に必須) |
--re-encrypt | 既存の SealedSecret をコントローラーの最新の鍵で再暗号化します(クラスターが必要) |
--validate | シール済みシークレットが復号できることを確認します — コントローラーに接続するため、クラスターへのアクセスが必要です |
--fetch-cert | 禁止 — 使用しないでください。 代わりに certs/ または Kubernetes MCP から証明書を取得してください(「証明書の取得」を参照)。失敗するとエラーメッセージが stdout に出力され、リダイレクトによって不正な証明書ファイルが作られます |
--controller-namespace <ns> | コントローラーが稼働する名前空間(デフォルト:kube-system)。--validate/--re-encrypt にのみ適用されます — 証明書の取得には使用しないでください |
--controller-name <name> | コントローラー名(デフォルト:sealed-secrets-controller)。上記と同じ制限があります |
--recovery-unseal | --recovery-private-key を使って災害復旧時に復号します(秘密鍵を扱うため、細心の注意を払って使用してください) |
スコープモード
strict(デフォルト):完全に一致する名前空間と名前でのみ、シールを解除できます。namespace-wide:指定した名前空間内であれば、任意の名前でシールを解除できます。cluster-wide:任意の名前空間で、任意の名前を使ってシールを解除できます。
既存のシークレットへのマージ
シークレット全体を再シールせずに個々のキーを更新するには(すべての平文の値を取得できない場合に便利です):
# Seal only the keys you want to update
cat > /tmp/delta-secret.yaml <<'EOF'
apiVersion: v1
kind: Secret
metadata:
name: my-secret
namespace: my-namespace
type: Opaque
stringData:
new-key: "new-value"
EOF
# Merge into existing sealed secret
kubeseal --cert "$CERT" --merge-into path/to/repo/sealedsecret.yaml -f /tmp/delta-secret.yaml
# SANITIZE
echo "" > /tmp/delta-secret.yaml
警告:--merge-into はキーを追加または更新しますが、キーを削除しません。キーを削除するには、シークレット全体を一から再シールする必要があります。
単一の値を暗号化する(--raw)
--raw は1つの値を暗号化し、暗号文の文字列を出力します。これを自分で spec.encryptedData.<key> に貼り付けます。特殊文字を含む値を扱う最も安全な方法です(標準入力から生のバイト列を読み取り、YAML のクォート処理を完全に回避します)。また、平文をファイルに書き込むこともありません。
# strict scope (default): --name AND --namespace are REQUIRED and must match the target SealedSecret
printf '%s' 'p@ss"word:with$pecial' | kubeseal --cert "$CERT" \
--raw --namespace my-namespace --name my-secret --from-file=/dev/stdin
# namespace-wide: only --namespace required
printf '%s' 'value' | kubeseal --cert "$CERT" \
--raw --scope namespace-wide --namespace my-namespace --from-file=/dev/stdin
# cluster-wide: neither required
printf '%s' 'value' | kubeseal --cert "$CERT" \
--raw --scope cluster-wide --from-file=/dev/stdin
- シークレット値に末尾の改行が追加されないように、
echoではなくprintf '%s'を使用してください。 --rawの暗号文は、指定したスコープ/名前/名前空間に紐付けられます。貼り付け先の SealedSecret と一致しない場合、コントローラーは復号を拒否します。
新しいコントローラーキーにローテーションする(--re-encrypt)
コントローラーのキーペアをローテーションした後も、既存の SealedSecret は復号できます(コントローラーは古いキーを保持します)が、最新のキーで再暗号化する必要があります。この操作にはクラスターへのアクセスが必要です。
kubeseal --re-encrypt -o yaml -f path/to/repo/sealedsecret.yaml > /tmp/reencrypted.yaml
cp /tmp/reencrypted.yaml path/to/repo/sealedsecret.yaml
--re-encrypt は平文を決して公開しません。再暗号化はコントローラー内で行われます。
SealedSecret の YAML 構造
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: my-secret
namespace: my-namespace
spec:
encryptedData:
key1: AgC...base64encrypteddata...==
key2: AgC...base64encrypteddata...==
template:
metadata:
name: my-secret
namespace: my-namespace
type: Opaque
よくあるパターン
S3 互換オブジェクトストアの認証情報
apiVersion: v1
kind: Secret
metadata:
name: s3-credentials
namespace: my-namespace
type: Opaque
stringData:
access-key-id: "<access-key>"
secret-access-key: "<secret-key>"
データベースの認証情報
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: my-namespace
type: Opaque
stringData:
username: "admin"
password: "<password>"
TLS 証明書
apiVersion: v1
kind: Secret
metadata:
name: tls-cert
namespace: my-namespace
type: kubernetes.io/tls
stringData:
tls.crt: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
tls.key: |
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
注意点と落とし穴
-
各キーは個別に暗号化されます — 再シールせずに、異なるシール操作で暗号化した値を同じ
encryptedDataブロックに混在させることはできません。--merge-intoを使用するか、シークレット全体を再シールしてください。 -
SealedSecret はコントローラーのキーペアに紐付けられます — コントローラーはローテーション後も古いキーを保持するため、既存の SealedSecret は引き続き復号できます。ただし、新しくシールする場合は現在の公開証明書が必要です。また、既存の SealedSecret は
kubeseal --re-encryptを使って最新のキーで再暗号化することが推奨されます。古いキーが削除されると、再暗号化されていない SealedSecret は復号できなくなります。 -
stringDataとdata— 平文の値にはstringDataを使用してください(エンコードは kubeseal が処理します)。事前に Base64 エンコードした値にはdataを使用してください。 -
スコープが重要です —
strictスコープの SealedSecret は、完全に一致する名前と名前空間でのみ復号できます。シークレットの名前を変更すると、復号できなくなります。名前の変更が想定される場合は、namespace-wideまたはcluster-wideを使用してください。 -
一時ファイルの管理 — シールした後は、必ず一時ファイルを上書きしてください。平文のシークレットをディスクに残さないでください。
-
Git の履歴 — シークレットを誤ってコミットすると、Git の履歴に残ります。
git filter-repoを使って削除し、その後、認証情報をローテーションしてください。 -
証明書のように見えるファイルが、実際には証明書ではない場合があります(実際に確認されています)。 コントローラーが存在しないクラスターに対して
kubeseal --fetch-cert > certs/sealed-secrets-cert.pemを実行すると、ファイルには次の内容が書き込まれます。error: cannot get sealed secret service: services "sealed-secrets-controller" not found.このファイルを使ったシールは成功したように見えますが、破損が判明するのは復号時で、その時点では平文が失われている可能性があります。エラーは
illegal base64 data at input byte Nまたはno key could decrypt secretです。このため、--fetch-certは禁止されており、シールする前に毎回openssl x509で検証する必要があります。サイズが数百バイトで、-----BEGIN CERTIFICATE-----の行がない証明書ファイルは、この失敗に該当します。 -
certs/内の秘密鍵は、利便性のためのものではなく、セキュリティインシデントです。certs/に証明書と一緒にtls.keyが含まれていると、リポジトリにアクセスできる人は誰でも、その中のすべての SealedSecret を復号できます。報告してください。コントローラーキーをローテーションし、そのファイルを Git の履歴から削除する必要があります。シールにはtls.crtだけを使用してください。
検証
デプロイする前に、稼働中のコントローラーに対してシール済みシークレットを検証してください(クラスターに接続するため、オフラインでは動作しません)。
# --validate talks to the controller (no --cert needed); add --controller-namespace/--controller-name if non-default
kubeseal --validate -f path/to/repo/sealedsecret.yaml
デプロイした後に、正しく復号されたことを確認してください。
# Check the Secret exists and has the expected keys
kubectl get secret <name> -n <namespace> -o jsonpath='{.data}' | python3 -c "import sys,json; [print(k) for k in json.load(sys.stdin).keys()]"
または、mcp__kubernetes__resources_get MCP ツールを使って、復号された Secret を確認してください。
シーリング後のチェックリスト
- 証明書は
certs/または Kubernetes MCP から取得しました。--fetch-cert、kubectl、URL は使用していません - シーリング前に
openssl x509で証明書を検証しました - 一時ファイルは上書きしました(削除しただけではありません)
- シェル履歴に平文の認証情報はありません(機密性の高い操作の前に
set +o historyを使用してください) - git diff の出力に認証情報はありません
- SealedSecret YAML を正しいブランチにコミットしました
- シール用の秘密鍵(
tls.key)はリポジトリにも出力にもありません - クラスター上でシール済みシークレットが正しく復号されることを確認しました