はじめに
これはConoHa Advent Calendar 2025の22日目の記事です。
以下、長ったらしく書いてますが達成したのは以下の2つです。
- CaddyというWebサーバーからワイルドカード証明書を自動取得、更新する
- legoにも同機能のモジュールを追加して、traefikやCI/CDなどから同証明書を自動取得、更新する
証明書の管理について
証明書を発行、管理は意外とめんどくさいです。個人ブログやコメントエンジンなど、小規模なサービスをいくつも公開している場合はワイルドカード証明書が便利ですが、更新作業をcronでスクリプト化し、更新後にWebサーバーをリロードして...と、細かい作業が必要です。
もう少し見通しの良い方法はないのでしょうか?
僕のConoHaの用途ではWebサーバーのみが証明書を利用するため、可能なら証明書管理もWebサーバー側で完結してほしいところです。
こういった用途にはCaddyが便利です。Caddyでは、HTTP-01チャレンジ(以下HTTP-01)を使った証明書の自動発行・更新を標準でサポートしています。しかし、ワイルドカード証明書や、まだ公開していないサブドメイン向けの証明書を取得したい場合はHTTP-01では対応できません。なぜなら、同チャレンジは証明書を取得したドメインに対応するWebサーバーが必要であり、ワイルド カード証明書や諸事情によりWebサーバーを利用しない(80番ポートが外部から利用できないなど)場合には利用できないからです。この場合、証明書の取得にはDNS-01チャレンジ(以下DNS-01)を利用する必要があり、CaddyではDNSプロバイダーに合わせたモジュールが必要になります。
DNS-01では、HTTP-01のようにWebサーバーにファイルを置くのではなく、DNSにTXTレコードを追加してドメインの所有を証明します。そのため、自動化するにはDNSのレコードをAPI経由で編集できなければなりません。ConoHaは、国内のVPSでは珍しく、DNSレコードをAPIで操作できます。
そこで今回は、CaddyがConoHaのAPIを利用してDNS-01チャレンジを自動的に実行できるようにするモジュールを作成したので、紹介したいと思います。
caddy-moduleの開発
Caddyでは各DNSプロバイダ向けのモジュールが公式で個別に用意されているわけではなく、多くはコミュニティによって開発・維持されています。CaddyからConoHaのDNSを扱うには、DNSレコード操作を行うためのlibdnsと、それをCaddyのモジュールとして利用するためのcaddy-dnsの2つを実装する必要があります。
今回は、Cloudflareなど既存のlibdns/caddy-dnsの実装を参考にしながら、ConoHaのAPI に合わせて実装しました。現在は両パッケージのメンテナンスも行っているので、実装に気づいた点や改善案などがあれば教えていただけると助かります。
また、ConoHa以外のプロバイダを利用していて既存のモジュールが見つからない場合でも、基本的な構造は共通しているため比較的簡単に作成できます。作成ガイドもあるので、必要に応じて参考にしてみてください。
caddy-moduleの利用
作成したモジュールを利用するには、dns-providerにConoHaのモジュールをを含めた状態でCaddyをビルドする必要があります。基本的には利用ガイドとREADMEを読めば問題なく使えると思いますが、ここではDocker/Podmanなど、コンテナを用いたビルドと運用方法を紹介します。
まずは次のようなContainerfileを用意します。
FROM caddy:2.10.0-builder-alpine AS builder
RUN xcaddy build --with github.com/caddy-dns/conoha@v0.1.0 # ここでConoHaのモジュールを指定
FROM caddy:2.10.0
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
次にCaddyfileを用意します。
CaddyfileはCaddyの設定ファイルで、サーバーの構成を簡潔に記述できます。
今回はConoHaのAPIを利用するため、CaddyfileにAPIテナントID、APIユーザーID、APIパスワードを記述する必要があります。いずれもConoHaVPSのコンソールにあるAPIのページから取得できます。具体的な記述例はREADMEにまとめてあります。
例としては次のようになります。
example.com, *.example.com{
tls {
dns conoha {
api_tenant_id {env.API_TENANT_ID}
api_user_id {env.API_USER_ID}
api_password {env.API_PASSWORD}
}
propagation_timeout 10m
propagation_delay 5m
}
...
上記の例では環境変数から読み込んでいますが、Caddyfileに直接書いても問題ありません。
また、DNSのレコードの伝播に少し時間がかかるので、propagation_delayなどで一定時間待ってあげると、失敗することなく証明書を発行できます。
あとはBasicUsageを参考に、DockerやPodmanでコンテナを起動するだけです。
例えば、以下のようなdocker-compose.ymlが考えられます。
services:
caddy:
build:
context: ./caddy/
dockerfile: Containerfile
container_name: caddy
restart: always
ports:
- "80:80"
- "443:443"
env_file:
- ".env_caddy"
volumes:
- ./caddy/conf:/etc/caddy:ro
- ./caddy/contents/public:/usr/share/caddy:ro
...
これで、ConoHaのDNSを利用したDNS-01チャレンジをCaddyで自動化できるようになります。簡単ですね。ワイルドカード証明書を安定して運用できるため、小規模なサービスを複数公開している場合でも管理の手間が大きく減ります。Caddy自体が証明書の更新をすべて処理してくれるため、cronの設定やスクリプトのメンテナンスも不要です。
懸念点、リスク、感想
APIクレデンシャルについて
CaddyでのDNS-01チャレンジを用いた証明書の更新は便利ですが、ConoHaのAPIクレデンシャルをそのまま使っているところに怖さがあると思います。
ConoHaのAPIは強力ですが、強力すぎるあまり、こういった長期間かつ継続的に動作するシステムへのクレデンシャルとして渡し、万が一そのシステムの脆弱性や自身の設定などでAPIクレデンシャルが漏洩したときは甚大な被害を受ける可能性があります。
たとえば、Caddyに渡しているConoHaのクレデンシャルが漏洩した場合、悪意あるユーザーはAPIで利用できるあらゆる行為を行うことができます。
ConoHaでは、2025年8月からサブユーザーと呼ばれるAPIのルートユーザーよりも権限を抑えた状態で利用できるモードが追加されました。
AWSのIAMのようにルートユーザーがロールをサブユーザーにアタッチして利用することで、サブユーザーがAPIでアクセスできる領域を制限しているわけですね。
しかし、サブユーザーにアタッチできるロールにはトークン発行やボリューム操作などVPS自体の操作がメインであり、残念ながら今回利用しているDNSのAPIは対象外になってしまいました。
個人的には、EFFの指摘にあるように、APIのロールやポリシーのスコープをもっと小さくして、ゾーンやレコードの種類ごとにロールを作成できるようにしても良いと思います。
例えばDNS-01チャレンジで編集する必要があるのはTXTレコードのみです。TXTレコードとA/AAAAレコードでは、その値が示す影響力や重要性が大きく異なると考えられますし、然らばそれに対応するポリシーを設けても良いのかと思います。
まあ、可能性の話に対してそこまでする必要があるのかはわかりませんが...
ワイルドカード証明書について
ワイルドカード証明書は便利ですが、以下のような問題もあると考えます。
-
様々なサービスで同一の証明書を使うことで、使用範囲が拡大するうえ、リスクも増大する
秘密鍵の露出範囲が広がり、鍵管理の難易度が上がるということですね。配布経路が増えるほど、漏洩の確率が高まるということですし、いずれか1箇所で秘密鍵が漏洩すると、すべてのサブドメインが危険に晒されることになります -
証明書の範囲と構成によっては、不思議な攻撃を可能にしてしまうリスクがある
異なるアプリケーションプロトコルに同一の証明書を使っている場合、意図しないサーバが別プロトコルのリクエストを受け取ってしまい、認証情報漏洩やCSRF的な副作用が発生する可能性があるそうですね。まあ、攻撃の成立条件が厳しく、発生しにくそうな攻撃ですが、念の為プロトコルごとに証明書は分けるべきなのでしょう。
ちゃんと利用範囲や鍵自体の管理をしましょうねってことですね。
Conoha APIについて
ConoHaのAPIは便利ですが、時折一貫性のなさや、隠れた挙動が確認できるときがあります。
今回のモジュール作成においては以下の問題が確認できました。
- レコード作成時には、API仕様書では認められていない、ttlフィールドをRequest Jsonに含み、設定することができる
- レコード更新時には、API仕様書では認められていない、ttlフィールドをRequest Jsonに含み、設定することができない
どちらも、おなじRequest Jsonを使用しているのに、挙動がことなるのは少し不思議に感じました。
個人的に、ttlはコンソール上から設定できるため、APIからでも設定できるべきだと考えます。
念の為、サポートに問い合わせようと思いましたが、ページから問い合わせメールアドレスをどうしても発見できなかったため、断念しました...
知っている方いたら教えていただけると幸いです。
ConoHa DNSレコードについて
ConoHaではDNSで利用できるレコードの種類が制限されています。1
これにより、今回のモジュールではCaddyで利用される一部機能の実装ができませんでした。
レコードの種類を制限する理由ってなにかあるのでしょうか?
たとえば、ユーザーの設定ミスによる被害を防ぐとか?
詳しく知っている方がいたらこちらも教えていただきたいです。
おまけ
legoにも同様の機能を追加しました。2
最後に
読んでいただきありがとうございました。
寒くなってきましたので、体調に気をつけて素敵な年末をお過ごしください。
-
2025年12月8日現在、A, AAAA, CNAME, MX, NS, SRV, TXTの6つ ↩