HashiCorp Vault 安全存储后端在 Apache APISIX 生态系统中的应用
API7.ai
January 24, 2022
マイクロサービスベースのアーキテクチャの台頭に伴い、セキュリティを維持することが以前よりもはるかに困難になっています。100のバックエンドサーバーインスタンスが単一の静的シークレット認証情報を使用してデータベースサーバーにアクセスする時代はすでに過去のものです。なぜなら、認証情報が漏洩した場合、システム全体が危険にさらされ、その認証情報を失効させると大規模なサービス停止が発生するからです(インスタンスが再設定されるまで誰も何もアクセスできなくなります)。セキュリティ侵害の可能性を完全に排除することはできませんが、そのような状況での被害範囲を制御することは私たち次第です。このようなシナリオに対処するために、本番環境ではHashiCorp Vaultのような人気のあるソリューションが、IDベースのシークレットおよび暗号化管理システムとして活用されます。この記事では、VaultをApache APISIX(クラウドネイティブのAPIゲートウェイ)のjwt-authプラグインと統合し、両者の優れた機能を効果的に活用する方法を紹介します。
Vaultとは
HashiCorp Vaultは、組織がシークレットへのアクセスを管理し、安全に伝達することを支援するために設計されています。シークレットとは、厳密に制御および監視される必要がある機密情報を解除するために使用されるパスワード、APIキー、SSHキー、RSAトークン、OTPなどの形式を指します。実際の世界では、シークレットが設定ファイルやプログラムコード内の変数として保存され、GitHub、BitBucket、GitLabなどのバージョン管理システムに誤って含まれることがよくあります。これはセキュリティ上の重大な脅威となります。Vaultは、シークレットを一元管理することでこの問題を解決します。Vaultは、静的シークレットの暗号化されたストレージ、TTLリース付きの動的シークレットの生成、ユーザー(マシンまたは人間)の認証を提供し、特定のシークレットにアクセスする権限があることを確認します。これにより、セキュリティ侵害が発生した場合でも、被害範囲が小さく抑えられます。
Vaultは、インフラストラクチャ内のすべてのシークレットを管理するための単一のインターフェースを提供することで、アクセスの制御と管理を非常に簡単にします。さらに、詳細な監査ログを作成し、誰が何にアクセスしたかを追跡する柔軟性も提供します。
APISIX jwt-authプラグインについて
これは、リクエストがアップストリームURIに転送される前にJWT(JSON Web Token、詳細)認証を実行するためにAPISIXルートにアタッチできる認証プラグインです。簡単に言えば、重要なリソースへの認可につながる安全な認証メカニズムです。通常、発行者はJWTに署名するために秘密鍵またはテキストシークレットを使用します。JWTの受信者は、署名が発行者によって署名された後に変更されていないことを確認するために署名を検証します。JWTメカニズム全体の完全性は、署名シークレット(テキストシークレットまたはRSAキーペア)に依存します。これにより、未認証のソースが署名キーを推測し、JWT内のクレームを変更しようとすることを困難にします。
したがって、これらのキーを安全な環境に保存することは非常に重要です。悪意のある者の手に渡ると、インフラストラクチャ全体のセキュリティが危険にさらされる可能性があります。APISIX側では標準的なSecOpsプラクティスに従うためのすべての手段を講じていますが、本番環境では、HashiCorp Vaultのような集中型キー管理ソリューションを使用して、詳細な監査証跡、定期的なキーローテーション、キーの失効などを実施することが一般的です。また、インフラストラクチャ全体でキーローテーションが行われるたびにApache APISIXの設定を更新する必要があると、非常に面倒な問題になります。
VaultとApache APISIXを使用する手順
Vaultとの統合には、Apache APISIXにconfig.yamlでVaultの設定を読み込む必要があります。
内部的には、APISIXはVaultサーバーとKVシークレットエンジンv1のHTTP APIを使用して通信します。多くのエンタープライズソリューションが本番環境でKV Secrets Engine - Version 1を使用することを好むため、APISIX-Vaultサポートの初期段階ではバージョン1のみをサポートしています。今後のリリースでは、K/Vバージョン2のサポートを追加する予定です。
Vaultを使用する主な理由は、低信頼環境でのセキュリティ上の懸念です。APISIX開発者は、ユーザーの優先事項を真剣に理解しています。そのため、APISIXサーバーに限定されたアクセスを許可する短いスコープのVaultアクセストークンを使用することを推奨しています。
Vaultの設定
すでに必要な権限を持つVaultインスタンスが実行されている場合は、このセクションをスキップしてください。このセクションでは、Apache APISIXエコシステム内でVaultを使用するためのベストプラクティスを共有します。以下の手順に従ってください。
ステップ1: Vaultサーバーを起動する
ここでは、Docker、プリコンパイル済みバイナリ、またはソースからのビルドなど、複数のオプションがあります。Vaultサーバーと通信するためにはVault CLIクライアントが必要なので、Dockerアプローチではなくプリコンパイル済みバイナリを使用することをお勧めします。ただし、これは完全にあなた次第です(Vaultの公式インストールドキュメントを参照してください)。開発サーバーを起動するには、次のコマンドを実行します。
$ vault server -dev -dev-root-token-id=root
…
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variable:
export VAULT_ADDR='http://127.0.0.1:8200'
The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.
Unseal Key: 12hURx2eDPKK1tzK+8TkgH9pPhPNJFpyfc/imCLgJKY=
Root Token: root
Development mode should NOT be used in production installations!
現在のCLIに正しい環境変数を設定します。
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='root'
適切なパスプレフィックスでVault k/vバージョン1シークレットエンジンバックエンドを有効にします。このデモでは、kv
パスを選択します。これにより、Vaultのデフォルトのシークレットパス(kvバージョン2用)との衝突を避けることができます。
$ vault secrets enable -path=kv -version=1 kv
Success! Enabled the kv secrets engine at: kv/
# ステータスを再確認するには、次のコマンドを実行します
$ vault secrets list
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_4eeb394c per-token private secret storage
identity/ identity identity_5ca6201e identity store
kv/ kv kv_92cd6d37 n/a
secret/ kv kv_6dd46a53 key/value secret storage
sys/ system system_2045ddb1 system endpoints used for control, policy and debugging
ステップ2: APISIX用のVaultアクセストークンを生成する
この記事は、jwt-auth
プラグインの観点からVaultを使用することに関するものです。したがって、APISIXコンシューマー(APISIXエコシステムでのコンシューマーについて不慣れな場合は、Apache APISIX Consumerのドキュメントを読んでください)のユーザー名がjack
の場合、jwt-auth
プラグインは(Vault設定が有効になっている場合)、Vaultのkvストレージ内の<config.yaml内のvault.prefix>/consumer/<consumer.username>/jwt-auth
にあるシークレットを検索します。このコンテキストでは、config.yaml
内のvault.prefix
としてkv/apisix
名前空間(Vaultパス)を割り当てている場合、kv/apisix/consumer/
のパスに対するポリシーを作成することをお勧めします。末尾のアスタリスク()は、kv/apisix/consumer
プレフィックスを持つ任意のパスに対する読み取りを許可することを保証します。
HashiCorp Configuration Language(HCL)でポリシーファイルを作成します。
$ tee apisix-policy.hcl << EOF
path "kv/apisix/consumer/*" {
capabilities = ["read"]
}
EOF
Vaultインスタンスにポリシーを適用します。
$ vault policy write apisix-policy apisix-policy.hcl
Success! Uploaded policy: apisix-policy
新しく定義されたポリシーを使用して、小さなアクセス境界で設定されたトークンを生成します。
$ vault token create -policy="apisix-policy"
Key Value
--- -----
token s.KUWFVhIXgoRuQbbp3j1eMVGa
token_accessor nPXT3q0mfZkLmhshfioOyx8L
token_duration 768h
token_renewable true
token_policies ["apisix-policy" "default"]
identity_policies []
policies ["apisix-policy" "default"]
このデモでは、s.KUWFVhIXgoRuQbbp3j1eMVGa
がアクセストークンです。
Apache APISIXにVault設定を追加する
前述のように、Apache APISIXはVault HTTP APIを介してVaultインスタンスと通信します。必要な設定はconfig.yamlに追加する必要があります。以下は、使用できるさまざまなフィールドに関する簡単な情報です。
- host: Vaultサーバーが実行されているホストアドレス。
- timeout: 各リクエストのHTTPタイムアウト。
- token: Vaultインスタンスから生成されたトークンで、Vaultからデータを読み取るためのアクセスを許可します。
- prefix: プレフィックスを有効にすると、ポリシーの適用、限定されたスコープのトークンの生成、APISIXからアクセスできるデータを厳密に制御することができます。有効なプレフィックスは(
kv/apisix
、secret
など)です。
vault:
host: 'http://0.0.0.0:8200'
timeout: 10
token: 's.KUWFVhIXgoRuQbbp3j1eMVGa'
prefix: 'kv/apisix'
APISIXコンシューマーを作成する
APISIXには、認証シナリオと並行して使用されるコンシューマーレベルの抽象化があります。APISIXルートに対して認証を有効にするには、その特定の認証サービスの適切な設定を持つコンシューマーが必要です。その後、APISIXはコンシューマー設定に従って認証を成功させ、リクエストをアップストリームURIに転送できます。APISIXコンシューマーには2つのフィールドがあります。1つはusername
(必須)で、他のコンシューマーから識別するために使用され、もう1つはplugins
で、コンシューマー固有のプラグイン設定を保持します。
この記事では、jwt-auth
プラグインを持つコンシューマーを作成します。これは、対応するルートまたはサービスに対してJWT認証を実行します。
Vault設定でjwt-auth
を有効にするには、次のリクエストを行います。
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "test-key",
"vault": {}
}
}
}'
ここで、プラグインはVaultパス(<conf.yamlのvault.prefix>/consumer/jack/jwt-auth
)内のキーシークレットを検索し、コンシューマーjack
の設定に記載されているシークレットを使用して、後続の署名とJWT検証を行います。同じパスにキーが見つからない場合、プラグインはエラーをログに記録し、JWT認証に失敗します。
テスト用のアップストリームサーバーを設定する
動作をテストするために、アップストリーム(単純なpingハンドラーでpongを返す)のルートを作成できます。これは、プレーンなGo HTTPサーバーで設定できます。
// シンプルなアップストリームサーバー
package main
import "net/http"
func ping(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("secure/pong\n"))
}
func main() {
http.HandleFunc("/secure/ping", ping)
http.ListenAndServe(":9999", nil)
}
認証が有効なAPISIXルートを作成する
このセキュアなping HTTPサーバーとjwt-auth
認証プラグインが有効なAPISIXルートを作成します。
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"plugins": {
"jwt-auth": {}
},
"upstream": {
"nodes": {
"127.0.0.1:9999": 1
},
"type": "roundrobin"
},
"uri": "/secure/ping"
}'
jwt-authプラグインからトークンを生成する
次に、APISIXからJWTシークレットに署名し、APISIXサーバーの[http://localhost:9080/secure/ping](http://localhost:9080/secure/ping)
プロキシルートにリクエストを行うために使用できるトークンを生成します。
$ curl http://127.0.0.1:9080/apisix/plugin/jwt/sign\?key\=test-key -i
HTTP/1.1 200 OK
Date: Tue, 18 Jan 2022 07:50:57 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.11.0
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODY1N30.nkyev1_KUapVgY_QVYETsSApA6gEkDWS8tsHFV1EpD8
前のステップでfailed to sign jwt
のようなメッセージが表示された場合は、Vaultのkv/apisix/consumers/jack/jwt-auth
パスにシークレットキーが保存されていることを確認してください。
# 例
$ vault kv put kv/apisix/consumer/jack/jwt-auth secret=$ecr3t-c0d3
Success! Data written to: kv/apisix/consumer/jack/jwt-auth
APISIXサーバーにリクエストする
次に、APISIXプロキシの/secure/ping
ルートにリクエストを行います。検証が成功すると、リクエストはGo HTTPサーバーに転送されます。
$ curl http://127.0.0.1:9080/secure/ping -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODU5M30.IYudBr7FTgRme70u4rEBoYNtGmGByzgfGlt8hctI__Q' -i
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Connection: keep-alive
Date: Tue, 18 Jan 2022 08:00:04 GMT
Server: APISIX/2.11.0
secure/pong
有効なJWTがないリクエストは、HTTP 401 Unauthorized
エラーをスローします。
$ curl http://127.0.0.1:9080/secure/ping -i
HTTP/1.1 401 Unauthorized
Date: Tue, 18 Jan 2022 08:00:33 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.11.0
{"message":"Missing JWT token in request"}
VaultをAPISIX jwt-authプラグインと統合できるさまざまなユースケース
Apache APISIXのjwt-auth
プラグインは、Vaultストレージから単純なテキストシークレットキーやRS256公開鍵と秘密鍵のペアを取得するように設定できます。
:::note この統合サポートの初期バージョンでは、プラグインはVaultパスに保存されたシークレットのキー名が[ secret
、public_key
、private_key
]のいずれかであることを期待しています。将来のリリースでは、カスタム名のキーを参照するサポートを追加する予定です。 :::
-
Vault内にHS256署名シークレットを保存し、JWTの署名と検証に使用したい場合。
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jack", "plugins": { "jwt-auth": { "key": "key-1", "vault": {} } } }'
ここで、プラグインはVaultパス(
<conf.yamlのvault.prefix>/consumer/jack/jwt-auth
)内のキーsecret
を検索し、コンシューマーjack
の設定に記載されているシークレットを使用して、後続の署名とJWT検証を行います。同じパスにキーが見つからない場合、プラグインはエラーをログに記録し、JWT認証に失敗します。 -
RS256 RSAキーペアの公開鍵と秘密鍵の両方がVaultに保存されている場合。
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jim", "plugins": { "jwt-auth": { "key": "rsa-keypair", "algorithm": "RS256", "vault": {} } } }'
プラグインは、Vaultのkvパス(
<conf.yamlのvault.prefix>/consumer/jim/jwt-auth
)内のpublic_key
とprivate_key
キーを検索します。見つからない場合、認証は失敗します。Vaultのkvストレージに公開鍵と秘密鍵を保存する方法がわからない場合は、次のコマンドを使用してください。
# 現在のディレクトリに"public.pem"と"private.pem"という名前のファイルが含まれている場合 $ vault kv put kv/apisix/consumer/jim/jwt-auth public_key=@public.pem private_key=@private.pem Success! Data written to: kv/apisix/consumer/jim/jwt-auth
-
コンシューマー設定に公開鍵があり、秘密鍵がVaultにある場合。
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "john", "plugins": { "jwt-auth": { "key": "user-key", "algorithm": "RS256", "public_key": "-----BEGIN PUBLIC KEY-----\n……\n-----END PUBLIC KEY-----" "vault": {} } } }'
このプラグインは、コンシューマー設定からRSA公開鍵を使用し、Vaultから直接取得した秘密鍵を使用します。
プラグインからVaultを無効にする
jwt-auth
プラグインからVaultの検索を無効にするには、コンシューマープラグイン設定(この場合はjack
)から空のVaultオブジェクトを削除します。これにより、JWTプラグインは、jwt-auth
設定が有効になっているURIルートへの後続のリクエストに対して、プラグイン設定内の署名シークレット(HS256/HS512またはRS512キーペア)を検索します。APISIXのconfig.yaml
でVault設定が有効になっていても、Vaultサーバーにリクエストは送信されません。
APISIXプラグインはホットリロードされるため、APISIXを再起動する必要はありません。
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "test-key",
"secret": "my-secret-key"
}
}
}'
まとめ
この記事では、VaultとApache APISIXの統合に関する今後のリリースと関連する詳細を紹介しました。
GitHub Discussionsでディスカッションを開始したり、メーリングリストを通じてコミュニケーションを取ることを自由に試してください。