コンテンツにスキップ

シークレットを入れ替える

tegata v0.3.1 の文書

シークレットを、それを使うサービスを止めずに新しい値に替える方法です。手順は、古い値と新しい値が同時に使えるかどうかで変わります。previous と settle が要る値と要らない値がある理由は、例とともにシークレットに previous が要るときにあります。

値 例 手順
自分のサービスが確かめていて、2 つの値を受け付けられる セッションの鍵、署名の鍵、Webhook のシークレット(previous を付けた random) rotate → render → settle → render → prune
ほかのサービスが発行していて、古い方を消すまでは新旧どちらも使える cloudflare/api_token、tailscale/auth_key、aws/ses_smtp_credentials rotate → render → prune
値が 1 つしかなく、新しく作るとすぐに古い方が使えなくなる gcp/sql_user、cloudflare/tunnel_token rotate(先に聞く)→ すぐに render → prune
手で入れる manual set → render → prune

「render」は、そのシークレットを使うすべてのサーバーで tegata render を流し、ファイルを読むサービスを再起動(または設定を読み直し)することです。tegata render をタイマーで --on-change 付きで動かしていれば、サーバーが数分のうちに自分でやります(サーバーで動かす)。GitHub Actions の secrets(届け先)には何も要りません。rotate、settle、set のときに tegata が新しい値を書き、ワークフローは次の実行からそれを使います。

tegata status で、各リソースがどこまで進んでいるかが分かります。generation 2 (+1 to prune) は、入れ替えのあとの片付けがまだ済んでいないという意味です。

自分のサービスが確かめる鍵:rotate、settle、prune

Section titled “自分のサービスが確かめる鍵:rotate、settle、prune”

リソースに previous のシークレットを付け、サービスが、どちらかに合う値を受け付けるようにします。

resources:
session_key:
provider: random
stores:
main:
- { name: SESSION_KEY, readers: [app] }
previous: { name: PREVIOUS_SESSION_KEY } # read by the readers of SESSION_KEY
templates/app/app.env
SESSION_KEY=__SESSION_KEY__
PREVIOUS_SESSION_KEY=__PREVIOUS_SESSION_KEY__

サービスは SESSION_KEY で署名し、両方を受け付けます。

ターミナルウィンドウ
tegata rotate session_key # SESSION_KEY: new; PREVIOUS_SESSION_KEY: the old value
# render and restart: services sign with the new key, and still accept the old one
tegata settle session_key # PREVIOUS_SESSION_KEY: the new value too
# render and restart: the old value is no longer accepted
tegata prune session_key # disable the old versions in the store

rotate と settle の間は、古い鍵で署名した値を有効にしておく必要がある間(セッションの鍵なら、セッションの有効期間)だけ待ちます。

並べて使えるトークン:rotate、prune

Section titled “並べて使えるトークン:rotate、prune”

プロバイダーが、1 つ目のトークンの隣に 2 つ目を作ります。prune が古い方を消すまでは、どちらも使えます。

ターミナルウィンドウ
tegata rotate releases_read # a second token exists; the old one still works
# render and restart on every server that uses it
tegata prune releases_read # delete the old token on the provider, disable the old versions

prune は、すべてのサーバーが新しい値を使うようになってから流してください。古いトークンのままのサービスは、それが消されると動かなくなります。aws/ses_smtp_credentials は、IAM ユーザーが持てるアクセスキーが 2 つまでなので、もう一度入れ替える前に prune してください。

すぐに置き換わる値:rotate、すぐに render、prune

Section titled “すぐに置き換わる値:rotate、すぐに render、prune”

Cloud SQL のユーザーのパスワードは 1 つ、Cloudflare Tunnel のシークレットも 1 つです。新しく作ると、古い方はすぐに使えなくなります。開いている接続は残りますが、新しい接続は、サーバーが新しい値を持つまで失敗します。apply と rotate は、何が止まるかと誰が読んでいるかを示し、yes と打つように求めます(スクリプトでは --yes で聞かずに進み、--dry-run は示すだけです)。

ターミナルウィンドウ
tegata rotate --dry-run db_password # see what would change
tegata rotate db_password # type yes
# render and restart at once on every server that uses it
tegata prune db_password # disable the old versions

cloudflare/tunnel_token では、prune は、入れ替えの前に起動したコネクターがまだつながっていないかを確かめ、すべての cloudflared が新しいトークンで再起動するまでは(コネクターの名前を挙げて)断ります。gcp/sql_user では、prune は Cloud SQL の上で何も消しません。古いパスワードはもう使えなくなっているためです。

ターミナルウィンドウ
tegata set admin_password # hidden input; or pipe it on standard input
# render and restart
tegata prune admin_password

リソースに previous があれば、set は rotate と同じように、先に今の値をそこに写します。そのあとは最初の節の手順になります。

  • サーバーが新しい値を読めない。 render は __NAME__ をファイルに残し、その名前を挙げます。サーバーのアイデンティティがそのシークレットの readers に入っているか、足したあとに tegata apply を流したかを確かめてください(読み手は tegata status で分かります。tegata check <identity> <templates> で、雛形とアイデンティティが読めるものを比べられます)。
  • サーバーが新しい値を持つ前に prune してしまった。 新しい値はもう保存先にあります。そのサーバーで render して再起動してください。古い版は消さずに無効にしてあるだけなので、Secret Manager で有効に戻せば読めます。ただし、プロバイダーの側で消したトークンは戻せません。
  • 古い値が漏れた。 間を置かずに、rotate、render、prune を続けて流してください。

このページの問題を報告する(GitHub。英語でも日本語でも)