Cert managerを拡張し未対応のDNSプロバイダで利用する
概要
Cert managerはデフォルトでCloud DNSやRoute 53をサポートしていますが、Alibaba Cloud DNSやさくらのクラウドのDNSアプライアンスなどはサポートしておらず、そのままインストールしても利用できません。
// github.com/jetstack/cert-manager/pkg/issuer/acme/dns/dns.go
type dnsProviderConstructors struct {
cloudDNS func(project string, serviceAccount []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*clouddns.DNSProvider, error)
cloudFlare func(email, apikey, apiToken string, dns01Nameservers []string) (*cloudflare.DNSProvider, error)
route53 func(accessKey, secretKey, hostedZoneID, region, role string, ambient bool, dns01Nameservers []string) (*route53.DNSProvider, error)
azureDNS func(environment, clientID, clientSecret, subscriptionID, tenantID, resourceGroupName, hostedZoneName string, dns01Nameservers []string, ambient bool) (*azuredns.DNSProvider, error)
acmeDNS func(host string, accountJson []byte, dns01Nameservers []string) (*acmedns.DNSProvider, error)
digitalOcean func(token string, dns01Nameservers []string) (*digitalocean.DNSProvider, error)
}
しかしCert managerにはWebhookの拡張用インターフェースが用意されており、当該インターフェースに準拠したWebサーバを開発することでデフォルトでサポートしているDNSプロバイダ以外でもCert managerを利用することが可能となります。
今回はそのWebhookの仕組みに沿ってCert managerを拡張する方法を解説します。
開発
Webhookでの拡張機能開発用に公式(jetstack
)から以下のテンプレートリポジトリが提供されており、これに沿って開発を進めることができます。
上記のテンプレートリポジトリにあるmain.go
は以下のように定義されており、それぞれのイベントに対応したメソッドが用意されています。
package main
import (
"encoding/json"
"fmt"
"os"
extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/client-go/rest"
"github.com/jetstack/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
"github.com/jetstack/cert-manager/pkg/acme/webhook/cmd"
)
var GroupName = os.Getenv("GROUP_NAME")
func main() {
if GroupName == "" {
panic("GROUP_NAME must be specified")
}
cmd.RunWebhookServer(GroupName,
&customDNSProviderSolver{},
)
}
type customDNSProviderSolver struct {
}
type customDNSProviderConfig struct {
}
func (c *customDNSProviderSolver) Name() string {
return "my-custom-solver"
}
func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
cfg, err := loadConfig(ch.Config)
if err != nil {
return err
}
return nil
}
func (c *customDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
return nil
}
func (c *customDNSProviderSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error {
return nil
}
func loadConfig(cfgJSON *extapi.JSON) (customDNSProviderConfig, error) {
cfg := customDNSProviderConfig{}
if cfgJSON == nil {
return cfg, nil
}
if err := json.Unmarshal(cfgJSON.Raw, &cfg); err != nil {
return cfg, fmt.Errorf("error decoding solver config: %v", err)
}
return cfg, nil
}
実装
主に実装すべき箇所となるmain.go
にある構造体やメソッドを順に解説していきます。
customDNSProviderConfig
この構造体は以下のように定義されており主にDNSプロバイダのAPIキーなど何らか設定情報のために用意されています。
type customDNSProviderConfig struct {
...
}
customDNSProviderSolver
この構造体はCert managerのイベントに対応したロジックを定義したメソッドを保持します。
type customDNSProviderSolver struct {
:
}
groupName
及びsolverName
webhookで実装された拡張機能は事前に定義・設定されたgroupName
とsolverName
によって識別されます。
main.go
では環境変数であるGROUP_NAME
から値を取得しmain()
関数内でcmd.RunWebhookServer()
関数の第一引数として取得した値を渡すことでgroupName
を設定します。
var GroupName = os.Getenv("GROUP_NAME")
func main() {
if GroupName == "" {
panic("GROUP_NAME must be specified")
}
cmd.RunWebhookServer(GroupName,
&customDNSProviderSolver{},
)
}
加えてcustomDNSProviderSolver
構造体はName()
というメソッドを持っており、この戻り値がsolverName
として扱われます。
func (c *customDNSProviderSolver) Name() string ...
DNS01チャレンジ用TXTレコードの追加及び削除
Let's encriptのDNS01チャレンジで用いるTXTレコードの追加及び削除は以下のメソッド内で行います。
func (solver *SacloudDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error ... // 追加
func (solver *SacloudDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error ... // 削除
上記メソッド内で行う処理はDNSプロバイダによって様々でプロバイダ側から提供されているライブラリやAPIを用いてチャレンジ用のレコードの作成及び削除を行う必要があります。
これで実装すべき箇所は以上となります。
デプロイ
Dockerイメージ化
Kubernetesのクラスタ上にデプロイする際は上記で実装したwebhookを事前にDockerイメージ化する必要があります。自身はGithub Actionsでコードのビルド及びタグ付け、レジストリへのpushまでを自動化しています。
Helm Chartの用意
デプロイするためのマニフェストはテンプレートリポジトリのdeploy/example-webhook
ディレクトリにあるHelm Chartを雛形として利用します。
当該ディレクトリ内のvalues.yaml
は以下のように定義されており必要に応じてDockerイメージのレジストリやイメージ、タグなどの値を設定します。
groupName: acme.mycompany.com
certManager:
namespace: cert-manager
serviceAccountName: cert-manager
image:
repository: mycompany/webhook-image
tag: latest
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
service:
type: ClusterIP
port: 443
resources: {}
nodeSelector: {}
tolerations: []
affinity: {}
Apply
以下のコマンドでHelmとしてインストールします。
$ helm install ./deploy/example-webhook/ --generate-name
デバッグ
デバッグは主にk8s.io/klog
パッケージでコード内にログの出力処理を埋め込んで行います。その他にもcert-manager
のnamespace内に存在するPodからもログが出力されるので参考にできると思います。
留意点
以下のプルリクで投げられている修正は適応する必要があります。
参考
自身はさくらのクラウドで提供されているDNSアプライアンス向けにwebhookを実装しましたが他にもAlibabaなどの実装が存在し非常に参考になりました。
Discussion