iijdpf.go raw
1 // Package iijdpf implements a DNS provider for solving the DNS-01 challenge using IIJ DNS Platform Service.
2 package iijdpf
3
4 import (
5 "context"
6 "errors"
7 "fmt"
8 "time"
9
10 "github.com/go-acme/lego/v4/challenge"
11 "github.com/go-acme/lego/v4/challenge/dns01"
12 "github.com/go-acme/lego/v4/platform/config/env"
13 "github.com/miekg/dns"
14 dpfapi "github.com/mimuret/golang-iij-dpf/pkg/api"
15 dpfapiutils "github.com/mimuret/golang-iij-dpf/pkg/apiutils"
16 )
17
18 // Environment variables names.
19 const (
20 envNamespace = "IIJ_DPF_"
21
22 EnvAPIToken = envNamespace + "API_TOKEN"
23 EnvServiceCode = envNamespace + "DPM_SERVICE_CODE"
24
25 EnvAPIEndpoint = envNamespace + "API_ENDPOINT"
26 EnvTTL = envNamespace + "TTL"
27 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
28 EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
29 )
30
31 var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
32
33 // Config is used to configure the creation of the DNSProvider.
34 type Config struct {
35 Token string
36 ServiceCode string
37
38 Endpoint string
39 PropagationTimeout time.Duration
40 PollingInterval time.Duration
41 TTL int
42 }
43
44 // NewDefaultConfig returns a default configuration for the DNSProvider.
45 func NewDefaultConfig() *Config {
46 return &Config{
47 Endpoint: env.GetOrDefaultString(EnvAPIEndpoint, dpfapi.DefaultEndpoint),
48 PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 660*time.Second),
49 PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 5*time.Second),
50 TTL: env.GetOrDefaultInt(EnvTTL, 300),
51 }
52 }
53
54 // DNSProvider implements the challenge.Provider interface.
55 type DNSProvider struct {
56 client dpfapi.ClientInterface
57 config *Config
58 }
59
60 // NewDNSProvider returns a DNSProvider instance configured for IIJ DNS.
61 func NewDNSProvider() (*DNSProvider, error) {
62 values, err := env.Get(EnvAPIToken, EnvServiceCode)
63 if err != nil {
64 return nil, fmt.Errorf("iijdpf: %w", err)
65 }
66
67 config := NewDefaultConfig()
68 config.Token = values[EnvAPIToken]
69 config.ServiceCode = values[EnvServiceCode]
70
71 return NewDNSProviderConfig(config)
72 }
73
74 // NewDNSProviderConfig takes a given config
75 // and returns a custom configured DNSProvider instance.
76 func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
77 if config.Token == "" {
78 return nil, errors.New("iijdpf: API token missing")
79 }
80
81 if config.ServiceCode == "" {
82 return nil, errors.New("iijdpf: Servicecode missing")
83 }
84
85 return &DNSProvider{
86 client: dpfapi.NewClient(config.Token, config.Endpoint, nil),
87 config: config,
88 }, nil
89 }
90
91 // Timeout returns the timeout and interval to use when checking for DNS propagation.
92 // Adjusting here to cope with spikes in propagation times.
93 func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
94 return d.config.PropagationTimeout, d.config.PollingInterval
95 }
96
97 // Present creates a TXT record using the specified parameters.
98 func (d *DNSProvider) Present(domain, token, keyAuth string) error {
99 ctx := context.Background()
100
101 info := dns01.GetChallengeInfo(domain, keyAuth)
102
103 zoneID, err := dpfapiutils.GetZoneIdFromServiceCode(ctx, d.client, d.config.ServiceCode)
104 if err != nil {
105 return fmt.Errorf("iijdpf: failed to get zone id: %w", err)
106 }
107
108 err = d.addTxtRecord(ctx, zoneID, dns.CanonicalName(info.EffectiveFQDN), `"`+info.Value+`"`)
109 if err != nil {
110 return fmt.Errorf("iijdpf: %w", err)
111 }
112
113 err = d.commit(ctx, zoneID)
114 if err != nil {
115 return fmt.Errorf("iijdpf: %w", err)
116 }
117
118 return nil
119 }
120
121 // CleanUp removes the TXT record matching the specified parameters.
122 func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
123 ctx := context.Background()
124
125 info := dns01.GetChallengeInfo(domain, keyAuth)
126
127 zoneID, err := dpfapiutils.GetZoneIdFromServiceCode(ctx, d.client, d.config.ServiceCode)
128 if err != nil {
129 return fmt.Errorf("iijdpf: failed to get zone id: %w", err)
130 }
131
132 err = d.deleteTxtRecord(ctx, zoneID, dns.CanonicalName(info.EffectiveFQDN), `"`+info.Value+`"`)
133 if err != nil {
134 return fmt.Errorf("iijdpf: %w", err)
135 }
136
137 err = d.commit(ctx, zoneID)
138 if err != nil {
139 return fmt.Errorf("iijdpf: %w", err)
140 }
141
142 return nil
143 }
144