diff --git a/README.md b/README.md index c1c25eac..6f287400 100644 --- a/README.md +++ b/README.md @@ -46,29 +46,29 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). | | | | | |---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------|---------------------------------------------------------------------------------| -| [Akamai EdgeDNS](https://go-acme.github.io/lego/dns/edgedns/) | [Alibaba Cloud DNS](https://go-acme.github.io/lego/dns/alidns/) | [Amazon Lightsail](https://go-acme.github.io/lego/dns/lightsail/) | [Amazon Route 53](https://go-acme.github.io/lego/dns/route53/) | -| [ArvanCloud](https://go-acme.github.io/lego/dns/arvancloud/) | [Aurora DNS](https://go-acme.github.io/lego/dns/auroradns/) | [Autodns](https://go-acme.github.io/lego/dns/autodns/) | [Azure](https://go-acme.github.io/lego/dns/azure/) | -| [Bindman](https://go-acme.github.io/lego/dns/bindman/) | [Bluecat](https://go-acme.github.io/lego/dns/bluecat/) | [Checkdomain](https://go-acme.github.io/lego/dns/checkdomain/) | [CloudDNS](https://go-acme.github.io/lego/dns/clouddns/) | -| [Cloudflare](https://go-acme.github.io/lego/dns/cloudflare/) | [ClouDNS](https://go-acme.github.io/lego/dns/cloudns/) | [CloudXNS](https://go-acme.github.io/lego/dns/cloudxns/) | [ConoHa](https://go-acme.github.io/lego/dns/conoha/) | -| [Constellix](https://go-acme.github.io/lego/dns/constellix/) | [deSEC.io](https://go-acme.github.io/lego/dns/desec/) | [Designate DNSaaS for Openstack](https://go-acme.github.io/lego/dns/designate/) | [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/) | -| [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/) | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/) | [DNSPod](https://go-acme.github.io/lego/dns/dnspod/) | [Domain Offensive (do.de)](https://go-acme.github.io/lego/dns/dode/) | -| [Domeneshop](https://go-acme.github.io/lego/dns/domeneshop/) | [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/) | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/) | [Dyn](https://go-acme.github.io/lego/dns/dyn/) | -| [Dynu](https://go-acme.github.io/lego/dns/dynu/) | [EasyDNS](https://go-acme.github.io/lego/dns/easydns/) | [Exoscale](https://go-acme.github.io/lego/dns/exoscale/) | [External program](https://go-acme.github.io/lego/dns/exec/) | -| [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) | [Gandi](https://go-acme.github.io/lego/dns/gandi/) | [Glesys](https://go-acme.github.io/lego/dns/glesys/) | [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | -| [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) | [Hetzner](https://go-acme.github.io/lego/dns/hetzner/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | -| [Hurricane Electric DNS](https://go-acme.github.io/lego/dns/hurricane/) | [HyperOne](https://go-acme.github.io/lego/dns/hyperone/) | [Infoblox](https://go-acme.github.io/lego/dns/infoblox/) | [Infomaniak](https://go-acme.github.io/lego/dns/infomaniak/) | -| [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [Internet.bs](https://go-acme.github.io/lego/dns/internetbs/) | [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Ionos](https://go-acme.github.io/lego/dns/ionos/) | -| [Joker](https://go-acme.github.io/lego/dns/joker/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | -| [Loopia](https://go-acme.github.io/lego/dns/loopia/) | [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | -| [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | -| [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [Netlify](https://go-acme.github.io/lego/dns/netlify/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [Njalla](https://go-acme.github.io/lego/dns/njalla/) | -| [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | -| [Porkbun](https://go-acme.github.io/lego/dns/porkbun/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [reg.ru](https://go-acme.github.io/lego/dns/regru/) | -| [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | -| [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Simply.com](https://go-acme.github.io/lego/dns/simply/) | [Sonic](https://go-acme.github.io/lego/dns/sonic/) | -| [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | -| [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | -| [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | +| [Akamai EdgeDNS](https://go-acme.github.io/lego/dns/edgedns/) | [Alibaba Cloud DNS](https://go-acme.github.io/lego/dns/alidns/) | [all-inkl](https://go-acme.github.io/lego/dns/allinkl/) | [Amazon Lightsail](https://go-acme.github.io/lego/dns/lightsail/) | +| [Amazon Route 53](https://go-acme.github.io/lego/dns/route53/) | [ArvanCloud](https://go-acme.github.io/lego/dns/arvancloud/) | [Aurora DNS](https://go-acme.github.io/lego/dns/auroradns/) | [Autodns](https://go-acme.github.io/lego/dns/autodns/) | +| [Azure](https://go-acme.github.io/lego/dns/azure/) | [Bindman](https://go-acme.github.io/lego/dns/bindman/) | [Bluecat](https://go-acme.github.io/lego/dns/bluecat/) | [Checkdomain](https://go-acme.github.io/lego/dns/checkdomain/) | +| [CloudDNS](https://go-acme.github.io/lego/dns/clouddns/) | [Cloudflare](https://go-acme.github.io/lego/dns/cloudflare/) | [ClouDNS](https://go-acme.github.io/lego/dns/cloudns/) | [CloudXNS](https://go-acme.github.io/lego/dns/cloudxns/) | +| [ConoHa](https://go-acme.github.io/lego/dns/conoha/) | [Constellix](https://go-acme.github.io/lego/dns/constellix/) | [deSEC.io](https://go-acme.github.io/lego/dns/desec/) | [Designate DNSaaS for Openstack](https://go-acme.github.io/lego/dns/designate/) | +| [Digital Ocean](https://go-acme.github.io/lego/dns/digitalocean/) | [DNS Made Easy](https://go-acme.github.io/lego/dns/dnsmadeeasy/) | [DNSimple](https://go-acme.github.io/lego/dns/dnsimple/) | [DNSPod](https://go-acme.github.io/lego/dns/dnspod/) | +| [Domain Offensive (do.de)](https://go-acme.github.io/lego/dns/dode/) | [Domeneshop](https://go-acme.github.io/lego/dns/domeneshop/) | [DreamHost](https://go-acme.github.io/lego/dns/dreamhost/) | [Duck DNS](https://go-acme.github.io/lego/dns/duckdns/) | +| [Dyn](https://go-acme.github.io/lego/dns/dyn/) | [Dynu](https://go-acme.github.io/lego/dns/dynu/) | [EasyDNS](https://go-acme.github.io/lego/dns/easydns/) | [Exoscale](https://go-acme.github.io/lego/dns/exoscale/) | +| [External program](https://go-acme.github.io/lego/dns/exec/) | [Gandi Live DNS (v5)](https://go-acme.github.io/lego/dns/gandiv5/) | [Gandi](https://go-acme.github.io/lego/dns/gandi/) | [Glesys](https://go-acme.github.io/lego/dns/glesys/) | +| [Go Daddy](https://go-acme.github.io/lego/dns/godaddy/) | [Google Cloud](https://go-acme.github.io/lego/dns/gcloud/) | [Hetzner](https://go-acme.github.io/lego/dns/hetzner/) | [Hosting.de](https://go-acme.github.io/lego/dns/hostingde/) | +| [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | [Hurricane Electric DNS](https://go-acme.github.io/lego/dns/hurricane/) | [HyperOne](https://go-acme.github.io/lego/dns/hyperone/) | [Infoblox](https://go-acme.github.io/lego/dns/infoblox/) | +| [Infomaniak](https://go-acme.github.io/lego/dns/infomaniak/) | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [Internet.bs](https://go-acme.github.io/lego/dns/internetbs/) | [INWX](https://go-acme.github.io/lego/dns/inwx/) | +| [Ionos](https://go-acme.github.io/lego/dns/ionos/) | [Joker](https://go-acme.github.io/lego/dns/joker/) | [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/) | +| [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [Loopia](https://go-acme.github.io/lego/dns/loopia/) | [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | +| [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | +| [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [Netlify](https://go-acme.github.io/lego/dns/netlify/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | +| [Njalla](https://go-acme.github.io/lego/dns/njalla/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | +| [OVH](https://go-acme.github.io/lego/dns/ovh/) | [Porkbun](https://go-acme.github.io/lego/dns/porkbun/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | +| [reg.ru](https://go-acme.github.io/lego/dns/regru/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | +| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Simply.com](https://go-acme.github.io/lego/dns/simply/) | +| [Sonic](https://go-acme.github.io/lego/dns/sonic/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | +| [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | +| [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 581f0280..d3fceabe 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -16,6 +16,7 @@ func allDNSCodes() string { "manual", "acme-dns", "alidns", + "allinkl", "arvancloud", "auroradns", "autodns", @@ -150,6 +151,26 @@ func displayDNSHelp(name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/alidns`) + case "allinkl": + // generated from: providers/dns/allinkl/allinkl.toml + ew.writeln(`Configuration for all-inkl.`) + ew.writeln(`Code: 'allinkl'`) + ew.writeln(`Since: 'v4.5.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ALL_INKL_API_KEY": API login`) + ew.writeln(` - "ALL_INKL_PASSWORD": API password`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ALL_INKL_HTTP_TIMEOUT": API request timeout`) + ew.writeln(` - "ALL_INKL_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "ALL_INKL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/allinkl`) + case "arvancloud": // generated from: providers/dns/arvancloud/arvancloud.toml ew.writeln(`Configuration for ArvanCloud.`) diff --git a/docs/content/dns/zz_gen_allinkl.md b/docs/content/dns/zz_gen_allinkl.md new file mode 100644 index 00000000..443bcb41 --- /dev/null +++ b/docs/content/dns/zz_gen_allinkl.md @@ -0,0 +1,63 @@ +--- +title: "all-inkl" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: allinkl +--- + + + + + +Since: v4.5.0 + +Configuration for [all-inkl](https://all-inkl.com). + + + + +- Code: `allinkl` + +Here is an example bash command using the all-inkl provider: + +```bash +ALL_INKL_LOGIN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ +ALL_INKL_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ +lego --email myemail@example.com --dns allinkl --domains my.example.org run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ALL_INKL_API_KEY` | API login | +| `ALL_INKL_PASSWORD` | API password | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here](/lego/dns/#configuration-and-credentials). + + +## Additional Configuration + +| Environment Variable Name | Description | +|--------------------------------|-------------| +| `ALL_INKL_HTTP_TIMEOUT` | API request timeout | +| `ALL_INKL_POLLING_INTERVAL` | Time between DNS propagation check | +| `ALL_INKL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here](/lego/dns/#configuration-and-credentials). + + + + +## More information + +- [API documentation](https://kasapi.kasserver.com/dokumentation/phpdoc/index.html) + + + + diff --git a/go.mod b/go.mod index f5ee7668..ccc94770 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/linode/linodego v0.25.3 github.com/liquidweb/liquidweb-go v1.6.3 github.com/miekg/dns v1.1.40 + github.com/mitchellh/mapstructure v1.4.1 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 github.com/nrdcg/auroradns v1.0.1 github.com/nrdcg/desec v0.5.0 diff --git a/go.sum b/go.sum index c442fa2c..97a68ff7 100644 --- a/go.sum +++ b/go.sum @@ -324,8 +324,9 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/providers/dns/allinkl/allinkl.go b/providers/dns/allinkl/allinkl.go new file mode 100644 index 00000000..a8807b32 --- /dev/null +++ b/providers/dns/allinkl/allinkl.go @@ -0,0 +1,161 @@ +// Package allinkl implements a DNS provider for solving the DNS-01 challenge using all-inkl. +package allinkl + +import ( + "errors" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/allinkl/internal" +) + +// Environment variables names. +const ( + envNamespace = "ALL_INKL_" + + EnvLogin = envNamespace + "LOGIN" + EnvPassword = envNamespace + "PASSWORD" + + 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 { + Login string + Password string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + recordIDs map[string]string + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for all-inkl. +// Credentials must be passed in the environment variable: ALL_INKL_API_KEY, ALL_INKL_PASSWORD. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvLogin, EnvPassword) + if err != nil { + return nil, fmt.Errorf("allinkl: %w", err) + } + + config := NewDefaultConfig() + config.Login = values[EnvLogin] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for all-inkl. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("allinkl: the configuration of the DNS provider is nil") + } + + if config.Login == "" || config.Password == "" { + return nil, errors.New("allinkl: missing credentials") + } + + client := internal.NewClient(config.Login, config.Password) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]string), + }, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to cope with spikes in propagation times. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return fmt.Errorf("allinkl: could not determine zone for domain %q: %w", domain, err) + } + + credential, err := d.client.Authentication(60, true) + if err != nil { + return fmt.Errorf("allinkl: %w", err) + } + + subDomain := dns01.UnFqdn(strings.TrimSuffix(fqdn, authZone)) + + record := internal.DNSRequest{ + ZoneHost: authZone, + RecordType: "TXT", + RecordName: subDomain, + RecordData: value, + } + + recordID, err := d.client.AddDNSSettings(credential, record) + if err != nil { + return fmt.Errorf("allinkl: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = recordID + d.recordIDsMu.Unlock() + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, _ := dns01.GetRecord(domain, keyAuth) + + credential, err := d.client.Authentication(60, true) + if err != nil { + return fmt.Errorf("allinkl: %w", err) + } + + // gets the record's unique ID from when we created it + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + if !ok { + return fmt.Errorf("allinkl: unknown record ID for '%s' '%s'", fqdn, token) + } + + _, err = d.client.DeleteDNSSettings(credential, recordID) + if err != nil { + return fmt.Errorf("allinkl: %w", err) + } + + return nil +} diff --git a/providers/dns/allinkl/allinkl.toml b/providers/dns/allinkl/allinkl.toml new file mode 100644 index 00000000..ea1aa636 --- /dev/null +++ b/providers/dns/allinkl/allinkl.toml @@ -0,0 +1,24 @@ +Name = "all-inkl" +Description = '''''' +URL = "https://all-inkl.com" +Code = "allinkl" +Since = "v4.5.0" + +Example = ''' +ALL_INKL_LOGIN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ +ALL_INKL_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ +lego --email myemail@example.com --dns allinkl --domains my.example.org run +''' + +[Configuration] + [Configuration.Credentials] + ALL_INKL_API_KEY = "API login" + ALL_INKL_PASSWORD = "API password" + [Configuration.Additional] + ALL_INKL_POLLING_INTERVAL = "Time between DNS propagation check" + ALL_INKL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + ALL_INKL_HTTP_TIMEOUT = "API request timeout" + +[Links] + API = "https://kasapi.kasserver.com/dokumentation/phpdoc/index.html" + Guide = "https://kasapi.kasserver.com/dokumentation/" diff --git a/providers/dns/allinkl/allinkl_test.go b/providers/dns/allinkl/allinkl_test.go new file mode 100644 index 00000000..af85f8c5 --- /dev/null +++ b/providers/dns/allinkl/allinkl_test.go @@ -0,0 +1,142 @@ +package allinkl + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvLogin, EnvPassword).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvLogin: "user", + EnvPassword: "secret", + }, + }, + { + desc: "missing credentials: account name", + envVars: map[string]string{ + EnvLogin: "", + EnvPassword: "secret", + }, + expected: "allinkl: some credentials information are missing: ALL_INKL_LOGIN", + }, + { + desc: "missing credentials: api key", + envVars: map[string]string{ + EnvLogin: "user", + EnvPassword: "", + }, + expected: "allinkl: some credentials information are missing: ALL_INKL_PASSWORD", + }, + { + desc: "missing credentials: all", + envVars: map[string]string{ + EnvLogin: "", + EnvPassword: "", + }, + expected: "allinkl: some credentials information are missing: ALL_INKL_LOGIN,ALL_INKL_PASSWORD", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer envTest.RestoreEnv() + envTest.ClearEnv() + + envTest.Apply(test.envVars) + + p, err := NewDNSProvider() + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + require.NotNil(t, p.client) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + login string + password string + expected string + }{ + { + desc: "success", + login: "user", + password: "secret", + }, + { + desc: "missing account name", + password: "secret", + expected: "allinkl: missing credentials", + }, + { + desc: "missing api key", + login: "user", + expected: "allinkl: missing credentials", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Login = test.login + config.Password = test.password + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + require.NotNil(t, p.client) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestLivePresent(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.Present(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} + +func TestLiveCleanUp(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/allinkl/internal/client.go b/providers/dns/allinkl/internal/client.go new file mode 100644 index 00000000..37d9eaef --- /dev/null +++ b/providers/dns/allinkl/internal/client.go @@ -0,0 +1,286 @@ +package internal + +import ( + "bytes" + "crypto/sha1" + "encoding/json" + "encoding/xml" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "strings" + "time" + + "github.com/mitchellh/mapstructure" +) + +const ( + authEndpoint = "https://kasapi.kasserver.com/soap/KasAuth.php" + apiEndpoint = "https://kasapi.kasserver.com/soap/KasApi.php" +) + +// Client a KAS server client. +type Client struct { + login string + password string + + authEndpoint string + apiEndpoint string + HTTPClient *http.Client + floodTime time.Time +} + +// NewClient creates a new Client. +func NewClient(login string, password string) *Client { + return &Client{ + login: login, + password: password, + authEndpoint: authEndpoint, + apiEndpoint: apiEndpoint, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + } +} + +// Authentication Creates a credential token. +// - sessionLifetime: Validity of the token in seconds. +// - sessionUpdateLifetime: with `true` the session is extended with every request. +func (c Client) Authentication(sessionLifetime int, sessionUpdateLifetime bool) (string, error) { + hash := sha1.New() + hash.Write([]byte(c.password)) + + sul := "N" + if sessionUpdateLifetime { + sul = "Y" + } + + ar := AuthRequest{ + Login: c.login, + AuthData: fmt.Sprintf("%x", hash.Sum(nil)), + AuthType: "sha1", + SessionLifetime: sessionLifetime, + SessionUpdateLifetime: sul, + } + + body, err := json.Marshal(ar) + if err != nil { + return "", fmt.Errorf("request marshal: %w", err) + } + + payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAuthEnvelope, body))) + + req, err := http.NewRequest(http.MethodPost, c.authEndpoint, bytes.NewReader(payload)) + if err != nil { + return "", fmt.Errorf("request creation: %w", err) + } + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return "", fmt.Errorf("request execution: %w", err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + data, _ := ioutil.ReadAll(resp.Body) + return "", fmt.Errorf("invalid status code: %d %s", resp.StatusCode, string(data)) + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("response read: %w", err) + } + + var e KasAuthEnvelope + decoder := xml.NewTokenDecoder(Trimmer{decoder: xml.NewDecoder(bytes.NewReader(data))}) + err = decoder.Decode(&e) + if err != nil { + return "", fmt.Errorf("response xml decode: %w", err) + } + + if e.Body.Fault != nil { + return "", e.Body.Fault + } + + return e.Body.KasAuthResponse.Return.Text, nil +} + +// GetDNSSettings Reading out the DNS settings of a zone. +// - zone: host zone. +// - recordID: the ID of the resource record (optional). +func (c *Client) GetDNSSettings(credentialToken, zone, recordID string) ([]ReturnInfo, error) { + requestParams := map[string]string{"zone_host": zone} + + if recordID != "" { + requestParams["record_id"] = recordID + } + + item, err := c.do(credentialToken, "get_dns_settings", requestParams) + if err != nil { + return nil, err + } + + raw := getValue(item) + + var g GetDNSSettingsAPIResponse + err = mapstructure.Decode(raw, &g) + if err != nil { + return nil, fmt.Errorf("response struct decode: %w", err) + } + + c.updateFloodTime(g.Response.KasFloodDelay) + + return g.Response.ReturnInfo, nil +} + +// AddDNSSettings Creation of a DNS resource record. +func (c *Client) AddDNSSettings(credentialToken string, record DNSRequest) (string, error) { + item, err := c.do(credentialToken, "add_dns_settings", record) + if err != nil { + return "", err + } + + raw := getValue(item) + + var g AddDNSSettingsAPIResponse + err = mapstructure.Decode(raw, &g) + if err != nil { + return "", fmt.Errorf("response struct decode: %w", err) + } + + c.updateFloodTime(g.Response.KasFloodDelay) + + return g.Response.ReturnInfo, nil +} + +// DeleteDNSSettings Deleting a DNS Resource Record. +func (c *Client) DeleteDNSSettings(credentialToken, recordID string) (bool, error) { + requestParams := map[string]string{"record_id": recordID} + + item, err := c.do(credentialToken, "delete_dns_settings", requestParams) + if err != nil { + return false, err + } + + raw := getValue(item) + + var g DeleteDNSSettingsAPIResponse + err = mapstructure.Decode(raw, &g) + if err != nil { + return false, fmt.Errorf("response struct decode: %w", err) + } + + c.updateFloodTime(g.Response.KasFloodDelay) + + return g.Response.ReturnInfo, nil +} + +func (c Client) do(credentialToken, action string, requestParams interface{}) (*Item, error) { + time.Sleep(time.Until(c.floodTime)) + + ar := KasRequest{ + Login: c.login, + AuthType: "session", + AuthData: credentialToken, + Action: action, + RequestParams: requestParams, + } + + body, err := json.Marshal(ar) + if err != nil { + return nil, fmt.Errorf("request marshal: %w", err) + } + + payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAPIEnvelope, body))) + + req, err := http.NewRequest(http.MethodPost, c.apiEndpoint, bytes.NewReader(payload)) + if err != nil { + return nil, fmt.Errorf("request creation: %w", err) + } + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request execution: %w", err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + data, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("invalid status code: %d %s", resp.StatusCode, string(data)) + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("response read: %w", err) + } + + var e KasAPIResponseEnvelope + decoder := xml.NewTokenDecoder(Trimmer{decoder: xml.NewDecoder(bytes.NewReader(data))}) + err = decoder.Decode(&e) + if err != nil { + return nil, fmt.Errorf("response xml decode: %w", err) + } + + if e.Body.Fault != nil { + return nil, e.Body.Fault + } + + return e.Body.KasAPIResponse.Return, nil +} + +func (c *Client) updateFloodTime(delay float64) { + c.floodTime = time.Now().Add(time.Duration(delay * float64(time.Second))) +} + +func getValue(item *Item) interface{} { + switch { + case item.Raw != "": + v, _ := strconv.ParseBool(item.Raw) + return v + + case item.Text != "": + switch item.Type { + case "xsd:string": + return item.Text + case "xsd:float": + v, _ := strconv.ParseFloat(item.Text, 64) + return v + case "xsd:int": + v, _ := strconv.ParseInt(item.Text, 10, 64) + return v + default: + return item.Text + } + + case item.Value != nil: + return getValue(item.Value) + + case len(item.Items) > 0 && item.Type == "SOAP-ENC:Array": + var v []interface{} + for _, i := range item.Items { + v = append(v, getValue(i)) + } + + return v + + case len(item.Items) > 0: + v := map[string]interface{}{} + for _, i := range item.Items { + v[getKey(i)] = getValue(i) + } + + return v + + default: + return "" + } +} + +func getKey(item *Item) string { + if item.Key == nil { + return "" + } + + return item.Key.Text +} diff --git a/providers/dns/allinkl/internal/client_test.go b/providers/dns/allinkl/internal/client_test.go new file mode 100644 index 00000000..e2b51d1e --- /dev/null +++ b/providers/dns/allinkl/internal/client_test.go @@ -0,0 +1,194 @@ +package internal + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClient_Authentication(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc("/", testHandler("auth.xml")) + + client := NewClient("user", "secret") + client.authEndpoint = server.URL + + credentialToken, err := client.Authentication(60, false) + require.NoError(t, err) + + assert.Equal(t, "593959ca04f0de9689b586c6a647d15d", credentialToken) +} + +func TestClient_Authentication_error(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc("/", testHandler("auth_fault.xml")) + + client := NewClient("user", "secret") + client.authEndpoint = server.URL + + _, err := client.Authentication(60, false) + require.Error(t, err) +} + +func TestClient_GetDNSSettings(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc("/", testHandler("get_dns_settings.xml")) + + client := NewClient("user", "secret") + client.apiEndpoint = server.URL + + token := "sha1secret" + + records, err := client.GetDNSSettings(token, "example.com", "") + require.NoError(t, err) + + expected := []ReturnInfo{ + { + ID: "57297429", + Zone: "example.org", + Name: "", + Type: "A", + Data: "10.0.0.1", + Changeable: "Y", + Aux: 0, + }, + { + ID: int64(0), + Zone: "example.org", + Name: "", + Type: "NS", + Data: "ns5.kasserver.com.", + Changeable: "N", + Aux: 0, + }, + { + ID: int64(0), + Zone: "example.org", + Name: "", + Type: "NS", + Data: "ns6.kasserver.com.", + Changeable: "N", + Aux: 0, + }, + { + ID: "57297479", + Zone: "example.org", + Name: "*", + Type: "A", + Data: "10.0.0.1", + Changeable: "Y", + Aux: 0, + }, + { + ID: "57297481", + Zone: "example.org", + Name: "", + Type: "MX", + Data: "user.kasserver.com.", + Changeable: "Y", + Aux: 10, + }, + { + ID: "57297483", + Zone: "example.org", + Name: "", + Type: "TXT", + Data: "v=spf1 mx a ?all", + Changeable: "Y", + Aux: 0, + }, + { + ID: "57297485", + Zone: "example.org", + Name: "_dmarc", + Type: "TXT", + Data: "v=DMARC1; p=none;", + Changeable: "Y", + Aux: 0, + }, + } + + assert.Equal(t, expected, records) +} + +func TestClient_AddDNSSettings(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc("/", testHandler("add_dns_settings.xml")) + + client := NewClient("user", "secret") + client.apiEndpoint = server.URL + + token := "sha1secret" + + record := DNSRequest{ + ZoneHost: "42cnc.de.", + RecordType: "TXT", + RecordName: "lego", + RecordData: "abcdefgh", + } + + recordID, err := client.AddDNSSettings(token, record) + require.NoError(t, err) + + assert.Equal(t, "57347444", recordID) +} + +func TestClient_DeleteDNSSettings(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc("/", testHandler("delete_dns_settings.xml")) + + client := NewClient("user", "secret") + client.apiEndpoint = server.URL + + token := "sha1secret" + + r, err := client.DeleteDNSSettings(token, "57347450") + require.NoError(t, err) + + assert.True(t, r) +} + +func testHandler(filename string) http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + } +} diff --git a/providers/dns/allinkl/internal/fixtures/add_dns_settings.json b/providers/dns/allinkl/internal/fixtures/add_dns_settings.json new file mode 100644 index 00000000..9d5cd65f --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/add_dns_settings.json @@ -0,0 +1,18 @@ +{ + "Request": { + "KasRequestParams": { + "record_aux": 0, + "record_data": "abcdefgh", + "record_name": "lego", + "record_type": "TXT", + "zone_host": "example.org." + }, + "KasRequestTime": 1625014992, + "KasRequestType": true + }, + "Response": { + "KasFloodDelay": 0.5, + "ReturnInfo": "57347444", + "ReturnString": "TRUE" + } +} diff --git a/providers/dns/allinkl/internal/fixtures/add_dns_settings.xml b/providers/dns/allinkl/internal/fixtures/add_dns_settings.xml new file mode 100644 index 00000000..8fa3a420 --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/add_dns_settings.xml @@ -0,0 +1,68 @@ + + + + + + + Request + + + KasRequestTime + 1625014992 + + + KasRequestType + + + + KasRequestParams + + + zone_host + example.org. + + + record_type + TXT + + + record_name + lego + + + record_data + abcdefgh + + + record_aux + 0 + + + + + + + Response + + + KasFloodDelay + 0.5 + + + ReturnString + TRUE + + + ReturnInfo + 57347444 + + + + + + + diff --git a/providers/dns/allinkl/internal/fixtures/auth.xml b/providers/dns/allinkl/internal/fixtures/auth.xml new file mode 100644 index 00000000..349b544e --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/auth.xml @@ -0,0 +1,11 @@ + + + + + 593959ca04f0de9689b586c6a647d15d + + + diff --git a/providers/dns/allinkl/internal/fixtures/auth_fault.xml b/providers/dns/allinkl/internal/fixtures/auth_fault.xml new file mode 100644 index 00000000..a945063d --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/auth_fault.xml @@ -0,0 +1,10 @@ + + + + + SOAP-ENV:Client + kas_login_syntax_incorrect + KasAuth + + + diff --git a/providers/dns/allinkl/internal/fixtures/delete_dns_settings.json b/providers/dns/allinkl/internal/fixtures/delete_dns_settings.json new file mode 100644 index 00000000..7ac41f18 --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/delete_dns_settings.json @@ -0,0 +1,14 @@ +{ + "Request": { + "KasRequestParams": { + "record_id": "57347444" + }, + "KasRequestTime": 1625016066, + "KasRequestType": true + }, + "Response": { + "KasFloodDelay": 0.5, + "ReturnInfo": true, + "ReturnString": "TRUE" + } +} diff --git a/providers/dns/allinkl/internal/fixtures/delete_dns_settings.xml b/providers/dns/allinkl/internal/fixtures/delete_dns_settings.xml new file mode 100644 index 00000000..42f52e3f --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/delete_dns_settings.xml @@ -0,0 +1,52 @@ + + + + + + + Request + + + KasRequestTime + 1625016066 + + + KasRequestType + + + + KasRequestParams + + + record_id + 57347444 + + + + + + + Response + + + KasFloodDelay + 0.5 + + + ReturnString + TRUE + + + ReturnInfo + + + + + + + + diff --git a/providers/dns/allinkl/internal/fixtures/get_dns_settings.json b/providers/dns/allinkl/internal/fixtures/get_dns_settings.json new file mode 100644 index 00000000..724eb32f --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/get_dns_settings.json @@ -0,0 +1,78 @@ +{ + "Request": { + "KasRequestParams": { + "zone_host": "example.org" + }, + "KasRequestTime": 1625012975, + "KasRequestType": true + }, + "Response": { + "KasFloodDelay": 0.5, + "ReturnInfo": [ + { + "record_aux": 0, + "record_changeable": "Y", + "record_data": "10.0.0.1", + "record_id": "57297429", + "record_name": "", + "record_type": "A", + "record_zone": "example.org" + }, + { + "record_aux": 0, + "record_changeable": "N", + "record_data": "ns5.kasserver.com.", + "record_id": 0, + "record_name": "", + "record_type": "NS", + "record_zone": "example.org" + }, + { + "record_aux": 0, + "record_changeable": "N", + "record_data": "ns6.kasserver.com.", + "record_id": 0, + "record_name": "", + "record_type": "NS", + "record_zone": "example.org" + }, + { + "record_aux": 0, + "record_changeable": "Y", + "record_data": "10.0.0.1", + "record_id": "57297479", + "record_name": "*", + "record_type": "A", + "record_zone": "example.org" + }, + { + "record_aux": 10, + "record_changeable": "Y", + "record_data": "user.kasserver.com.", + "record_id": "57297481", + "record_name": "", + "record_type": "MX", + "record_zone": "example.org" + }, + { + "record_aux": 0, + "record_changeable": "Y", + "record_data": "v=spf1 mx a ?all", + "record_id": "57297483", + "record_name": "", + "record_type": "TXT", + "record_zone": "example.org" + }, + { + "record_aux": 0, + "record_changeable": "Y", + "record_data": "v=DMARC1; p=none;", + "record_id": "57297485", + "record_name": "_dmarc", + "record_type": "TXT", + "record_zone": "example.org" + } + ], + "ReturnString": "TRUE" + } +} diff --git a/providers/dns/allinkl/internal/fixtures/get_dns_settings.xml b/providers/dns/allinkl/internal/fixtures/get_dns_settings.xml new file mode 100644 index 00000000..6bdb0095 --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/get_dns_settings.xml @@ -0,0 +1,263 @@ + + + + + + + Request + + + KasRequestTime + 1624993260 + + + KasRequestType + + + + KasRequestParams + + + zone_host + example.org + + + + + + + Response + + + KasFloodDelay + 0.5 + + + ReturnString + TRUE + + + ReturnInfo + + + + record_zone + example.org + + + record_name + + + + record_type + A + + + record_data + 10.0.0.1 + + + record_aux + 0 + + + record_id + 57297429 + + + record_changeable + Y + + + + + record_zone + example.org + + + record_name + + + + record_type + NS + + + record_data + ns5.kasserver.com. + + + record_aux + 0 + + + record_id + 0 + + + record_changeable + N + + + + + record_zone + example.org + + + record_name + + + + record_type + NS + + + record_data + ns6.kasserver.com. + + + record_aux + 0 + + + record_id + 0 + + + record_changeable + N + + + + + record_zone + example.org + + + record_name + * + + + record_type + A + + + record_data + 10.0.0.1 + + + record_aux + 0 + + + record_id + 57297479 + + + record_changeable + Y + + + + + record_zone + example.org + + + record_name + + + + record_type + MX + + + record_data + user.kasserver.com. + + + record_aux + 10 + + + record_id + 57297481 + + + record_changeable + Y + + + + + record_zone + example.org + + + record_name + + + + record_type + TXT + + + record_data + v=spf1 mx a ?all + + + record_aux + 0 + + + record_id + 57297483 + + + record_changeable + Y + + + + + record_zone + example.org + + + record_name + _dmarc + + + record_type + TXT + + + record_data + v=DMARC1; p=none; + + + record_aux + 0 + + + record_id + 57297485 + + + record_changeable + Y + + + + + + + + + + diff --git a/providers/dns/allinkl/internal/types.go b/providers/dns/allinkl/internal/types.go new file mode 100644 index 00000000..ac2ddd39 --- /dev/null +++ b/providers/dns/allinkl/internal/types.go @@ -0,0 +1,46 @@ +package internal + +import ( + "bytes" + "encoding/xml" + "fmt" +) + +// Trimmer trim all XML fields. +type Trimmer struct { + decoder *xml.Decoder +} + +func (tr Trimmer) Token() (xml.Token, error) { + t, err := tr.decoder.Token() + if cd, ok := t.(xml.CharData); ok { + t = xml.CharData(bytes.TrimSpace(cd)) + } + return t, err +} + +// Fault a SOAP fault. +type Fault struct { + Code string `xml:"faultcode"` + Message string `xml:"faultstring"` + Actor string `xml:"faultactor"` +} + +func (f Fault) Error() string { + return fmt.Sprintf("%s: %s: %s", f.Actor, f.Code, f.Message) +} + +// KasResponse a KAS SOAP response. +type KasResponse struct { + Return *Item `xml:"return"` +} + +// Item an item of the KAS SOAP response. +type Item struct { + Text string `xml:",chardata" json:"text,omitempty"` + Type string `xml:"type,attr" json:"type,omitempty"` + Raw string `xml:"nil,attr" json:"raw,omitempty"` + Key *Item `xml:"key" json:"key,omitempty"` + Value *Item `xml:"value" json:"value,omitempty"` + Items []*Item `xml:"item" json:"item,omitempty"` +} diff --git a/providers/dns/allinkl/internal/types_api.go b/providers/dns/allinkl/internal/types_api.go new file mode 100644 index 00000000..49db25a3 --- /dev/null +++ b/providers/dns/allinkl/internal/types_api.go @@ -0,0 +1,94 @@ +package internal + +import "encoding/xml" + +// kasAPIEnvelope a KAS API request envelope. +const kasAPIEnvelope = ` + + + + %s + + +` + +// KasAPIResponseEnvelope a KAS envelope of the API response. +type KasAPIResponseEnvelope struct { + XMLName xml.Name `xml:"Envelope"` + Body KasAPIBody `xml:"Body"` +} + +type KasAPIBody struct { + KasAPIResponse *KasResponse `xml:"KasApiResponse"` + Fault *Fault `xml:"Fault"` +} + +// --- + +type KasRequest struct { + // Login the relevant KAS login. + Login string `json:"kas_login,omitempty"` + // AuthType the authentication type. + AuthType string `json:"kas_auth_type,omitempty"` + // AuthData the authentication data. + AuthData string `json:"kas_auth_data,omitempty"` + // Action API function. + Action string `json:"kas_action,omitempty"` + // RequestParams Parameters to the API function. + RequestParams interface{} `json:"KasRequestParams,omitempty"` +} + +type DNSRequest struct { + // ZoneHost the zone in question (must be a FQDN). + ZoneHost string `json:"zone_host"` + // RecordType the TYPE of the resource record (MX, A, AAAA etc.). + RecordType string `json:"record_type"` + // RecordName the NAME of the resource record. + RecordName string `json:"record_name"` + // RecordData the DATA of the resource record. + RecordData string `json:"record_data"` + // RecordAux the AUX of the resource record. + RecordAux int `json:"record_aux"` +} + +// --- + +type GetDNSSettingsAPIResponse struct { + Response GetDNSSettingsResponse `json:"Response" mapstructure:"Response"` +} + +type GetDNSSettingsResponse struct { + KasFloodDelay float64 `json:"KasFloodDelay" mapstructure:"KasFloodDelay"` + ReturnInfo []ReturnInfo `json:"ReturnInfo" mapstructure:"ReturnInfo"` + ReturnString string `json:"ReturnString"` +} + +type ReturnInfo struct { + ID interface{} `json:"record_id,omitempty" mapstructure:"record_id"` + Zone string `json:"record_zone,omitempty" mapstructure:"record_zone"` + Name string `json:"record_name,omitempty" mapstructure:"record_name"` + Type string `json:"record_type,omitempty" mapstructure:"record_type"` + Data string `json:"record_data,omitempty" mapstructure:"record_data"` + Changeable string `json:"record_changeable,omitempty" mapstructure:"record_changeable"` + Aux int `json:"record_aux,omitempty" mapstructure:"record_aux"` +} + +type AddDNSSettingsAPIResponse struct { + Response AddDNSSettingsResponse `json:"Response" mapstructure:"Response"` +} + +type AddDNSSettingsResponse struct { + KasFloodDelay float64 `json:"KasFloodDelay" mapstructure:"KasFloodDelay"` + ReturnInfo string `json:"ReturnInfo" mapstructure:"ReturnInfo"` + ReturnString string `json:"ReturnString" mapstructure:"ReturnString"` +} + +type DeleteDNSSettingsAPIResponse struct { + Response DeleteDNSSettingsResponse `json:"Response"` +} + +type DeleteDNSSettingsResponse struct { + KasFloodDelay float64 `json:"KasFloodDelay"` + ReturnInfo bool `json:"ReturnInfo"` + ReturnString string `json:"ReturnString"` +} diff --git a/providers/dns/allinkl/internal/types_auth.go b/providers/dns/allinkl/internal/types_auth.go new file mode 100644 index 00000000..181f76df --- /dev/null +++ b/providers/dns/allinkl/internal/types_auth.go @@ -0,0 +1,34 @@ +package internal + +import "encoding/xml" + +// kasAuthEnvelope a KAS authentication request envelope. +const kasAuthEnvelope = ` + + + + %s + + +` + +// KasAuthEnvelope a KAS envelope of the authentication response. +type KasAuthEnvelope struct { + XMLName xml.Name `xml:"Envelope"` + Body KasAuthBody `xml:"Body"` +} + +type KasAuthBody struct { + KasAuthResponse *KasResponse `xml:"KasAuthResponse"` + Fault *Fault `xml:"Fault"` +} + +// --- + +type AuthRequest struct { + Login string `json:"kas_login,omitempty"` + AuthData string `json:"kas_auth_data,omitempty"` + AuthType string `json:"kas_auth_type,omitempty"` + SessionLifetime int `json:"session_lifetime,omitempty"` + SessionUpdateLifetime string `json:"session_update_lifetime,omitempty"` +} diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index bd36f4be..b9ef5f33 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -7,6 +7,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/providers/dns/acmedns" "github.com/go-acme/lego/v4/providers/dns/alidns" + "github.com/go-acme/lego/v4/providers/dns/allinkl" "github.com/go-acme/lego/v4/providers/dns/arvancloud" "github.com/go-acme/lego/v4/providers/dns/auroradns" "github.com/go-acme/lego/v4/providers/dns/autodns" @@ -104,6 +105,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return acmedns.NewDNSProvider() case "alidns": return alidns.NewDNSProvider() + case "allinkl": + return allinkl.NewDNSProvider() case "arvancloud": return arvancloud.NewDNSProvider() case "azure":