APIで取得する、さくらのクラウドのシークレットマネージャ活用法

さくらのクラウド , セキュリティ # API # KMS # サービスプリンシパル # シークレットマネージャ

こんにちは、UOZUです!

アプリケーションからデータベースへ接続するとき、パスワードをどこに保管していますか?

設定ファイルやスクリプトへ直接記載していると、サーバーの台数が増えるにつれて変更箇所を把握しにくくなります。ソースコードやバックアップに認証情報が含まれてしまうこともあります。

今回は、さくらのクラウドの「シークレットマネージャ」を使って、パスワードの保管先をまとめる方法を紹介します。まずは検証用のダミー情報を登録し、アプリケーションから利用する仕組みを理解しましょう。

シークレットマネージャとは?

シークレットマネージャは、パスワードなどの機密情報を暗号化して保管するサービスです。コントロールパネルでは「シークレット保管庫」として管理します。利用には、暗号鍵を管理するKMSが必要です。

例えば、WebアプリケーションのDBパスワードを管理する場合、アプリケーションは必要な認証情報を保管庫から取得し、その情報でDBに接続する構成にできます。

導入するだけで既存の設定ファイルが自動的に置き換わるわけではありません。アプリケーション側にも、保管庫から値を取得して利用する実装が必要です。

KMSとシークレットマネージャの役割

両者は次のように役割が分かれています。

サービス管理するもの今回の用途
KMS暗号鍵保管庫で使用する鍵を用意する
シークレットマネージャパスワードなどの機密情報DBパスワードを名前付きで保管する

ここでは、検証専用のKMSキーと保管庫を1つずつ作成します。KMSキーと保管庫はそれぞれ課金対象になるため、作成前に公式サービスページで料金を確認してください。

1.KMSキーを作成する

コントロールパネルで [グローバル]→[KMS キー] を開きます。
[追加] を選択し、以下の内容で KMS キーを作成します。

項目今回の設定例
キー生成方法自動生成
名前uozu-secret-key
説明シークレットマネージャ検証用
KMSキー追加画面

KMSキーは、保管した情報を取り出すためにも必要です。単なる管理用のラベルとして扱わず、どの保管庫が利用しているかを把握しておきましょう。

2.シークレット保管庫を作成する

続いて、[グローバル]→[シークレット保管庫] を開き、[追加] を選択します。
「使用する KMS キー」で、先ほど作成した KMS キーを選択してシークレット保管庫を作成します。

項目今回の設定例
使用するKMSキーuozu-secret-key
名前uozu-secret-vault
説明シークレットマネージャ検証用
シークレット保管庫追加画面

3.検証用パスワードを登録する

作成したシークレット保管庫の詳細画面の「シークレット」タブの「追加」から、名前と値を登録します。

「名前」は、アプリケーションから取得するシークレットを識別するための名称です。「値」には、保管するパスワードそのものを入力します。

シークレット保管庫の名前や説明欄には、パスワードなどの機密情報を記載しないでください。

項目入力例
名前uozu-db-password
値DemoOnly-Password
シークレット情報追加画面

※値は記事用のダミーです。実際のDBやサービスのパスワードには使用しないでください。

「名前」はアプリケーションから目的の情報を指定するための識別子、「値」は保管するパスワードそのものです。保管庫の名前や説明欄にパスワードを書かないようにします。

登録したパスワードはどう使う?

アプリケーションからの利用にはAPIを使います。

目的API操作(ベースURLからの相対パス)
シークレット一覧を取得GET /vaults/{vault_resource_id}/secrets
シークレットの値を復号して取得POST /vaults/{vault_resource_id}/secrets/unveil

APIキーを利用する例

アプリケーションからシークレットを取得するには、API を利用します。

API キーは、ログイン後に [API キー] から発行できます。パスワードを表示させる為、「作成・削除」権限を付与した API キーを使用します。

APIキー作成画面
アクセストークン・アクセストークンシークレット表示画面

作成後、アクセストークンとアクセストークンシークレットが表示されます。

アクセストークンおよびアクセストークンシークレットは、漏えいしないよう安全に保管してください。

curlでの表示例

curlコマンドで利用が可能です。非常にシンプルですが、権限に「作成・削除」が付与されているので、キーの利用には注意してください。

$ curl -s \
  --user "アクセストークン:アクセストークンシークレット" \
  --request POST \
  --header 'Content-Type: application/json' \
  --data '{"Secret":{"Name":"uozu-db-password"}}' \
  'https://secure.sakura.ad.jp/cloud/zone/is1a/api/cloud/1.1/secretmanager/vaults/113802280710/secrets/unveil' \
  | jq .
--
{
  "Secret": {
    "Name": "uozu-db-password",
    "Version": 1,
    "Value": "DemoOnly-Password"
  },
  "is_ok": true
}

サービスプリンシパルを利用する例

API の利用権限をより細かく制御したい場合は、サービスプリンシパルを利用できます。

サービスプリンシパルを作成した後、サービスプリンシパルキーを発行します。その際、RSA 公開鍵の登録が必要です。ssh-keygen などを使用して鍵ペアを作成し、公開鍵を登録してください。

サービスプリンシパル新規作成画面
サービスプリンシパルキー発行画面
サービスプリンシパルへの公開鍵登録後の画面

続いて、IAM ポリシーでサービスプリンシパルにシークレットマネージャを利用するための権限を付与します。

「IAMポリシー」内の「サービスプリンシパル」へ、「シークレットマネージャ利用者」の権限の設定を行います。

サービスプリンシパルのロール設定画面

実際の利用サンプル(サービスプリンシパル)

実際にAPIを利用してパスワード表示までが可能なスクリプトのサンプルを作成してみました。

利用の際は、サービスプリンシパルのリソースIDをSP_ID「000000000000」に、KID「XXX~」はサービスプリンシパルキーKIDに、KEY_FILE「$HOME/id_rsa」はサービスプリンシパルに登録した公開鍵と対になる秘密鍵を、VAULT_ID「111111111111」はシークレット保管庫のリソースIDを指定してください。

$ cat get-secret.sh
#!/usr/bin/env bash
set -euo pipefail

SP_ID='000000000000'
KID='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
KEY_FILE="$HOME/id_rsa"

VAULT_ID='111111111111'
SECRET_NAME='uozu-db-password'
TOKEN_URL='https://secure.sakura.ad.jp/cloud/api/iam/1.0/service-principals/oauth2/token'

if [ ! -r "$KEY_FILE" ]; then
  echo "秘密鍵を読み込めません: $KEY_FILE" >&2
  exit 1
fi

b64url() {
  openssl base64 -A | tr '+/' '-_' | tr -d '='
}

JWT_HEADER=$(jq -cn --arg kid "$KID" \
  '{alg:"RS256",kid:$kid,typ:"JWT"}' | b64url)

NOW=$(date +%s)
JWT_PAYLOAD=$(jq -cn \
  --arg aud "$TOKEN_URL" \
  --arg id "$SP_ID" \
  --argjson now "$NOW" \
  '{aud:$aud,iat:$now,exp:($now+300),iss:$id,sub:$id}' |
  b64url)

JWT_INPUT="${JWT_HEADER}.${JWT_PAYLOAD}"
JWT_SIGNATURE=$(printf '%s' "$JWT_INPUT" |
  openssl dgst -sha256 -sign "$KEY_FILE" |
  b64url)
JWT="${JWT_INPUT}.${JWT_SIGNATURE}"

echo '1. トークン発行' >&2

if TOKEN_RESPONSE=$(curl --fail-with-body --silent --show-error \
  --request POST \
  --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
  --data-urlencode "assertion=${JWT}" \
  "$TOKEN_URL"); then
  TOKEN=$(printf '%s' "$TOKEN_RESPONSE" |
    jq -er '.access_token | select(type == "string" and length > 0)')
else
  echo 'トークン発行エラー:' >&2
  printf '%s\n' "$TOKEN_RESPONSE" >&2
  exit 1
fi

echo '2. シークレット取得' >&2

REQUEST=$(jq -cn --arg name "$SECRET_NAME" \
  '{Secret:{Name:$name}}')

if SECRET_RESPONSE=$(curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Authorization: Bearer ${TOKEN}" \
  --header 'Content-Type: application/json' \
  --header 'X-Requested-With: XMLHttpRequest' \
  --data "$REQUEST" \
  "https://secure.sakura.ad.jp/cloud/zone/is1a/api/cloud/1.1/secretmanager/vaults/${VAULT_ID}/secrets/unveil"); then
  printf '%s' "$SECRET_RESPONSE" |
    jq -er '.Secret.Value // empty'
else
  echo 'シークレット取得エラー:' >&2
  printf '%s\n' "$SECRET_RESPONSE" >&2
  exit 1
fi

以下のコマンドで利用が出来るはずです。

$ bash -n ~/get-secret.sh && bash ~/get-secret.sh
--
1. トークン発行
2. シークレット取得
DemoOnly-Password

APIの認証情報は別途保護する

DBパスワードを保管庫に移しても、APIにアクセスするための認証情報の管理は残ります。

この認証情報をソースコードに直接記載すると、別の秘密情報を埋め込むことになってしまいます。実行環境に適した方法で渡し、ファイルへ保存する場合はアクセスできるOSユーザーを限定します。

権限も、アプリケーションに必要な範囲へ絞ります。値を取得するプログラムに、無関係なリソースの削除や管理まで許可する必要があるかを確認しましょう。

シークレットマネージャは、秘密情報を一元管理するための仕組みです。サーバー自体への侵入や、取得後の値がログへ出力される問題まで自動的に防ぐものではありません。

さいごに

今回は、さくらのクラウドのシークレットマネージャを使用して、検証用のパスワードを保管・取得する手順を紹介しました。

API キーでシークレットを取得する場合は、必要な権限を事前に確認してください。検証時には、「リソース閲覧」や「設定編集」の権限ではシークレットを表示できず、「作成・削除」権限が必要でした。実際の運用では、必要最小限の権限で実現できるかを十分に検証することをおすすめします。

より細かな権限管理が必要な場合は、サービスプリンシパルの利用を検討できます。ただし、JWT の生成や秘密鍵による署名が必要になるため、鍵の保管方法やローテーション方法も含めて設計してください。

最後までお読みいただき、ありがとうございました!

この記事を書いた人

UOZU

ネットアシスト運用チーム10年目の運用エンジニア

さくらのクラウド検定 ベーシック (第一回)

さくらのクラウド検定 アドバンスド (第一回)

AWS Certified Solutions Architect - Associate

AWS Certified AI Practitioner