diff --git a/README.md b/README.md index 1a8480b246..cb3f8ab2e7 100644 --- a/README.md +++ b/README.md @@ -62,212 +62,212 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Active24 Akamai EdgeDNS Alibaba Cloud DNS - all-inkl + Alibaba Cloud ESA + all-inkl Amazon Lightsail Amazon Route 53 Anexia CloudDNS - ArvanCloud + ArvanCloud Aurora DNS Autodns Axelname - Azion + Azion Azure (deprecated) Azure DNS Baidu Cloud - Beget.com + Beget.com Binary Lane Bindman Bluecat - BookMyName + BookMyName Brandit (deprecated) Bunny Checkdomain - Civo + Civo Cloud.ru CloudDNS Cloudflare - ClouDNS + ClouDNS CloudXNS (Deprecated) ConoHa v2 ConoHa v3 - Constellix + Constellix Core-Networks CPanel/WHM Derak Cloud - deSEC.io + deSEC.io Designate DNSaaS for Openstack Digital Ocean DirectAdmin - DNS Made Easy + DNS Made Easy dnsHome.de DNSimple DNSPod (deprecated) - Domain Offensive (do.de) + Domain Offensive (do.de) Domeneshop DreamHost Duck DNS - Dyn + Dyn DynDnsFree.de Dynu EasyDNS - Efficient IP + Efficient IP Epik Exoscale External program - F5 XC + F5 XC freemyip.com G-Core Gandi - Gandi Live DNS (v5) + Gandi Live DNS (v5) Glesys Go Daddy Google Cloud - Google Domains + Google Domains Hetzner Hosting.de Hostinger - Hosttech + Hosttech HTTP request http.net Huawei Cloud - Hurricane Electric DNS + Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service - Infoblox + Infoblox Infomaniak Internet Initiative Japan Internet.bs - INWX + INWX Ionos IPv64 iwantmyname (Deprecated) - Joker + Joker Joohoi's ACME-DNS KeyHelp Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Octenium Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Technitium + Technitium Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - webnames.ca + webnames.ca webnames.ru Websupport WEDOS - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index e21f37e63d..b5c4aaf4d9 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -16,6 +16,7 @@ func allDNSCodes() string { "acme-dns", "active24", "alidns", + "aliesa", "allinkl", "anexia", "arvancloud", @@ -251,6 +252,29 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/alidns`) + case "aliesa": + // generated from: providers/dns/aliesa/aliesa.toml + ew.writeln(`Configuration for Alibaba Cloud ESA.`) + ew.writeln(`Code: 'aliesa'`) + ew.writeln(`Since: 'v4.29.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ALIESA_ACCESS_KEY": Access key ID`) + ew.writeln(` - "ALIESA_RAM_ROLE": Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance)`) + ew.writeln(` - "ALIESA_SECRET_KEY": Access Key secret`) + ew.writeln(` - "ALIESA_SECURITY_TOKEN": STS Security Token (optional)`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ALIESA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ALIESA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ALIESA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "ALIESA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/aliesa`) + case "allinkl": // generated from: providers/dns/allinkl/allinkl.toml ew.writeln(`Configuration for all-inkl.`) diff --git a/docs/content/dns/zz_gen_aliesa.md b/docs/content/dns/zz_gen_aliesa.md new file mode 100644 index 0000000000..6f68adb54a --- /dev/null +++ b/docs/content/dns/zz_gen_aliesa.md @@ -0,0 +1,78 @@ +--- +title: "Alibaba Cloud ESA" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: aliesa +dnsprovider: + since: "v4.29.0" + code: "aliesa" + url: "https://www.alibabacloud.com/en/product/esa" +--- + + + + + + +Configuration for [Alibaba Cloud ESA](https://www.alibabacloud.com/en/product/esa). + + + + +- Code: `aliesa` +- Since: v4.29.0 + + +Here is an example bash command using the Alibaba Cloud ESA provider: + +```bash +# Setup using instance RAM role +ALIESA_RAM_ROLE=lego \ +lego --email you@example.com --dns aliesa -d '*.example.com' -d example.com run + +# Or, using credentials +ALIESA_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ +ALIESA_SECRET_KEY=your-secret-key \ +ALIESA_SECURITY_TOKEN=your-sts-token \ +lego --email you@example.com --dns aliesa - -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ALIESA_ACCESS_KEY` | Access key ID | +| `ALIESA_RAM_ROLE` | Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance) | +| `ALIESA_SECRET_KEY` | Access Key secret | +| `ALIESA_SECURITY_TOKEN` | STS Security Token (optional) | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here]({{% ref "dns#configuration-and-credentials" %}}). + + +## Additional Configuration + +| Environment Variable Name | Description | +|--------------------------------|-------------| +| `ALIESA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ALIESA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ALIESA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `ALIESA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here]({{% ref "dns#configuration-and-credentials" %}}). + + + + +## More information + +- [API documentation](https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-overview?spm=a2c63.p38356.help-menu-2673927.d_6_0_0.20b224c28PSZDc#:~:text=DNS-,DNS%20records,-DNS%20records) +- [Go client](https://github.com/alibabacloud-go/esa-20240910) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 80cbcaac07..5b1341e448 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/go.mod b/go.mod index 3dc72c1ffd..578c89ae95 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.27 github.com/go-acme/alidns-20150109/v4 v4.6.1 + github.com/go-acme/esa-20240910/v2 v2.39.4-0.20251105201639-2cc10a8ec341 github.com/go-acme/tencentclouddnspod v1.1.10 github.com/go-acme/tencentedgdeone v1.1.48 github.com/go-jose/go-jose/v4 v4.1.3 diff --git a/go.sum b/go.sum index a8d61029da..0e90a229f0 100644 --- a/go.sum +++ b/go.sum @@ -314,6 +314,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-acme/alidns-20150109/v4 v4.6.1 h1:Dch3aWRcw4U62+jKPjPQN3iW3TPvgIywATbvHzojXeo= github.com/go-acme/alidns-20150109/v4 v4.6.1/go.mod h1:RBcqBA5IvUWtlpjx6dC6EkPVyBNLQ+mR18XoaP38BFY= +github.com/go-acme/esa-20240910/v2 v2.39.4-0.20251105201639-2cc10a8ec341 h1:1PFxP0oYr2WuPuwFjGR2oxm0D4G2DBxMj8sJl7PX3xs= +github.com/go-acme/esa-20240910/v2 v2.39.4-0.20251105201639-2cc10a8ec341/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI= github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco= github.com/go-acme/tencentedgdeone v1.1.48 h1:WLyLBsRVhSLFmtbEFXk0naLODSQn7X6J0Fc/qR8xVUk= diff --git a/providers/dns/aliesa/aliesa.go b/providers/dns/aliesa/aliesa.go new file mode 100644 index 0000000000..deb8162da8 --- /dev/null +++ b/providers/dns/aliesa/aliesa.go @@ -0,0 +1,251 @@ +// Package aliesa implements a DNS provider for solving the DNS-01 challenge using AlibabaCloud ESA. +package aliesa + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/dara" + "github.com/aliyun/credentials-go/credentials" + esa "github.com/go-acme/esa-20240910/v2/client" + "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/internal/ptr" +) + +// Environment variables names. +const ( + envNamespace = "ALIESA_" + + EnvRAMRole = envNamespace + "RAM_ROLE" + EnvAccessKey = envNamespace + "ACCESS_KEY" + EnvSecretKey = envNamespace + "SECRET_KEY" + EnvSecurityToken = envNamespace + "SECURITY_TOKEN" + EnvRegionID = envNamespace + "REGION_ID" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +const defaultRegionID = "cn-hangzhou" + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + RAMRole string + APIKey string + SecretKey string + SecurityToken string + RegionID string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPTimeout time.Duration +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *esa.Client + + recordIDs map[string]int64 + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for AlibabaCloud ESA. +func NewDNSProvider() (*DNSProvider, error) { + config := NewDefaultConfig() + config.RegionID = env.GetOrFile(EnvRegionID) + + values, err := env.Get(EnvRAMRole) + if err == nil { + config.RAMRole = values[EnvRAMRole] + return NewDNSProviderConfig(config) + } + + values, err = env.Get(EnvAccessKey, EnvSecretKey) + if err != nil { + return nil, fmt.Errorf("aliesa: %w", err) + } + + config.APIKey = values[EnvAccessKey] + config.SecretKey = values[EnvSecretKey] + config.SecurityToken = env.GetOrFile(EnvSecurityToken) + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for AlibabaCloud ESA. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("aliesa: the configuration of the DNS provider is nil") + } + + if config.RegionID == "" { + config.RegionID = defaultRegionID + } + + cfg := new(openapi.Config). + SetRegionId(config.RegionID). + SetReadTimeout(int(config.HTTPTimeout.Milliseconds())) + + switch { + case config.RAMRole != "": + // https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance + credentialsCfg := new(credentials.Config). + SetType("ecs_ram_role"). + SetRoleName(config.RAMRole) + + credentialClient, err := credentials.NewCredential(credentialsCfg) + if err != nil { + return nil, fmt.Errorf("aliesa: new credential: %w", err) + } + + cfg = cfg.SetCredential(credentialClient) + + case config.APIKey != "" && config.SecretKey != "" && config.SecurityToken != "": + cfg = cfg. + SetAccessKeyId(config.APIKey). + SetAccessKeySecret(config.SecretKey). + SetSecurityToken(config.SecurityToken) + + case config.APIKey != "" && config.SecretKey != "": + cfg = cfg. + SetAccessKeyId(config.APIKey). + SetAccessKeySecret(config.SecretKey) + + default: + return nil, errors.New("aliesa: ram role or credentials missing") + } + + client, err := esa.NewClient(cfg) + if err != nil { + return nil, fmt.Errorf("aliesa: new client: %w", err) + } + + // Workaround to get a regional URL. + // https://github.com/alibabacloud-go/esa-20240910/blame/7660e3aab2045d4820e4b83427a154efe0c79319/client/client.go#L27 + // The `EndpointRule` is hardcoded with an empty string, so the region is ignored. + client.Endpoint = nil + client.EndpointRule = ptr.Pointer("regional") + + client.Endpoint, err = esa.GetEndpoint(client, dara.String("esa"), client.RegionId, client.EndpointRule, client.Network, client.Suffix, client.EndpointMap, client.Endpoint) + if err != nil { + return nil, fmt.Errorf("aliesa: get endpoint: %w", err) + } + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]int64), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + siteID, err := d.getSiteID(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("aliesa: %w", err) + } + + crReq := new(esa.CreateRecordRequest). + SetSiteId(siteID). + SetType("TXT"). + SetRecordName(dns01.UnFqdn(info.EffectiveFQDN)). + SetTtl(int32(d.config.TTL)). + SetData(new(esa.CreateRecordRequestData).SetValue(info.Value)) + + // https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-createrecord + crResp, err := esa.CreateRecordWithContext(ctx, d.client, crReq, &dara.RuntimeOptions{}) + if err != nil { + return fmt.Errorf("aliesa: create record: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = ptr.Deref(crResp.Body.GetRecordId()) + d.recordIDsMu.Unlock() + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + // gets the record's unique ID + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("aliesa: unknown record ID for '%s'", info.EffectiveFQDN) + } + + drReq := new(esa.DeleteRecordRequest). + SetRecordId(recordID) + + // https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-deleterecord + _, err := esa.DeleteRecordWithContext(ctx, d.client, drReq, &dara.RuntimeOptions{}) + if err != nil { + return fmt.Errorf("aliesa: delete record: %w", err) + } + + return 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 +} + +func (d *DNSProvider) getSiteID(ctx context.Context, fqdn string) (int64, error) { + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return 0, fmt.Errorf("aliesa: could not find zone for domain %q: %w", fqdn, err) + } + + lsReq := new(esa.ListSitesRequest). + SetSiteName(dns01.UnFqdn(authZone)). + SetSiteSearchType("suffix") + + // https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-listsites + lsResp, err := esa.ListSitesWithContext(ctx, d.client, lsReq, &dara.RuntimeOptions{}) + if err != nil { + return 0, fmt.Errorf("list sites: %w", err) + } + + for f := range dns01.UnFqdnDomainsSeq(fqdn) { + domain := dns01.UnFqdn(f) + + for _, site := range lsResp.Body.GetSites() { + if ptr.Deref(site.GetSiteName()) == domain { + return ptr.Deref(site.GetSiteId()), nil + } + } + } + + return 0, fmt.Errorf("site not found (fqdn: %q)", fqdn) +} diff --git a/providers/dns/aliesa/aliesa.toml b/providers/dns/aliesa/aliesa.toml new file mode 100644 index 0000000000..aaf579768f --- /dev/null +++ b/providers/dns/aliesa/aliesa.toml @@ -0,0 +1,33 @@ +Name = "Alibaba Cloud ESA" +Description = '''''' +URL = "https://www.alibabacloud.com/en/product/esa" +Code = "aliesa" +Since = "v4.29.0" + +Example = ''' +# Setup using instance RAM role +ALIESA_RAM_ROLE=lego \ +lego --email you@example.com --dns aliesa -d '*.example.com' -d example.com run + +# Or, using credentials +ALIESA_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ +ALIESA_SECRET_KEY=your-secret-key \ +ALIESA_SECURITY_TOKEN=your-sts-token \ +lego --email you@example.com --dns aliesa - -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + ALIESA_RAM_ROLE = "Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance)" + ALIESA_ACCESS_KEY = "Access key ID" + ALIESA_SECRET_KEY = "Access Key secret" + ALIESA_SECURITY_TOKEN = "STS Security Token (optional)" + [Configuration.Additional] + ALIESA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ALIESA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ALIESA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + ALIESA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-overview?spm=a2c63.p38356.help-menu-2673927.d_6_0_0.20b224c28PSZDc#:~:text=DNS-,DNS%20records,-DNS%20records" + GoClient = "https://github.com/alibabacloud-go/esa-20240910" diff --git a/providers/dns/aliesa/aliesa_test.go b/providers/dns/aliesa/aliesa_test.go new file mode 100644 index 0000000000..025529409b --- /dev/null +++ b/providers/dns/aliesa/aliesa_test.go @@ -0,0 +1,151 @@ +package aliesa + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvAccessKey, + EnvSecretKey, + EnvRAMRole). + WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAccessKey: "123", + EnvSecretKey: "456", + }, + }, + { + desc: "success (RAM role)", + envVars: map[string]string{ + EnvRAMRole: "LegoInstanceRole", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvAccessKey: "", + EnvSecretKey: "", + }, + expected: "aliesa: some credentials information are missing: ALIESA_ACCESS_KEY,ALIESA_SECRET_KEY", + }, + { + desc: "missing access key", + envVars: map[string]string{ + EnvAccessKey: "", + EnvSecretKey: "456", + }, + expected: "aliesa: some credentials information are missing: ALIESA_ACCESS_KEY", + }, + { + desc: "missing secret key", + envVars: map[string]string{ + EnvAccessKey: "123", + EnvSecretKey: "", + }, + expected: "aliesa: some credentials information are missing: ALIESA_SECRET_KEY", + }, + } + + 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 + ramRole string + apiKey string + secretKey string + expected string + }{ + { + desc: "success", + apiKey: "123", + secretKey: "456", + }, + { + desc: "success", + ramRole: "LegoInstanceRole", + }, + { + desc: "missing credentials", + expected: "aliesa: ram role or credentials missing", + }, + { + desc: "missing api key", + secretKey: "456", + expected: "aliesa: ram role or credentials missing", + }, + { + desc: "missing secret key", + apiKey: "123", + expected: "aliesa: ram role or credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIKey = test.apiKey + config.SecretKey = test.secretKey + config.RAMRole = test.ramRole + + 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) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 32de816a36..3b6ef4d42e 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -10,6 +10,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/acmedns" "github.com/go-acme/lego/v4/providers/dns/active24" "github.com/go-acme/lego/v4/providers/dns/alidns" + "github.com/go-acme/lego/v4/providers/dns/aliesa" "github.com/go-acme/lego/v4/providers/dns/allinkl" "github.com/go-acme/lego/v4/providers/dns/anexia" "github.com/go-acme/lego/v4/providers/dns/arvancloud" @@ -185,6 +186,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return active24.NewDNSProvider() case "alidns": return alidns.NewDNSProvider() + case "aliesa": + return aliesa.NewDNSProvider() case "allinkl": return allinkl.NewDNSProvider() case "anexia":