🔑

ロードバランサー経由の Vault クライアントのアクセス制御

2024/08/02に公開

Vaultを本番環境で利用する場合、リファレンスアーキテクチャーに沿った構成にして頂く事が推奨されています。
https://developer.hashicorp.com/validated-designs/vault-solution-design-guides-vault-enterprise/architecture#cluster-architecture-summary

その場合、VaultクラスタのフロントエンドにL4のロードバランサーを置いて頂いて、Vaultクライアントからのリクエストをロードバランサー経由でVaultクラスタを構成する各ノードに振り分けて頂く構成にするのが一般的です。

ただ、ロードバランサーを経由したアクセスの場合、Vaultの設定を正しく行わないと、ロードバランサーからのリクエストとしてVaultに処理されてしまうため、実際のクライアントからのリクエストを正しく制御出来ない場合があります。

その部分をうまく制御するパラメータがVaultの設定ファイルのtcplistenerスタンザで利用可能であるため、検証してみました。

Configuration

検証で利用したClient -> LoadBalancer -> Vaultの環境は、以下の様な構成にしています。

Client-LB-Vault Enterprise検証構成

Hostname IP address
client 10.0.101.5
traefik (vault.lab.demo) 10.0.1.70
vault1 10.0.1.10
vault2 10.0.2.10
vault3 10.0.3.10

clientからtraefik, 3台のvaultサーバに対しては、SSHでアクセスできる様にしています。一方、clientからは3台のvaultサーバに対して、8200/tcpで直接VaultのAPIエンドポイントにはアクセス出来ない様にしています。

また、traefikから3台のvaultサーバには8200/tcpでVaultのAPIエンドポイントにアクセスできる様にしており、clientからhttps://vault.lab.demo:8200で来たリクエストをバックエンドのVaultサーバにロードバランスできる様にしています。

Vault configuration

vault1のVaultの構成ファイルは以下の様に定義しています。

vault1のconfig.hcl
api_addr     = "https://10.0.1.10:8200"
cluster_addr = "https://10.0.1.10:8201"

ui = true

license_path = "/etc/vault.d/vault.hclic"

listener "tcp" {
  address            = "0.0.0.0:8200"
  cluster_address    = "0.0.0.0:8201"
  tls_client_ca_file = "/etc/vault.d/ca.pem"
  tls_cert_file      = "/etc/vault.d/vault-cert.pem"
  tls_key_file       = "/etc/vault.d/vault-private-key.pem"
  x_forwarded_for_authorized_addrs = ["0.0.0.0/0"]
  telemetry {
    unauthenticated_metrics_access = "true"
  }
}

storage "raft" {
  path    = "/var/lib/vault"
  node_id = "vault1"

  retry_join {
    leader_api_addr         = "https://10.0.1.10:8200"
    leader_ca_cert_file     = "/etc/vault.d/ca.pem"
    leader_client_cert_file = "/etc/vault.d/vault-cert.pem"
    leader_client_key_file  = "/etc/vault.d/vault-private-key.pem"
    leader_tls_servername   = "vault.lab.demo"
  }

  retry_join {
    leader_api_addr         = "https://10.0.2.10:8200"
    leader_ca_cert_file     = "/etc/vault.d/ca.pem"
    leader_client_cert_file = "/etc/vault.d/vault-cert.pem"
    leader_client_key_file  = "/etc/vault.d/vault-private-key.pem"
    leader_tls_servername   = "vault.lab.demo"
  }

  retry_join {
    leader_api_addr         = "https://10.0.3.10:8200"
    leader_ca_cert_file     = "/etc/vault.d/ca.pem"
    leader_client_cert_file = "/etc/vault.d/vault-cert.pem"
    leader_client_key_file  = "/etc/vault.d/vault-private-key.pem"
    leader_tls_servername   = "vault.lab.demo"
  }
}

telemetry {
  disable_hostname          = true
  prometheus_retention_time = "12h"
}

今回の構成の場合、x_forwarded_for_authorized_addrsが、Vaultクライアント側のIPアドレスを利用し、アクセス制御を行う上で必要なパラメータになります。

x_forwarded_for_authorized_addrsX-Forwarded-Forヘッダーが信頼される送信元IP CIDRのリストを指定します。上の設定では0.0.0.0/0としていますが、本来ロードバランサーのIP CIDR等適切な値を指定します。カンマ区切りのリスト、またはJSON配列で指定する事が出来ます。

x_forwarded_for_authorized_addrsを指定する事で、指定したVaultのtcplistenerでX-Forwarded-Forのサポートが有効になります。この設定を入れる事で、Vaultの監査ログのremote_addressフィールドに、ロードバランサー経由でアクセスしてきたVaultクライアントのIPアドレスが書き込まれ、Vaultクライアントが持つIPアドレスレンジを用いたアクセス制御が正しく行える様になります。

vault2vault3も同じ様なVault設定ファイルを作成し、Vaultクラスタを構成しています。

3台構成のVaultクラスタ
$ vault operator raft list-peers
Node      Address           State       Voter
----      -------           -----       -----
vault1    10.0.1.10:8201    leader      true
vault2    10.0.2.10:8201    follower    true
vault3    10.0.3.10:8201    follower    true

デフォルトだと監査ログの設定がされていないため、leaderであるvault1に対して、以下のコマンドを実行し、監査ログを有効にします。

vault audit enable syslog tag="vault" facility="AUTH"

この設定を行う事で、この環境においては、/var/log/syslog/var/log/auth.logにVaultのログが出力される様になります。
クライアントからのAPIリクエストに関するログは、/var/log/auth.logに出力されます。

grep -o '{.*}' /var/log/auth.log > json_auth_log.txt
cat json_auth_log.txt | jq -r .
Vaultの監査ログの出力例
{
  "auth": {
    "policy_results": {
      "allowed": true
    },
    "token_type": "default"
  },
  "request": {
    "data": {
      "role_id": "hmac-sha256:567cdfc1f10ec09004c6982876c9c97e4eb950a40e9833bf3ca132d4758d6ec4",
      "secret_id": "hmac-sha256:61ad43bc793b2e25371f5e29e55cfa86421e5c235f7336d0f1d12f69d43bd5fa"
    },
    "id": "127f0c69-0af1-1a1e-d844-fd3be055b90a",
    "mount_accessor": "auth_approle_2430c318",
    "mount_class": "auth",
    "mount_point": "auth/test/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "approle",
    "namespace": {
      "id": "root"
    },
    "operation": "update",
    "path": "auth/test/login",
    "remote_address": "10.0.1.70",
    "remote_port": 53934
  },
  "time": "2024-08-01T00:50:27.007693637Z",
  "type": "request"
}

Vault側の事前準備は以上です。

ロードバランサーが接続クライアントのIPアドレスをX-Forwarded-Forヘッダーに送る必要がありますので、ロードバランサー側の設定も見てみます。

Traefik configuration

ロードバランサーとしてTraefikを利用しています。OSレベルでは今回利用する自己署名証明書を信頼する設定を入れていますが、コンテナレベルでは設定をしていないため、dynamic.tomlファイルでinsecureSkipVerify = trueの設定をしています。

traefik.toml
[entryPoints]
  [entryPoints.http]
    address = ":80"
  [entryPoints.https]
    address = ":443"
  [entryPoints.vault]
    address = ":8200"

[log]
  level = "INFO"

[api]
  dashboard = true
  insecure = true

[accessLog]
  filePath = "/var/log/traefik/access.log"
  format = "json"

[providers.file]
  filename = "/etc/traefik/dynamic.toml"

[entryPoints.vault.forwardedHeaders]
  trustedIPs = ["0.0.0.0/0"]
dynamic.toml
http.services]
  [http.services.vault.loadBalancer]
    passHostHeader = true
    serversTransport = "insecureTransport"
    [[http.services.vault.loadBalancer.servers]]
      url = "https://10.0.1.10:8200"
    [[http.services.vault.loadBalancer.servers]]
      url = "https://10.0.2.10:8200"
    [[http.services.vault.loadBalancer.servers]]
      url = "https://10.0.3.10:8200"

[http.routers]
  [http.routers.vault]
    rule = "Host(`vault.lab.demo`)"
    entryPoints = ["vault"]
    service = "vault"
    [http.routers.vault.tls]

[tls]
  [tls.stores]
    [tls.stores.default]
      [tls.stores.default.defaultCertificate]
    certFile = "/cert/vault-cert.pem"
    keyFile = "/cert/vault-private-key.pem"

[http.serversTransports]
  [http.serversTransports.insecureTransport]
    insecureSkipVerify = true

これら設定ファイルを利用して、DockerコンテナでTraefikをtraefikサーバで動かしています。

docker run -d \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /home/ubuntu/lb/traefik.toml:/etc/traefik/traefik.toml \
  -v /home/ubuntu/lb/dynamic.toml:/etc/traefik/dynamic.toml \
  -v /home/ubuntu/certs/:/cert/ \
  -v /var/log/traefik:/var/log/traefik \
  -p 80:80 \
  -p 443:443 \
  -p 8080:8080 \
  -p 8200:8200 \
  --restart always \
  --name traefik \
  traefik:v2.5
Vaultクラスタへのロードバランスのためにポート8200をLISTEN
$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                                                                                                                                                            NAMES
ecfbe5c6a31f   traefik:v2.5   "/entrypoint.sh trae…"   3 seconds ago   Up 2 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:8200->8200/tcp, :::8200->8200/tcp   traefik

Test

テストを行う準備が整ったので、clientからtraefik経由でVaultクラスタにリクエストを送ってみます。

export VAULT_ADDR=https://vault.lab.demo:8200

Vaultのステータスを確認するエンドポイントにリクエストをしてみます。

$ vault status
Key                     Value
---                     -----
Seal Type               shamir
Initialized             true
Sealed                  false
Total Shares            1
Threshold               1
Version                 1.17.2+ent
Build Date              2024-07-05T15:19:27Z
Storage Type            raft
Cluster Name            vault-cluster-2d146662
Cluster ID              7570c5d1-3f52-08b6-f52a-3aaaecee55ef
HA Enabled              true
HA Cluster              https://10.0.1.10:8201
HA Mode                 active
Active Since            2024-07-31T09:44:49.315910551Z
Raft Committed Index    379878
Raft Applied Index      379878
Last WAL                145460

続いて、クライアントトークンが必要なリクエストを送ってみます。clientノードには有効なクライアントトークンがVAULT_TOKENに設定されていないため、リクエストは拒否されます。

権限を持ったクライアントトークンが設定されていないリクエストは拒否される
$ vault auth list
Error listing enabled authentications: Error making API request.

URL: GET https://vault.lab.demo:8200/v1/sys/auth
Code: 403. Errors:

* permission denied

Vaultクラスタのノードにログインし、監査ログを確認して、clientのIPアドレスがremote_accessとして記録されている事を確認してみます。

該当箇所の監査ログの抜粋
{
  "auth": {
    "token_type": "default"
  },
  "error": "permission denied",
  "request": {
    "id": "b64462f2-eb99-8c09-6412-6eab0e37f7b5",
    "mount_class": "secret",
    "mount_point": "sys/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "system",
    "namespace": {
      "id": "root"
    },
    "operation": "read",
    "path": "sys/auth",
    "remote_address": "10.0.101.5",
    "remote_port": 58284
  },
  "time": "2024-08-02T02:29:49.710022647Z",
  "type": "request"
}
{
  "auth": {
    "token_type": "default"
  },
  "error": "1 error occurred:\n\t* permission denied\n\n",
  "request": {
    "id": "b64462f2-eb99-8c09-6412-6eab0e37f7b5",
    "mount_class": "secret",
    "mount_point": "sys/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "system",
    "namespace": {
      "id": "root"
    },
    "operation": "read",
    "path": "sys/auth",
    "remote_address": "10.0.101.5",
    "remote_port": 58284
  },
  "response": {
    "data": {
      "error": "hmac-sha256:18e81b291988ad095b6e3672ca8ff5ba7f76c1af2cb22117ad60a7df58d5ab4b"
    },
    "mount_class": "secret",
    "mount_point": "sys/",
    "mount_running_plugin_version": "v1.17.2+builtin.vault",
    "mount_type": "system"
  },
  "time": "2024-08-02T02:29:49.712106752Z",
  "type": "response"
}

監査ログのremote_address10.0.101.5となっている事が分かります。Vaultクラスタへのリクエストはロードバランサー(vault.lab.demo)を経由していますが、その先にいるVaultクライアントのIPアドレスが監査ログにログされている事が分かります。

続いて、AppRole認証メソッドのIPアドレス制限を利用し、正しくアクセス制御出来るか確認してみます。

Configure AppRole auth method

AppRole認証メソッドに関しては、設定したロールに紐づくSecretIDの利用を特定のCIDRと紐付ける事が出来ます。clientが所属するIP CIDRを指定し、正しくコントロール出来るか確認してみます。

テスト用のKVストアとポリシーをVault providerを利用して以下の様に設定しています。

main.tf
provider "vault" {}

resource "vault_mount" "kvv2" {
  path        = "test"
  type        = "kv"
  options     = { version = "2" }
  description = "kv-v2 secrets engine for test"
}

resource "vault_kv_secret_v2" "fruits" {
  mount               = vault_mount.kvv2.path
  name                = "fruits"
  cas                 = 1
  delete_all_versions = true
  data_json = jsonencode(
    {
      member1 = "apple",
      member2 = "banana"
    }
  )
  depends_on = [
    vault_mount.kvv2
  ]
}

resource "vault_kv_secret_v2" "vegetables" {
  mount               = vault_mount.kvv2.path
  name                = "vegetables"
  cas                 = 1
  delete_all_versions = true
  data_json = jsonencode(
    {
      member1 = "asparagus",
      member2 = "broccoli",
      member3 = "cabbage"
    }
  )
  depends_on = [
    vault_mount.kvv2
  ]
}
policy.tf
resource "vault_policy" "read_fruits" {
  name = "read-fruits"

  policy = <<EOT
path "test/data/fruits" {
  capabilities = ["read"]
}
EOT
}

resource "vault_policy" "all_vegetables" {
  name = "all-vegetables"

  policy = <<EOT
path "test/data/vegetables" {
  capabilities = ["sudo","read","create","update","delete","list","patch"]
}
EOT
}

また、AppRole認証メソッドに関しては、上記KVストアとポリシー設定とはステートファイルを分けた形で以下の様に設定しています。secret_id_bound_cidrstoken_bound_cidrsパラメーターでclientサーバにアサインされているIPレンジを指定しています。

main.tf
provider "vault" {}

variable "approle_path" {
  description = "path name for approle auth method"
  default     = "test"
}

resource "vault_auth_backend" "approle" {
  path        = var.approle_path
  type        = "approle"
  description = "approle auth method"
}

resource "vault_approle_auth_backend_role" "r1" {
  backend               = vault_auth_backend.approle.path
  role_name             = "tokyo"
  secret_id_bound_cidrs = ["10.0.101.0/24"]
  secret_id_num_uses    = 5
  secret_id_ttl         = 300
  token_policies        = ["default", "read-fruits"]
  token_ttl             = 300
  token_max_ttl         = 600
  token_bound_cidrs     = ["10.0.101.0/24"]
}

data "vault_approle_auth_backend_role_id" "r1" {
  backend   = vault_auth_backend.approle.path
  role_name = vault_approle_auth_backend_role.r1.role_name
}

output "tokyo_roleid" {
  description = "The RoleID of the role: tokyo"
  value       = data.vault_approle_auth_backend_role_id.r1.role_id
}

それぞれ、Terraformのワークフローに則って、設定をVaultクラスタに反映させます。

反映させたら、AppRole認証メソッドのロールtokyoの設定内容を確認します。

$ vault read auth/test/role/tokyo
Key                        Value
---                        -----
bind_secret_id             true
local_secret_ids           false
secret_id_bound_cidrs      [10.0.101.0/24]
secret_id_num_uses         5
secret_id_ttl              5m
token_bound_cidrs          [10.0.101.0/24]
token_explicit_max_ttl     0s
token_max_ttl              10m
token_no_default_policy    false
token_num_uses             0
token_period               0s
token_policies             [default read-fruits]
token_ttl                  5m
token_type                 default

次に、生成したtokyoロールのRoleIDをファイルに出力しておき、RoleIDを確認します。

terraform output -json tokyo_roleid | jq -r . > role-id/tokyo
$ cat role-id/tokyo
0aa31721-e697-e9eb-960e-4e7d407a70c9

Vaultクラスタが初期化される際に出力される初期ルートトークンを利用して、tokyoロールのSecretIDを動的に生成します。

$ vault write -f auth/test/role/tokyo/secret-id
Key                   Value
---                   -----
secret_id             9bca8775-cfbe-40d4-cdce-8fde5ef7c359
secret_id_accessor    3b4c7b85-940c-0d4e-f96a-2f00f22fbbc8
secret_id_num_uses    5
secret_id_ttl         5m

AppRole認証メソッドを利用して、Vaultにログインするための情報が揃ったので、tokyoロールに設定したIP CIDRの制約が上手く機能するか確認していきます。

Test with AppRole auth method

clientサーバから、RoleIDとSecretIDを利用して、Vaultにログインし、問題なくオペレーション出来るか確認していきます。先ほどvault1サーバで生成しておいた、RoleIDとSecretIDを環境変数に設定します。

export ROLE_ID_T="0aa31721-e697-e9eb-960e-4e7d407a70c9"
export SECRET_ID_T="9bca8775-cfbe-40d4-cdce-8fde5ef7c359"

AppRole認証メソッドtokyoロールを利用して、Vaultへログインします。

$ vault write auth/test/login role_id=$ROLE_ID_T secret_id=$SECRET_ID_T
Key                     Value
---                     -----
token                   hvs.CAESIBwCiqiwCfjvHQ0WniyKwOiO6pnHgpstTIWTJsQRe_5XGiIKHGh2cy5rRkRjRk54bFlZTEg5YkVVcEtWWEVmQzgQuJ0J
token_accessor          ZBkOpqaKQfbMNwEL2tOJgyz0
token_duration          5m
token_renewable         true
token_policies          ["default" "read-fruits"]
identity_policies       []
policies                ["default" "read-fruits"]
token_meta_role_name    tokyo

無事にログインする事が出来ました。続いて、生成されたクライアントトークンを利用し、Vaultに対してオペレーションを行います。

export VAULT_TOKEN=hvs.CAESIBwCiqiwCfjvHQ0WniyKwOiO6pnHgpstTIWTJsQRe_5XGiIKHGh2cy5rRkRjRk54bFlZTEg5YkVVcEtWWEVmQzgQuJ0J

クライアントトークンに付与されたポリシーread-fruitsで定義されている通り、KVストアtest/fruitsに対するRead処理は成功し、test/vegetablesに対しては権限がないため、リクエストが通らない事が確認出来ます。

$ vault kv get test/fruits
== Secret Path ==
test/data/fruits

======= Metadata =======
Key                Value
---                -----
created_time       2024-08-01T00:44:52.659101222Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

===== Data =====
Key        Value
---        -----
member1    apple
member2    banana
$ vault kv get test/vegetables
Error reading test/data/vegetables: Error making API request.

URL: GET https://vault.lab.demo:8200/v1/test/data/vegetables
Code: 403. Errors:

* 1 error occurred:
	* permission denied

次に、traefikサーバに移動して、同様にVaultに対してオペレーションを実施してみます。

Vaultサーバへリクエストが通る事を確認しておきます。

export VAULT_ADDR=https://10.0.1.10:8200
$ vault status
Key                     Value
---                     -----
Seal Type               shamir
Initialized             true
Sealed                  false
Total Shares            1
Threshold               1
Version                 1.17.2+ent
Build Date              2024-07-05T15:19:27Z
Storage Type            raft
Cluster Name            vault-cluster-2d146662
Cluster ID              7570c5d1-3f52-08b6-f52a-3aaaecee55ef
HA Enabled              true
HA Cluster              https://10.0.1.10:8201
HA Mode                 active
Active Since            2024-07-31T09:44:49.315910551Z
Raft Committed Index    395080
Raft Applied Index      395080
Last WAL                151280

clientサーバで実施したのと同様に、RoleIDとSecretIDを環境変数に設定します。

export ROLE_ID_T="0aa31721-e697-e9eb-960e-4e7d407a70c9"
export SECRET_ID_T="9bca8775-cfbe-40d4-cdce-8fde5ef7c359"

tokyoロールでVaultへログインを試みてみます。

$ vault write auth/test/login role_id=$ROLE_ID_T secret_id=$SECRET_ID_T
Error writing data to auth/test/login: Error making API request.

URL: PUT https://10.0.1.10:8200/v1/auth/test/login
Code: 400. Errors:

* source address "10.0.1.70" unauthorized by CIDR restrictions on the role: %!w(<nil>)

認可されていないCIDR(traefikサーバのIPアドレスは、10.0.1.70)からのログインリクエストと言う事で、tokyoロールを用いたVaultへのログインが失敗します。

clientサーバ、traefikサーバでのVaultクラスタへのログイン時の挙動から、tokyoロールで設定したsecret_id_bound_cidrsが効いている事が分かります。

続いて、clientサーバで生成したクライアントトークン使って、traefikサーバからオペレーションが出来るか確認してみます。

export VAULT_TOKEN=hvs.CAESIBwCiqiwCfjvHQ0WniyKwOiO6pnHgpstTIWTJsQRe_5XGiIKHGh2cy5rRkRjRk54bFlZTEg5YkVVcEtWWEVmQzgQuJ0J
$ vault kv get test/fruits
Error making API request.

URL: GET https://10.0.1.10:8200/v1/sys/internal/ui/mounts/test/fruits
Code: 403. Errors:

* permission denied

VAULT_TOKENに設定したクライアントトークンには、KVストアtest/fruitsに対するRead処理を行う権限が付与されていますが、クライアントトークン利用時の制約であるtoken_bound_cidrsが効いて、traefikサーバからは利用出来ない事が分かります。

Check vault audit log

それぞれのサーバからの確認が出来たので、vaultサーバで監査ログを確認してみます。clientサーバに関しては以下の通りです。remote_addressとして、"remote_address": "10.0.101.5"とログにも記録され、設定通りに動いている事が監査ログからも読み取れます。

clientからログイン時の監査ログ
{
  "auth": {
    "token_type": "default"
  },
  "forwarded_from": "10.0.3.10:8200",
  "request": {
    "data": {
      "role_id": "hmac-sha256:567cdfc1f10ec09004c6982876c9c97e4eb950a40e9833bf3ca132d4758d6ec4",
      "secret_id": "hmac-sha256:b6e0343cc897a30bec1abf0abeb00e320c58312d2ce583ca7193a622dc10fe00"
    },
    "id": "e8917848-53db-b2d7-4007-9370f214905a",
    "mount_accessor": "auth_approle_2430c318",
    "mount_point": "auth/test/",
    "mount_type": "approle",
    "namespace": {
      "id": "root"
    },
    "operation": "update",
    "path": "auth/test/login",
    "remote_address": "10.0.101.5",
    "remote_port": 35278,
    "replication_cluster": "855a7ea2-2367-2449-345c-a96e8fcd0bdb"
  },
  "time": "2024-08-02T04:06:36.273137645Z",
  "type": "request"
}
{
  "auth": {
    "token_type": "default"
  },
  "forwarded": true,
  "request": {
    "data": {
      "role_id": "hmac-sha256:567cdfc1f10ec09004c6982876c9c97e4eb950a40e9833bf3ca132d4758d6ec4",
      "secret_id": "hmac-sha256:b6e0343cc897a30bec1abf0abeb00e320c58312d2ce583ca7193a622dc10fe00"
    },
    "id": "e8917848-53db-b2d7-4007-9370f214905a",
    "mount_accessor": "auth_approle_2430c318",
    "mount_class": "auth",
    "mount_point": "auth/test/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "approle",
    "namespace": {
      "id": "root"
    },
    "operation": "update",
    "path": "auth/test/login",
    "remote_address": "10.0.101.5",
    "remote_port": 35278,
    "replication_cluster": "855a7ea2-2367-2449-345c-a96e8fcd0bdb"
  },
  "response": {
    "auth": {
      "metadata": {
        "role_name": "tokyo"
      },
      "policies": [
        "default",
        "read-fruits"
      ],
      "token_ttl": 300,
      "token_type": "service"
    },
    "mount_accessor": "auth_approle_2430c318",
    "mount_class": "auth",
    "mount_point": "auth/test/",
    "mount_running_plugin_version": "v1.17.2+builtin.vault",
    "mount_type": "approle"
  },
  "time": "2024-08-02T04:06:36.282362164Z",
  "type": "response"
}
clientからtest/fruitsへreadリクエスト時の監査ログ
{
  "auth": {
    "accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "client_token": "hmac-sha256:8647a21b17af1104f4be9c16abf5762f4f23845841f73fa956ed50a18a4bb25d",
    "display_name": "test",
    "entity_id": "6ffffcd0-6720-9bf4-1709-a8b8facfd111",
    "metadata": {
      "role_name": "tokyo"
    },
    "policies": [
      "default",
      "read-fruits"
    ],
    "policy_results": {
      "allowed": true
    },
    "token_policies": [
      "default",
      "read-fruits"
    ],
    "token_issue_time": "2024-08-02T04:06:36Z",
    "token_ttl": 300,
    "token_type": "service"
  },
  "request": {
    "client_id": "6ffffcd0-6720-9bf4-1709-a8b8facfd111",
    "client_token": "hmac-sha256:01c08ece7fbde3a76019f2c67fa79a93e28c7b782b28626a0467ca76609f4452",
    "client_token_accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "id": "896b92c8-0aa8-4db2-81ab-886afa5d1a5b",
    "mount_class": "secret",
    "mount_point": "sys/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "system",
    "namespace": {
      "id": "root"
    },
    "operation": "read",
    "path": "sys/internal/ui/mounts/test/fruits",
    "remote_address": "10.0.101.5",
    "remote_port": 33702
  },
  "time": "2024-08-02T04:06:55.449515847Z",
  "type": "request"
}
{
  "auth": {
    "accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "client_token": "hmac-sha256:8647a21b17af1104f4be9c16abf5762f4f23845841f73fa956ed50a18a4bb25d",
    "display_name": "test",
    "entity_id": "6ffffcd0-6720-9bf4-1709-a8b8facfd111",
    "metadata": {
      "role_name": "tokyo"
    },
    "policies": [
      "default",
      "read-fruits"
    ],
    "policy_results": {
      "allowed": true
    },
    "token_policies": [
      "default",
      "read-fruits"
    ],
    "token_issue_time": "2024-08-02T04:06:36Z",
    "token_ttl": 300,
    "token_type": "service"
  },
  "request": {
    "client_id": "6ffffcd0-6720-9bf4-1709-a8b8facfd111",
    "client_token": "hmac-sha256:01c08ece7fbde3a76019f2c67fa79a93e28c7b782b28626a0467ca76609f4452",
    "client_token_accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "id": "896b92c8-0aa8-4db2-81ab-886afa5d1a5b",
    "mount_accessor": "system_dcfeebaa",
    "mount_class": "secret",
    "mount_point": "sys/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "system",
    "namespace": {
      "id": "root"
    },
    "operation": "read",
    "path": "sys/internal/ui/mounts/test/fruits",
    "remote_address": "10.0.101.5",
    "remote_port": 33702
  },
  "response": {
    "data": {
      "accessor": "hmac-sha256:16f4feca2fc475e4467ae9579e8ee5641a6cb844294e21c8ade9bb42bc40c87c",
      "config": {
        "default_lease_ttl": 2764800,
        "force_no_cache": false,
        "max_lease_ttl": 2764800
      },
      "deprecation_status": "hmac-sha256:88bf0ee43f76a41a1be75710639a2ce879f2ac594d39e52ea90e7aaf22e77a5e",
      "description": "hmac-sha256:2e12fc4df650fd7175f0f09c6f7c47d5f3c3ad716af024ae49dba00384c31722",
      "external_entropy_access": false,
      "local": false,
      "options": {
        "version": "hmac-sha256:255b6fbcea9b2af09ebc8eb7ae30718462d5f1dbd85025efcf651950a436ae0b"
      },
      "path": "hmac-sha256:2b177b0c4c0a8b1fb69da9a92a9e3e345226665acb22b32530e06a44ea446f53",
      "plugin_version": "hmac-sha256:69154b9e70228ed55173426b2de2522c5bb3c8bb5352a45df3d93cd1b5700b34",
      "running_plugin_version": "hmac-sha256:594008bac65bc3f095ac5c2fd929a78a4e18a213b9d3cfd78e84e7a51ce4a6d9",
      "running_sha256": "hmac-sha256:69154b9e70228ed55173426b2de2522c5bb3c8bb5352a45df3d93cd1b5700b34",
      "seal_wrap": false,
      "type": "hmac-sha256:78ad9f0d136001883f45858260115818ecd7c49477162fc160d02a4a44c9170e",
      "uuid": "hmac-sha256:513cf1b91eb032dfb75854272c4384cf662b46c55fd7a433bd3fb3d8b1926c10"
    },
    "mount_accessor": "system_dcfeebaa",
    "mount_class": "secret",
    "mount_point": "sys/",
    "mount_running_plugin_version": "v1.17.2+builtin.vault",
    "mount_type": "system"
  },
  "time": "2024-08-02T04:06:55.452080207Z",
  "type": "response"
}
clientからtest/vegetablesへreadリクエスト時の監査ログ
{
  "auth": {
    "accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "client_token": "hmac-sha256:8647a21b17af1104f4be9c16abf5762f4f23845841f73fa956ed50a18a4bb25d",
    "display_name": "test",
    "entity_id": "6ffffcd0-6720-9bf4-1709-a8b8facfd111",
    "metadata": {
      "role_name": "tokyo"
    },
    "policies": [
      "default",
      "read-fruits"
    ],
    "policy_results": {
      "allowed": false
    },
    "token_policies": [
      "default",
      "read-fruits"
    ],
    "token_issue_time": "2024-08-02T04:06:36Z",
    "token_ttl": 300,
    "token_type": "service"
  },
  "error": "1 error occurred:\n\t* permission denied\n\n",
  "request": {
    "client_id": "6ffffcd0-6720-9bf4-1709-a8b8facfd111",
    "client_token": "hmac-sha256:01c08ece7fbde3a76019f2c67fa79a93e28c7b782b28626a0467ca76609f4452",
    "client_token_accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "id": "11bcc248-532e-d382-cbde-7fba2222c422",
    "mount_class": "secret",
    "mount_point": "test/",
    "mount_running_version": "v0.19.0+builtin",
    "mount_type": "kv",
    "namespace": {
      "id": "root"
    },
    "operation": "read",
    "path": "test/data/vegetables",
    "remote_address": "10.0.101.5",
    "remote_port": 33702
  },
  "time": "2024-08-02T04:07:00.402109044Z",
  "type": "request"
}
{
  "auth": {
    "accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "client_token": "hmac-sha256:8647a21b17af1104f4be9c16abf5762f4f23845841f73fa956ed50a18a4bb25d",
    "display_name": "test",
    "entity_id": "6ffffcd0-6720-9bf4-1709-a8b8facfd111",
    "metadata": {
      "role_name": "tokyo"
    },
    "policies": [
      "default",
      "read-fruits"
    ],
    "policy_results": {
      "allowed": false
    },
    "token_policies": [
      "default",
      "read-fruits"
    ],
    "token_issue_time": "2024-08-02T04:06:36Z",
    "token_ttl": 300,
    "token_type": "service"
  },
  "error": "1 error occurred:\n\t* permission denied\n\n",
  "request": {
    "client_id": "6ffffcd0-6720-9bf4-1709-a8b8facfd111",
    "client_token": "hmac-sha256:01c08ece7fbde3a76019f2c67fa79a93e28c7b782b28626a0467ca76609f4452",
    "client_token_accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "id": "11bcc248-532e-d382-cbde-7fba2222c422",
    "mount_class": "secret",
    "mount_point": "test/",
    "mount_running_version": "v0.19.0+builtin",
    "mount_type": "kv",
    "namespace": {
      "id": "root"
    },
    "operation": "read",
    "path": "test/data/vegetables",
    "remote_address": "10.0.101.5",
    "remote_port": 33702
  },
  "response": {
    "data": {
      "error": "hmac-sha256:4251ad8d96c56eb038ff67d8a104bd6b83ab7e4744352f280bef4acfa40bae35"
    },
    "mount_class": "secret",
    "mount_point": "test/",
    "mount_running_plugin_version": "v0.19.0+builtin",
    "mount_type": "kv"
  },
  "time": "2024-08-02T04:07:00.402523849Z",
  "type": "response"
}

traefikサーバに関しては以下の通りです。remote_addressとして、"remote_address": "10.0.1.70"とログに記録され、AppRole認証メソッドのtokyoロールに設定したIP CIDRの制約のため、エラーがレスポンスされている事が読み取れます。

traefikからログイン時の監査ログ
{
  "auth": {
    "policy_results": {
      "allowed": true
    },
    "token_type": "default"
  },
  "request": {
    "data": {
      "role_id": "hmac-sha256:567cdfc1f10ec09004c6982876c9c97e4eb950a40e9833bf3ca132d4758d6ec4",
      "secret_id": "hmac-sha256:b6e0343cc897a30bec1abf0abeb00e320c58312d2ce583ca7193a622dc10fe00"
    },
    "id": "1417d98c-8729-cc12-9a63-ea210221c91b",
    "mount_accessor": "auth_approle_2430c318",
    "mount_class": "auth",
    "mount_point": "auth/test/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "approle",
    "namespace": {
      "id": "root"
    },
    "operation": "update",
    "path": "auth/test/login",
    "remote_address": "10.0.1.70",
    "remote_port": 38114
  },
  "time": "2024-08-02T04:07:54.931654443Z",
  "type": "request"
}
{
  "auth": {
    "policy_results": {
      "allowed": true
    },
    "token_type": "default"
  },
  "request": {
    "data": {
      "role_id": "hmac-sha256:567cdfc1f10ec09004c6982876c9c97e4eb950a40e9833bf3ca132d4758d6ec4",
      "secret_id": "hmac-sha256:b6e0343cc897a30bec1abf0abeb00e320c58312d2ce583ca7193a622dc10fe00"
    },
    "id": "1417d98c-8729-cc12-9a63-ea210221c91b",
    "mount_accessor": "auth_approle_2430c318",
    "mount_class": "auth",
    "mount_point": "auth/test/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "approle",
    "namespace": {
      "id": "root"
    },
    "operation": "update",
    "path": "auth/test/login",
    "remote_address": "10.0.1.70",
    "remote_port": 38114
  },
  "response": {
    "data": {
      "error": "hmac-sha256:3487ea3db5b8f4c90438de840c8bd209411f32d421a8f7ad8da83912fd38dba7"
    },
    "mount_accessor": "auth_approle_2430c318",
    "mount_class": "auth",
    "mount_point": "auth/test/",
    "mount_running_plugin_version": "v1.17.2+builtin.vault",
    "mount_type": "approle"
  },
  "time": "2024-08-02T04:07:54.941065121Z",
  "type": "response"
}
traefikからclientで生成したクライアントトークンを用いたオペレーション時の監査ログ
{
  "auth": {
    "accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "client_token": "hmac-sha256:8647a21b17af1104f4be9c16abf5762f4f23845841f73fa956ed50a18a4bb25d",
    "policy_results": {
      "allowed": true
    },
    "token_type": "default"
  },
  "request": {
    "client_token": "hmac-sha256:01c08ece7fbde3a76019f2c67fa79a93e28c7b782b28626a0467ca76609f4452",
    "client_token_accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "id": "47020eec-a386-c9e1-6675-313506bee18c",
    "mount_class": "secret",
    "mount_point": "sys/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "system",
    "namespace": {
      "id": "root"
    },
    "operation": "read",
    "path": "sys/internal/ui/mounts/test/fruits",
    "remote_address": "10.0.1.70",
    "remote_port": 38974
  },
  "time": "2024-08-02T04:08:10.63952107Z",
  "type": "request"
}
{
  "auth": {
    "accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "client_token": "hmac-sha256:8647a21b17af1104f4be9c16abf5762f4f23845841f73fa956ed50a18a4bb25d",
    "policy_results": {
      "allowed": true
    },
    "token_type": "default"
  },
  "error": "permission denied",
  "request": {
    "client_token": "hmac-sha256:01c08ece7fbde3a76019f2c67fa79a93e28c7b782b28626a0467ca76609f4452",
    "client_token_accessor": "hmac-sha256:cb0b4fd5a155dea744491bd9467b0c1a5bd474f3c62836e9625583d402576776",
    "id": "47020eec-a386-c9e1-6675-313506bee18c",
    "mount_accessor": "system_dcfeebaa",
    "mount_class": "secret",
    "mount_point": "sys/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "system",
    "namespace": {
      "id": "root"
    },
    "operation": "read",
    "path": "sys/internal/ui/mounts/test/fruits",
    "remote_address": "10.0.1.70",
    "remote_port": 38974
  },
  "response": {
    "mount_accessor": "system_dcfeebaa",
    "mount_class": "secret",
    "mount_point": "sys/",
    "mount_running_plugin_version": "v1.17.2+builtin.vault",
    "mount_type": "system"
  },
  "time": "2024-08-02T04:08:10.640096347Z",
  "type": "response"
}

この様にロードバランサー越しにVaultクライアントからVaultクラスタにアクセスする場合においても、VaultクライアントのIPアドレスを用いて、アクセス制御を行える事が確認できました。

使えそうな部分があれば、参考にしてみてください!

(Optional) without x_forwarded_for_authorized_addrs setting

以下の様に、x_forwarded_for_authorized_addrsをVaultの設定ファイルが削除した場合に、どのような挙動になるか確認しておきます。

vault1のconfig.hcl
api_addr     = "https://10.0.1.10:8200"
cluster_addr = "https://10.0.1.10:8201"

ui = true

license_path = "/etc/vault.d/vault.hclic"

listener "tcp" {
  address            = "0.0.0.0:8200"
  cluster_address    = "0.0.0.0:8201"
  tls_client_ca_file = "/etc/vault.d/ca.pem"
  tls_cert_file      = "/etc/vault.d/vault-cert.pem"
  tls_key_file       = "/etc/vault.d/vault-private-key.pem"
  telemetry {
    unauthenticated_metrics_access = "true"
  }
}

storage "raft" {
  path    = "/var/lib/vault"
  node_id = "vault1"

  retry_join {
    leader_api_addr         = "https://10.0.1.10:8200"
    leader_ca_cert_file     = "/etc/vault.d/ca.pem"
    leader_client_cert_file = "/etc/vault.d/vault-cert.pem"
    leader_client_key_file  = "/etc/vault.d/vault-private-key.pem"
    leader_tls_servername   = "vault.lab.demo"
  }

  retry_join {
    leader_api_addr         = "https://10.0.2.10:8200"
    leader_ca_cert_file     = "/etc/vault.d/ca.pem"
    leader_client_cert_file = "/etc/vault.d/vault-cert.pem"
    leader_client_key_file  = "/etc/vault.d/vault-private-key.pem"
    leader_tls_servername   = "vault.lab.demo"
  }

  retry_join {
    leader_api_addr         = "https://10.0.3.10:8200"
    leader_ca_cert_file     = "/etc/vault.d/ca.pem"
    leader_client_cert_file = "/etc/vault.d/vault-cert.pem"
    leader_client_key_file  = "/etc/vault.d/vault-private-key.pem"
    leader_tls_servername   = "vault.lab.demo"
  }
}

telemetry {
  disable_hostname          = true
  prometheus_retention_time = "12h"
}

前述のテスト同様、AppRole認証メソッドのtokyoロールに紐づくRoleIDとSecretIDを準備して、それを用いてclientサーバからログインしてみます。

export VAULT_ADDR=https://vault.lab.demo:8200
export ROLE_ID_T=0aa31721-e697-e9eb-960e-4e7d407a70c9
export SECRET_ID_T=dc23d22f-4e18-e170-2405-e333bcef1ebd
$ vault write auth/test/login role_id=$ROLE_ID_T secret_id=$SECRET_ID_T
Error writing data to auth/test/login: Error making API request.

URL: PUT https://vault.lab.demo:8200/v1/auth/test/login
Code: 400. Errors:

* source address "10.0.1.70" unauthorized by CIDR restrictions on the role: %!w(<nil>)

client(10.0.101.5/24)からログインしていますが、ロードバランサーであるtraefik(10.0.1.70/24)からのログインとして、Vaultには認識され、tokyoロールでログインする事が出来なくなっています。

vaultサーバで監査ログを確認してみます。clientサーバからログインを試みていますが、監査ログに記録されている"remote_address":は、ロードバランサーの"10.0.1.70"となっています。

clientからAppRoleでログインしようと際の監査ログ
{
  "auth": {
    "token_type": "default"
  },
  "forwarded_from": "10.0.2.10:8200",
  "request": {
    "data": {
      "role_id": "hmac-sha256:567cdfc1f10ec09004c6982876c9c97e4eb950a40e9833bf3ca132d4758d6ec4",
      "secret_id": "hmac-sha256:0633b0e27b11427df6f4697507db65e2276aca7266174fd65ff03e7c405796fe"
    },
    "id": "bc939b9e-9469-2737-a50f-fa72b7b2d509",
    "mount_accessor": "auth_approle_2430c318",
    "mount_point": "auth/test/",
    "mount_type": "approle",
    "namespace": {
      "id": "root"
    },
    "operation": "update",
    "path": "auth/test/login",
    "remote_address": "10.0.1.70",
    "remote_port": 35066,
    "replication_cluster": "08a0256f-5b7a-e2f1-aa0e-86a181e42af9"
  },
  "time": "2024-08-02T06:45:24.639776869Z",
  "type": "request"
}
{
  "auth": {
    "token_type": "default"
  },
  "forwarded": true,
  "request": {
    "data": {
      "role_id": "hmac-sha256:567cdfc1f10ec09004c6982876c9c97e4eb950a40e9833bf3ca132d4758d6ec4",
      "secret_id": "hmac-sha256:0633b0e27b11427df6f4697507db65e2276aca7266174fd65ff03e7c405796fe"
    },
    "id": "bc939b9e-9469-2737-a50f-fa72b7b2d509",
    "mount_accessor": "auth_approle_2430c318",
    "mount_class": "auth",
    "mount_point": "auth/test/",
    "mount_running_version": "v1.17.2+builtin.vault",
    "mount_type": "approle",
    "namespace": {
      "id": "root"
    },
    "operation": "update",
    "path": "auth/test/login",
    "remote_address": "10.0.1.70",
    "remote_port": 35066,
    "replication_cluster": "08a0256f-5b7a-e2f1-aa0e-86a181e42af9"
  },
  "response": {
    "data": {
      "error": "hmac-sha256:3487ea3db5b8f4c90438de840c8bd209411f32d421a8f7ad8da83912fd38dba7"
    },
    "mount_accessor": "auth_approle_2430c318",
    "mount_class": "auth",
    "mount_point": "auth/test/",
    "mount_running_plugin_version": "v1.17.2+builtin.vault",
    "mount_type": "approle"
  },
  "time": "2024-08-02T06:45:24.64938285Z",
  "type": "response"
}

ロードバランサーを経由して、Vaultクラスタにアクセスする場合、VaultクライアントのIPアドレスを利用して、アクセス制御等を行う場合は、x_forwarded_for_authorized_addrs設定が必要となる事が分かります。

References

Discussion