Add DNS provider for all-inkl (#1444)

This commit is contained in:
Ludovic Fernandez 2021-06-30 22:49:02 +02:00 committed by GitHub
parent 2a194d6ab9
commit 43779d7533
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1608 additions and 24 deletions

View file

@ -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/) |
<!-- END DNS PROVIDERS LIST -->

View file

@ -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.`)

View file

@ -0,0 +1,63 @@
---
title: "all-inkl"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: allinkl
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/allinkl/allinkl.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Since: v4.5.0
Configuration for [all-inkl](https://all-inkl.com).
<!--more-->
- 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)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/allinkl/allinkl.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

1
go.mod
View file

@ -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

3
go.sum
View file

@ -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=

View file

@ -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
}

View file

@ -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/"

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}
}
}

View file

@ -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"
}
}

View file

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="https://kasapi.kasserver.com/soap/KasApi.php"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ns2="http://xml.apache.org/xml-soap"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:KasApiResponse>
<return xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">Request</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">KasRequestTime</key>
<value xsi:type="xsd:int">1625014992</value>
</item>
<item>
<key xsi:type="xsd:string">KasRequestType</key>
<value xsi:nil="true"/>
</item>
<item>
<key xsi:type="xsd:string">KasRequestParams</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">zone_host</key>
<value xsi:type="xsd:string">example.org.</value>
</item>
<item>
<key xsi:type="xsd:string">record_type</key>
<value xsi:type="xsd:string">TXT</value>
</item>
<item>
<key xsi:type="xsd:string">record_name</key>
<value xsi:type="xsd:string">lego</value>
</item>
<item>
<key xsi:type="xsd:string">record_data</key>
<value xsi:type="xsd:string">abcdefgh</value>
</item>
<item>
<key xsi:type="xsd:string">record_aux</key>
<value xsi:type="xsd:int">0</value>
</item>
</value>
</item>
</value>
</item>
<item>
<key xsi:type="xsd:string">Response</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">KasFloodDelay</key>
<value xsi:type="xsd:float">0.5</value>
</item>
<item>
<key xsi:type="xsd:string">ReturnString</key>
<value xsi:type="xsd:string">TRUE</value>
</item>
<item>
<key xsi:type="xsd:string">ReturnInfo</key>
<value xsi:type="xsd:string">57347444</value>
</item>
</value>
</item>
</return>
</ns1:KasApiResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="https://kasapi.kasserver.com/soap/KasAuth.php"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:KasAuthResponse>
<return xsi:type="xsd:string">593959ca04f0de9689b586c6a647d15d</return>
</ns1:KasAuthResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring>kas_login_syntax_incorrect</faultstring>
<faultactor>KasAuth</faultactor>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

View file

@ -0,0 +1,14 @@
{
"Request": {
"KasRequestParams": {
"record_id": "57347444"
},
"KasRequestTime": 1625016066,
"KasRequestType": true
},
"Response": {
"KasFloodDelay": 0.5,
"ReturnInfo": true,
"ReturnString": "TRUE"
}
}

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="https://kasapi.kasserver.com/soap/KasApi.php"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ns2="http://xml.apache.org/xml-soap"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:KasApiResponse>
<return xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">Request</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">KasRequestTime</key>
<value xsi:type="xsd:int">1625016066</value>
</item>
<item>
<key xsi:type="xsd:string">KasRequestType</key>
<value xsi:nil="true"/>
</item>
<item>
<key xsi:type="xsd:string">KasRequestParams</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:string">57347444</value>
</item>
</value>
</item>
</value>
</item>
<item>
<key xsi:type="xsd:string">Response</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">KasFloodDelay</key>
<value xsi:type="xsd:float">0.5</value>
</item>
<item>
<key xsi:type="xsd:string">ReturnString</key>
<value xsi:type="xsd:string">TRUE</value>
</item>
<item>
<key xsi:type="xsd:string">ReturnInfo</key>
<value xsi:nil="true"/>
</item>
</value>
</item>
</return>
</ns1:KasApiResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

View file

@ -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"
}
}

View file

@ -0,0 +1,263 @@
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="https://kasapi.kasserver.com/soap/KasApi.php"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:KasApiResponse>
<return xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">Request</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">KasRequestTime</key>
<value xsi:type="xsd:int">1624993260</value>
</item>
<item>
<key xsi:type="xsd:string">KasRequestType</key>
<value xsi:nil="true"/>
</item>
<item>
<key xsi:type="xsd:string">KasRequestParams</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">zone_host</key>
<value xsi:type="xsd:string">example.org</value>
</item>
</value>
</item>
</value>
</item>
<item>
<key xsi:type="xsd:string">Response</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">KasFloodDelay</key>
<value xsi:type="xsd:float">0.5</value>
</item>
<item>
<key xsi:type="xsd:string">ReturnString</key>
<value xsi:type="xsd:string">TRUE</value>
</item>
<item>
<key xsi:type="xsd:string">ReturnInfo</key>
<value SOAP-ENC:arrayType="ns2:Map[7]" xsi:type="SOAP-ENC:Array">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">record_zone</key>
<value xsi:type="xsd:string">example.org</value>
</item>
<item>
<key xsi:type="xsd:string">record_name</key>
<value xsi:type="xsd:string"></value>
</item>
<item>
<key xsi:type="xsd:string">record_type</key>
<value xsi:type="xsd:string">A</value>
</item>
<item>
<key xsi:type="xsd:string">record_data</key>
<value xsi:type="xsd:string">10.0.0.1</value>
</item>
<item>
<key xsi:type="xsd:string">record_aux</key>
<value xsi:type="xsd:int">0</value>
</item>
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:string">57297429</value>
</item>
<item>
<key xsi:type="xsd:string">record_changeable</key>
<value xsi:type="xsd:string">Y</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">record_zone</key>
<value xsi:type="xsd:string">example.org</value>
</item>
<item>
<key xsi:type="xsd:string">record_name</key>
<value xsi:type="xsd:string"></value>
</item>
<item>
<key xsi:type="xsd:string">record_type</key>
<value xsi:type="xsd:string">NS</value>
</item>
<item>
<key xsi:type="xsd:string">record_data</key>
<value xsi:type="xsd:string">ns5.kasserver.com.</value>
</item>
<item>
<key xsi:type="xsd:string">record_aux</key>
<value xsi:type="xsd:int">0</value>
</item>
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:int">0</value>
</item>
<item>
<key xsi:type="xsd:string">record_changeable</key>
<value xsi:type="xsd:string">N</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">record_zone</key>
<value xsi:type="xsd:string">example.org</value>
</item>
<item>
<key xsi:type="xsd:string">record_name</key>
<value xsi:type="xsd:string"></value>
</item>
<item>
<key xsi:type="xsd:string">record_type</key>
<value xsi:type="xsd:string">NS</value>
</item>
<item>
<key xsi:type="xsd:string">record_data</key>
<value xsi:type="xsd:string">ns6.kasserver.com.</value>
</item>
<item>
<key xsi:type="xsd:string">record_aux</key>
<value xsi:type="xsd:int">0</value>
</item>
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:int">0</value>
</item>
<item>
<key xsi:type="xsd:string">record_changeable</key>
<value xsi:type="xsd:string">N</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">record_zone</key>
<value xsi:type="xsd:string">example.org</value>
</item>
<item>
<key xsi:type="xsd:string">record_name</key>
<value xsi:type="xsd:string">*</value>
</item>
<item>
<key xsi:type="xsd:string">record_type</key>
<value xsi:type="xsd:string">A</value>
</item>
<item>
<key xsi:type="xsd:string">record_data</key>
<value xsi:type="xsd:string">10.0.0.1</value>
</item>
<item>
<key xsi:type="xsd:string">record_aux</key>
<value xsi:type="xsd:int">0</value>
</item>
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:string">57297479</value>
</item>
<item>
<key xsi:type="xsd:string">record_changeable</key>
<value xsi:type="xsd:string">Y</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">record_zone</key>
<value xsi:type="xsd:string">example.org</value>
</item>
<item>
<key xsi:type="xsd:string">record_name</key>
<value xsi:type="xsd:string"></value>
</item>
<item>
<key xsi:type="xsd:string">record_type</key>
<value xsi:type="xsd:string">MX</value>
</item>
<item>
<key xsi:type="xsd:string">record_data</key>
<value xsi:type="xsd:string">user.kasserver.com.</value>
</item>
<item>
<key xsi:type="xsd:string">record_aux</key>
<value xsi:type="xsd:int">10</value>
</item>
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:string">57297481</value>
</item>
<item>
<key xsi:type="xsd:string">record_changeable</key>
<value xsi:type="xsd:string">Y</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">record_zone</key>
<value xsi:type="xsd:string">example.org</value>
</item>
<item>
<key xsi:type="xsd:string">record_name</key>
<value xsi:type="xsd:string"></value>
</item>
<item>
<key xsi:type="xsd:string">record_type</key>
<value xsi:type="xsd:string">TXT</value>
</item>
<item>
<key xsi:type="xsd:string">record_data</key>
<value xsi:type="xsd:string">v=spf1 mx a ?all</value>
</item>
<item>
<key xsi:type="xsd:string">record_aux</key>
<value xsi:type="xsd:int">0</value>
</item>
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:string">57297483</value>
</item>
<item>
<key xsi:type="xsd:string">record_changeable</key>
<value xsi:type="xsd:string">Y</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">record_zone</key>
<value xsi:type="xsd:string">example.org</value>
</item>
<item>
<key xsi:type="xsd:string">record_name</key>
<value xsi:type="xsd:string">_dmarc</value>
</item>
<item>
<key xsi:type="xsd:string">record_type</key>
<value xsi:type="xsd:string">TXT</value>
</item>
<item>
<key xsi:type="xsd:string">record_data</key>
<value xsi:type="xsd:string">v=DMARC1; p=none;</value>
</item>
<item>
<key xsi:type="xsd:string">record_aux</key>
<value xsi:type="xsd:int">0</value>
</item>
<item>
<key xsi:type="xsd:string">record_id</key>
<value xsi:type="xsd:string">57297485</value>
</item>
<item>
<key xsi:type="xsd:string">record_changeable</key>
<value xsi:type="xsd:string">Y</value>
</item>
</item>
</value>
</item>
</value>
</item>
</return>
</ns1:KasApiResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

View file

@ -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"`
}

View file

@ -0,0 +1,94 @@
package internal
import "encoding/xml"
// kasAPIEnvelope a KAS API request envelope.
const kasAPIEnvelope = `
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<KasApi xmlns="https://kasserver.com/">
<Params>%s</Params>
</KasApi>
</Body>
</Envelope>`
// 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"`
}

View file

@ -0,0 +1,34 @@
package internal
import "encoding/xml"
// kasAuthEnvelope a KAS authentication request envelope.
const kasAuthEnvelope = `
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<KasAuth xmlns="https://kasserver.com/">
<Params>%s</Params>
</KasAuth>
</Body>
</Envelope>`
// 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"`
}

View file

@ -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":