🔐

Secret Managerのシークレットバージョン「破棄」時の挙動をまとめる

2024/05/21に公開

3行まとめ

  • Google CloudのSecret Managerのシークレットバージョンには有効 無効 破棄の3つのステータスがある
  • 破棄すると有効 無効 ステータスには2度と戻せなくなりシークレットにアクセスできなくなる
  • シークレットバージョンが削除されるわけではないのでlatestバージョンの破棄は特に慎重におこなった方がいい

Secret Managerのシークレットバージョンが持つ3つのステータス

Google CloudのSecret Managerではシークレットがバージョン管理できるようになっている。そして、以下の3つのステータスを持つことができる。

  • 有効
  • 無効
  • 破棄

https://cloud.google.com/secret-manager/docs/disable-secret-version?1hl=ja

簡単にそれぞれのステータスについて説明する。

有効

そのバージョンに保存されたシークレットは呼び出し、利用できる状態になっている。
保存したシークレットの値を確認することも可能。
後述する無効 破棄ステータスへの変更が可能。

無効

そのバージョンに保存されたシークレットは呼び出し不可、利用できない状態になっている。
保存したシークレットの値を確認することは不可能。
有効と後述する破棄ステータスへの変更が可能。

破棄

そのバージョンに保存されたシークレットは呼び出し不可、利用できない状態になっている。
保存したシークレットの値を確認することは不可能。
どのステータスへの変更も不可能。

今回はその中でも破棄ステータスの挙動についてをまとめていきたい。

「破棄」は「削除」ではない

そもそもシークレットバージョンには削除という概念がない。
secret-manager-version-destroyed-01
そのため、上記のように当該のバージョンは永久欠番になるようなイメージだ。
後述するが、破棄されたバージョンのシークレットを呼び出すと
Secret Version [projects/XXXXXXXXXXXXXX/secrets/hogehogesecret/versions/hoge] is in DESTROYED state.
といったようなエラーが返ってくる。(これはCloud FunctionsのPythonランタイムで呼び出した時のエラーメッセージ)
hogeバージョンを呼び出すようにコードを書いていた場合、hogeバージョンは削除して新規に作成することも不可能なため永久欠番になってしまい、呼び出せなくなってしまう。
「破棄したバージョンを呼び出さないようにすればいい」という話であるが、ここで見落とされてしまいがちなポイントについて話したいと思う。
3行まとめでも書いていたlatestについてだ。

latestバージョンが「破棄」された場合

Secret Managerのシークレットバージョンにアクセスするには、以下のような指定方法がある。

シークレット バージョンにアクセスすると、シークレットのコンテンツとシークレット バージョンに関する追加のメタデータが返されます。シークレット バージョンにアクセスする際は、version-id または alias を指定します(割り当てられている場合)。また、"latest" をバージョンとして指定することにより、シークレットの最新バージョンにもアクセスできます。

https://cloud.google.com/secret-manager/docs/access-secret-version?hl=ja

バージョンを更新する際、基本的には以前のバージョンを無効化して最新のバージョンを有効にする運用をすることが多いと思われる。
例えば、Cloud Functionsでシークレットを取得する際に以下のようなコードを書いていたとする。

main.py
def get_secret(project_id, secret_id, version='latest'):
    """
    Secrets Managerから情報を取得する
    """
    sm_client = secretmanager.SecretManagerServiceClient()
    name = f"projects/{project_id}/secrets/{secret_id}/versions/{version}"
    response = sm_client.access_secret_version(request={"name": name})
    return response.payload.data.decode("UTF-8").strip()

get_secret関数の引数にlatestバージョンを指定してSecret Managerから保存したシークレットにアクセスしている。
この時、Secret Managerのシークレットが以下のような状態になったとしよう。
secret-manager-version-destroyed-02
破棄ステータスになったバージョンは、上記で説明した通り残り続ける。現状latestバージョンは3バージョンということになり、このままアクセスしようとすると以下のようなエラーが出てしまう。

status = StatusCode.FAILED_PRECONDITION
details = "Secret Version [projects/XXXXXXXXXXXXXX/secrets/hogehogesecret/versions/3] is in DESTROYED state."

上記環境でこの状況を解決するには、以下の手段のどれかを取る必要がある。

  1. Secret Managerのシークレットで新しいバージョンを作成し、latestバージョンに有効ステータスのシークレットがある状態にする
  2. 有効ステータスのバージョンを明示的に指定するようにコードを変更する

ベストな運用方法を考えてみる

latestバージョンを指定して常に最新バージョンのシークレットを利用すると、シークレット更新時にコードのデプロイ作業をしなくて済み常に最新のシークレットを利用することができるので運用面でもセキュリティ面でもlatestバージョンを指定して利用することは望ましいと私は考えている。
Secret Managerにシークレットを保存する際に常に最新のシークレットが有効になる状態を作るように運用することが大事になってくるのではないだろうか。
ただ、どうしても以前のバージョンのシークレットを使わないといけない状況は出てくるので綺麗にバージョン管理を行うことが難しくなることはあると思う。

改善案としては、以下のようなものが実現可能であると考えている。
現在はコード内でバージョンを指定しているが、Cloud Functionsの環境変数として外出ししてバージョンを指定すればコード自体の変更が入らないので運用面の安定感は少し上がるのではないだろうか。(再デプロイ自体は必要になるのでサービスとしての一時的な停止はどうしても発生してしまうが)

おまけ

Secret Managerのバージョンにはエイリアス機能があり、エイリアス名でバージョンを指定することができる。
最新じゃないlatestを作ってしまう……というトンチのようなことができたりしないかな?と思って確認してみた。
secret-manager-version-destroyed-03
それはそう
案の定できないように予約されていた。エイリアス機能をトンチ的な使い方をするのはやめよう(できないが)

Discussion