Skip to content

Commit

Permalink
feat: add DNS provider for Volcano Engine
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez committed Sep 20, 2024
1 parent eb7de2a commit 5c29ea1
Show file tree
Hide file tree
Showing 6 changed files with 776 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ require (
github.com/ultradns/ultradns-go-sdk v1.7.0-20240913052650-970ca9a
github.com/urfave/cli/v2 v2.27.4
github.com/vinyldns/go-vinyldns v0.9.16
github.com/volcengine/volc-sdk-golang v1.0.177
github.com/vultr/govultr/v3 v3.9.1
github.com/yandex-cloud/go-genproto v0.0.0-20240911120709-1fa0cb6f47c2
github.com/yandex-cloud/go-sdk v0.0.0-20240911121212-e4e74d0d02f5
Expand Down
368 changes: 368 additions & 0 deletions go.sum

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions providers/dns/dns_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/versio"
"github.com/go-acme/lego/v4/providers/dns/vinyldns"
"github.com/go-acme/lego/v4/providers/dns/vkcloud"
"github.com/go-acme/lego/v4/providers/dns/volcengine"
"github.com/go-acme/lego/v4/providers/dns/vscale"
"github.com/go-acme/lego/v4/providers/dns/vultr"
"github.com/go-acme/lego/v4/providers/dns/webnames"
Expand Down Expand Up @@ -397,6 +398,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return vinyldns.NewDNSProvider()
case "vkcloud":
return vkcloud.NewDNSProvider()
case "volcengine":
return volcengine.NewDNSProvider()
case "vscale":
return vscale.NewDNSProvider()
case "vultr":
Expand Down
228 changes: 228 additions & 0 deletions providers/dns/volcengine/volcengine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// Package volcengine implements a DNS provider for solving the DNS-01 challenge using Volcano Engine.
package volcengine

import (
"context"
"errors"
"fmt"
"net/http"
"sync"
"time"

"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/miekg/dns"
"github.com/volcengine/volc-sdk-golang/base"
volc "github.com/volcengine/volc-sdk-golang/service/dns"
)

// Environment variables names.
const (
envNamespace = "VOLC_"

EnvAccessKey = envNamespace + "ACCESSKEY"
EnvSecretKey = envNamespace + "SECRETKEY"

EnvRegion = envNamespace + "REGION"
EnvHost = envNamespace + "HOST"
EnvScheme = envNamespace + "SCHEME"

EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
)

// Config is used to configure the creation of the DNSProvider.
type Config struct {
AccessKey string
SecretKey string

Region string
Host string
Scheme string

PropagationTimeout time.Duration
PollingInterval time.Duration
TTL int
HTTPTimeout time.Duration
}

// NewDefaultConfig returns a default configuration for the DNSProvider.
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, volc.Timeout*time.Second),
}
}

// DNSProvider implements the challenge.Provider interface.
type DNSProvider struct {
client *volc.Client
config *Config

recordIDs map[string]*string
recordIDsMu sync.Mutex
}

// NewDNSProvider returns a DNSProvider instance configured for Volcano Engine.
// Credentials must be passed in the environment variable: VOLC_ACCESSKEY, VOLC_SECRETKEY.
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAccessKey, EnvSecretKey)
if err != nil {
return nil, fmt.Errorf("volcengine: %w", err)
}

config := NewDefaultConfig()
config.AccessKey = values[EnvAccessKey]
config.SecretKey = values[EnvSecretKey]
config.Scheme = env.GetOrDefaultString(EnvScheme, "https")
config.Host = env.GetOrDefaultString(EnvHost, "open.volcengineapi.com")
config.Region = env.GetOrDefaultString(EnvRegion, volc.DefaultRegion)

return NewDNSProviderConfig(config)
}

// NewDNSProviderConfig return a DNSProvider instance configured for Volcano Engine.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("volcengine: the configuration of the DNS provider is nil")
}

if config.AccessKey == "" || config.SecretKey == "" {
return nil, errors.New("volcengine: missing credentials")
}

return &DNSProvider{
config: config,
client: newClient(config),
recordIDs: make(map[string]*string),
}, nil
}

// Present creates a TXT record to fulfill the dns-01 challenge.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
ctx := context.Background()

info := dns01.GetChallengeInfo(domain, keyAuth)

zoneID, err := d.getZoneID(ctx, info.EffectiveFQDN)
if err != nil {
return fmt.Errorf("volcengine: get zone ID: %w", err)
}

crr := &volc.CreateRecordRequest{
Host: pointer(dns01.UnFqdn(info.EffectiveFQDN)),
TTL: pointer(int64(d.config.TTL)),
Type: pointer("TXT"),
Value: pointer(info.Value),
ZID: zoneID,
}

record, err := d.client.CreateRecord(ctx, crr)
if err != nil {
return fmt.Errorf("volcengine: create record: %w", err)
}

d.recordIDsMu.Lock()
d.recordIDs[token] = record.RecordID
d.recordIDsMu.Unlock()

return nil
}

// CleanUp removes the TXT record matching the specified parameters.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
info := dns01.GetChallengeInfo(domain, keyAuth)

// gets the record's unique ID
d.recordIDsMu.Lock()
recordID, ok := d.recordIDs[token]
d.recordIDsMu.Unlock()
if !ok {
return fmt.Errorf("volcengine: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token)
}

drr := &volc.DeleteRecordRequest{RecordID: recordID}

err := d.client.DeleteRecord(context.Background(), drr)
if err != nil {
return fmt.Errorf("volcengine: delete record: %w", err)
}

return nil
}

func (d *DNSProvider) getZoneID(ctx context.Context, fqdn string) (*int64, error) {
for _, index := range dns.Split(fqdn) {
domain := fqdn[index:]

lzr := &volc.ListZonesRequest{
Key: pointer(dns01.UnFqdn(domain)),
SearchMode: pointer("exact"),
}

zones, err := d.client.ListZones(ctx, lzr)
if err != nil {
return nil, fmt.Errorf("list zones: %w", err)
}

total := deref(zones.Total)

if total == 0 || len(zones.Zones) == 0 {
continue
}

if total > 1 {
return nil, fmt.Errorf("too many zone for %s", domain)
}

return zones.Zones[0].ZID, nil
}

return nil, fmt.Errorf("zone no found for fqdn: %s", fqdn)
}

// https://github.com/volcengine/volc-sdk-golang/tree/main/service/dns
// https://github.com/volcengine/volc-sdk-golang/blob/main/example/dns/demo_dns_test.go
func newClient(config *Config) *volc.Client {
// https://github.com/volcengine/volc-sdk-golang/blob/fae992a31d02754e271c322095413d374ea4ea1b/service/dns/config.go#L20-L35
serviceInfo := &base.ServiceInfo{
Timeout: config.HTTPTimeout,
Host: config.Host,
Header: http.Header{"Accept": []string{"application/json"}},
Scheme: config.Scheme,
Credentials: base.Credentials{
Service: volc.ServiceName,
Region: config.Region,
AccessKeyID: config.AccessKey,
SecretAccessKey: config.SecretKey,
},
}

// https://github.com/volcengine/volc-sdk-golang/blob/fae992a31d02754e271c322095413d374ea4ea1b/service/dns/caller.go#L17-L19
client := base.NewClient(serviceInfo, nil)

// https://github.com/volcengine/volc-sdk-golang/blob/fae992a31d02754e271c322095413d374ea4ea1b/service/dns/caller.go#L25-L34
caller := &volc.VolcCaller{Volc: client}
caller.Volc.SetAccessKey(serviceInfo.Credentials.AccessKeyID)
caller.Volc.SetSecretKey(serviceInfo.Credentials.SecretAccessKey)
caller.Volc.SetHost(serviceInfo.Host)
caller.Volc.SetScheme(serviceInfo.Scheme)
caller.Volc.SetTimeout(serviceInfo.Timeout)

return volc.NewClient(caller)
}

func pointer[T any](v T) *T { return &v }

func deref[T any](v *T) T {
if v == nil {
var zero T
return zero
}

return *v
}
28 changes: 28 additions & 0 deletions providers/dns/volcengine/volcengine.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Name = "Volcano Engine/火山引擎"
Description = ''''''
URL = "https://www.volcengine.com/"
Code = "volcengine"
Since = "v4.19.0"

Example = '''
VOLC_ACCESSKEY=xxx \
VOLC_SECRETKEY=yyy \
lego --email [email protected] --dns volcengine --domains "example.org" --domains "*.example.org" run
'''

[Configuration]
[Configuration.Credentials]
VOLC_ACCESSKEY = "Access Key ID (AK)"
VOLC_SECRETKEY = "Secret Access Key (SK)"
[Configuration.Additional]
VOLC_REGION = "Region"
VOLC_HOST = "API host"
VOLC_SCHEME = "API scheme"
VOLC_POLLING_INTERVAL = "Time between DNS propagation check"
VOLC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
VOLC_TTL = "The TTL of the TXT record used for the DNS challenge"
VOLC_HTTP_TIMEOUT = "API request timeout"

[Links]
API = "https://www.volcengine.com/docs/6758/155086"
GoClient = "https://github.com/volcengine/volc-sdk-golang"
Loading

0 comments on commit 5c29ea1

Please sign in to comment.