Featured image of post Prometheusにクライアント認証をかける

Prometheusにクライアント認証をかける

特定の相手だけがメトリクスを見られるようにしたい

やったことのまとめ

Prometheusクライアント認証をかけてみた.

Prometheus単独でクライアント認証を行う方法と,
クライアント認証をかけたnginxでリバースプロキシする方法をそれぞれ試した.

Prometheus, nginxはすべてDocker Desktop for Macで動かした.

つかうもの

やったこと

各種秘密鍵と証明書の作成

まずはクライアント認証に必要なサーバーとクライアントの秘密鍵+証明書をOpenSSLで作成する.

# nginxコンテナを起動してOpenSSLを使う
$ docker container run --rm -it -v="$PWD:/workdir" -w="/workdir" --entrypoint=/bin/bash nginx:1.21.0

# サーバー用のオレオレ証明書を作成
# ホスト名はprometheus.hogehoge.comとしてSANsの設定もしておく(Chromeで開くため)
$ openssl genpkey -algorithm RSA -out server-private-key.pem
$ openssl req -x509 -key server-private-key.pem -out server-cert.pem -addext 'subjectAltName = DNS:prometheus.hogehoge.com'
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:prometheus.hogehoge.com
Email Address []:

# クライアント認証局の証明書を作成(オレオレ認証局)
$ openssl genpkey -algorithm RSA -out client-ca-private-key.pem
$ openssl req -x509 -key client-ca-private-key.pem -out client-ca-cert.pem -days 365
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:client-ca
Email Address []:

# 署名のための準備
$ mkdir -p demoCA/newcerts && touch demoCA/index.txt
$ echo 01 > ./demoCA/serial

# クライアント証明書を作成(クライアント認証局で署名する)
# PKCS#12形式のものも作成しておく
$ openssl genpkey -algorithm RSA -out client-private-key.pem
$ openssl req -new -key client-private-key.pem -out client-csr.pem
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:client
Email Address []:
A challenge password []:
An optional company name []:

$ openssl ca -in client-csr.pem -out client-cert.pem -keyfile client-ca-private-key.pem -cert client-ca-cert.pem -days 365
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y

$ openssl pkcs12 -export -clcerts -in client-cert.pem -inkey client-private-key.pem -out client-cert.p12
Enter Export Password: fugafuga
Verifying - Enter Export Password: fugafuga

# コンテナから出ると鍵と証明書が作成されている
$ exit
$ ls -1
client-ca-cert.pem
client-ca-private-key.pem
client-cert.p12
client-cert.pem
client-csr.pem
client-private-key.pem
demoCA
server-cert.pem
server-private-key.pem

今回は鍵と証明書をたくさん使っていて後で混乱しそうなのでここに内容をまとめておく.

  • サーバー認証局の証明書(server-cert.pem)
    • サーバー証明書に署名した認証局の証明書
    • 今回はオレオレ証明書なのでサーバー証明書と同一だが, ちゃんとした認証局(CA)によってサーバー証明書を発行した場合は認証局の証明書を使う.
  • サーバー認証局の秘密鍵(server-private-key.pem)
    • サーバー証明書に署名した認証局の秘密鍵
    • 今回はオレオレ証明書なのでサーバー秘密鍵と同一
    • このあとは使わない
  • サーバー証明書(server-cert.pem)
    • サーバー(Prometheus)の身元を証明する証明書. 今回はオレオレ証明書を使う.
    • ホスト名はprometheus.hogehoge.comとした(オレオレ証明書なので任意の名前)
  • サーバー秘密鍵(server-private-key.pem)
    • サーバー(Prometheus)に持たせる秘密鍵
  • クライアント認証局の証明書(client-ca-cert.pem)
    • クライアント証明書に署名した認証局の証明書
  • クライアント認証局の秘密鍵(client-ca-private-key.pem)
    • クライアント証明書に署名した認証局の秘密鍵
    • このあとは使わない
  • クライアント証明書(client-cert.pem, client-cert.p12)
    • クライアントの身元を証明する証明書
    • クライアント(Prometheus, Grafana, User)ごとに分けてもいいけど, 面倒なので全部同じものを使う
  • クライアント秘密鍵(client-private-key.pem)
    • クライアントに持たせる秘密鍵
  • クライアントのCSR(client-csr.pem)
    • クライアント証明書を発行するときに使ったCSR
    • このあとは使わない

ついでにブラウザでも動作確認できるように, Macでサーバー証明書を信頼する設定とクライアント証明書を持たせる設定を行う.

Finderからclient-cert.p12server-cert.pemをそれぞれダブルクリックして,
証明書をキーチェーンアクセスで信頼するように設定する.

最後に, 今回はサーバー証明書で実在しないドメイン(prometheus.hogehoge.com)を指定しているので,
Macの/etc/hostsに名前解決の設定を記述しておく.

# /etc/hostsにprometheus.hogehoge.com(localhostに飛ばす)を追加
$ echo "127.0.0.1 prometheus.hogehoge.com" | sudo tee -a /etc/hosts

以上で証明書とか秘密鍵の準備は完了.

Prometheus単独でクライアント認証をかける

Prometheus自体にクライアント認証の仕組みがあるので1,
サーバー証明書サーバー秘密鍵(HTTPS化してサーバーの身元を証明するのに使用),
クライアント認証局の証明書(クライアントの身元を確認するのに使用)を持たせる.

クライアント認証の設定はweb-config.ymlに記述する.

tls_server_config:
# PrometheusをHTTPS化する
cert_file: "server-cert.pem" # サーバー証明書
key_file: "server-private-key.pem" # サーバー秘密鍵
# Prometheusにクライアント認証をかける
client_auth_type: "RequireAndVerifyClientCert" # https://golang.org/pkg/crypto/tls/#ClientAuthType
client_ca_file: "client-ca-cert.pem" # クライアント認証局の証明書
view raw web-config.yml hosted with ❤ by GitHub

Prometheus自身のメトリクスを取得する際にもクライアント認証を突破する必要があるので,
クライアント証明書クライアント秘密鍵(クライアントの身元を証明するのに使用),
サーバー認証局の証明書(HTTPSサーバーの身元を確認するのに使用)も持たせることにする.
(Prometheus自身のメトリクスを取得しない場合は不要)

クライアント認証を突破するための設定はprometheus.ymlscrape_configs2に記述する.

scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['prometheus.hogehoge.com:443'] # 対象(自分自身)をサーバー証明書のSANsにあるホスト名で指定
scheme: https # HTTPSで接続する
tls_config:
# クライアント認証を突破する
cert_file: "client-cert.pem" # クライアント証明書
key_file: "client-private-key.pem" # クライアント秘密鍵
# サーバー証明書が公的な認証局で署名されている場合は不要かも?
ca_file: "server-cert.pem" # サーバー認証局の証明書
# insecure_skip_verify: true # めんどくさいときはこっち
view raw prometheus.yml hosted with ❤ by GitHub

あとは指定のパスにそれぞれの秘密鍵, 証明書ファイルを配置する.
今回はDockerで起動するのでパスを指定してマウントしてあげれば良い.

version: "3.9"
services:
prometheus:
image: prom/prometheus:v2.27.1
container_name: prometheus.hogehoge.com # コンテナから名前解決できるようにコンテナ名をサーバー証明書のSANsに指定したホスト名にする
command: [
"--config.file=/etc/prometheus/prometheus.yml",
"--web.config.file=/etc/prometheus/web-config.yml",
"--web.external-url=https://prometheus.hogehoge.com/", # 証明書のSANsに指定したホスト名
"--web.listen-address=0.0.0.0:443", # Prometheusを443番ポートで起動
]
volumes: [
"./prometheus.yml:/etc/prometheus/prometheus.yml",
"./web-config.yml:/etc/prometheus/web-config.yml",
"./server-cert.pem:/etc/prometheus/server-cert.pem", # サーバー証明書
"./server-private-key.pem:/etc/prometheus/server-private-key.pem", # サーバー秘密鍵
"./client-ca-cert.pem:/etc/prometheus/client-ca-cert.pem", # クライアント認証局の証明書
"./client-cert.pem:/etc/prometheus/client-cert.pem", # クライアント証明書(自身のメトリクスを見ない場合は不要)
"./client-private-key.pem:/etc/prometheus/client-private-key.pem", # クライアント秘密鍵(自身のメトリクスを見ない場合は不要)
]
ports:
- 443:443 # ホストOSの443番ポートをこのコンテナの443番に割り当てる

以上で準備ができたので, いよいよDockerで起動する.

# ディレクトリの状態(使わないものは消している)
$ ls -1
client-ca-cert.pem
client-cert.pem
client-private-key.pem
docker-compose.yml
prometheus.yml
server-cert.pem
server-private-key.pem
web-config.yml

# コンテナを起動する
$ docker compose up -d --force-recreate --remove-orphans

動作確認のため,
Chromeで https://prometheus.hogehoge.com を開く.

Macにクライアント証明書をもたせたため, 問題なく閲覧できた.

今度はクライアント証明書を使わずに https://prometheus.hogehoge.com を開いてみる.

要求されたクライアント証明書を提示しなかったため, 閲覧ができない.
以上でPrometheusにクライアント認証がかかっていることを確認できた.

# コンテナを終了しておく
$ docker compose down

Prometheus+nginxでクライアント認証をかける

Prometheusの前段にnginx(リバースプロキシ)を用意して, そこでクライアント認証をかけることもできる.

nginxサーバー証明書サーバー秘密鍵(HTTPSサーバーの身元を証明するのに使用),
クライアント認証局の証明書(クライアントの身元を確認するのに使用)を持たせる.

今回はHTTPS化と認証まわりの設定をhttps.confに記述する.
今回はクライアント認証を行うポート(443)と別に認証をかけないポート(44433)も用意してみる.

# クライアント認証をかけるポート
server {
# HTTPS化する
listen 443 ssl;
server_name prometheus.hogehoge.com; # 証明書のSANsに指定した名前
ssl_certificate /etc/nginx/https/server-cert.pem; # サーバー証明書
ssl_certificate_key /etc/nginx/https/server-private-key.pem; # サーバー秘密鍵
# クライアント認証をかける
ssl_verify_client on; # クライアント認証を有効にする
ssl_client_certificate /etc/nginx/https/client-ca-cert.pem; # クライアント認証局の証明書
# Prometheusにリバースプロキシする
location / {
proxy_pass http://prometheus:9090/; # Docker Composeなのでコンテナ名で名前解決可能
}
}
# クライアント認証をかけないポート
server {
listen 44433 ssl; # 443以外のポートを指定
server_name prometheus.hogehoge.com;
ssl_certificate /etc/nginx/https/server-cert.pem;
ssl_certificate_key /etc/nginx/https/server-private-key.pem;
# Basic認証などその他の認証をかけたいときはここで設定する
location / {
proxy_pass http://prometheus:9090/;
}
}
view raw https.conf hosted with ❤ by GitHub

この場合Prometheus自体にはクライアント認証をかけないのでweb-config.ymlは不要,
さらに自身のメトリクスを取得する場合でもクライアント証明書クライアント秘密鍵は不要となる.
(したがってprometheus.ymlも自分で作らずデフォルトのものを使用する)

あとは各種ファイルをnginxのコンテナにマウントして起動する.
(先程とは異なり, Prometheusにファイルを持たせないなど設定が変わっていることに注意)

version: "3.9"
services:
prometheus:
image: prom/prometheus:v2.27.1
command: [
"--config.file=/etc/prometheus/prometheus.yml", # 今回はデフォルト設定
"--web.external-url=https://prometheus.hogehoge.com", # 証明書のSANsに指定したホスト名
"--web.route-prefix=/", # パスは/を明示的に指定
]
nginx:
image: nginx:1.21.0
container_name: prometheus.hogehoge.com # コンテナから名前解決できるようにコンテナ名をサーバー証明書のSANsに指定したホスト名にする
volumes: [
"./https.conf:/etc/nginx/conf.d/https.conf",
"./server-cert.pem:/etc/nginx/https/server-cert.pem", # サーバー証明書
"./server-private-key.pem:/etc/nginx/https/server-private-key.pem", # サーバー秘密鍵
"./client-ca-cert.pem:/etc/nginx/https/client-ca-cert.pem", # クライアント認証局の証明書
]
ports:
- 443:443
- 44433:44433
# ディレクトリの状態(使わないものを消している)
$ ls -1
client-ca-cert.pem
docker-compose.yml
https.conf
server-cert.pem
server-private-key.pem

# コンテナを起動する
$ docker compose up -d --force-recreate --remove-orphans

Chromeでhttps://prometheus.hogehoge.comを開く.

クライアント証明書を提示しなかったので認証に失敗した.
(クライアント証明書を要求された際に"OK"を選択すると認証に成功する)

この状態で次はhttps://prometheus.hogehoge.com:44433を開く.

44433ポートにはクライアント認証をかけていないため, そのまま開くことができた.

こんな感じの出し分けをPrometheus単独でやるのは難しいが,
nginxを使うと比較的カンタンに実現できるし, Prometheus自身のメトリクスを取得するためにクライアント証明書などを持たせる必要もなくなる.

クライアント認証以外の認証方式も設定しやすいので,
個人的にはこちらのほうがすき.

# コンテナを終了しておく
$ docker compose down

おわり

Prometheusにクライアント認証をかけてみた.

セキュリティ要件でPrometheusへのアクセスに認証が必要な場合は,
基本は前段のnginxで認証設定をかけてクライアント側(Grafanaとか)に認証情報を持たせてアクセスするのが良いと思う.

おまけ