1
0
mirror of https://github.com/go-acme/lego.git synced 2024-12-22 16:53:17 +02:00

Add DNS provider for VinylDNS (#1384)

This commit is contained in:
Jonathan G 2021-04-10 08:18:48 -06:00 committed by GitHub
parent 2334340d7a
commit 7f53f88555
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1006 additions and 14 deletions

View File

@ -65,7 +65,8 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
| [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | [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/) | [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/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) |
| [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [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/) |
| [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/) | [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

@ -92,6 +92,7 @@ func allDNSCodes() string {
"transip",
"vegadns",
"versio",
"vinyldns",
"vscale",
"vultr",
"yandex",
@ -1767,6 +1768,27 @@ func displayDNSHelp(name string) error {
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/versio`)
case "vinyldns":
// generated from: providers/dns/vinyldns/vinyldns.toml
ew.writeln(`Configuration for VinylDNS.`)
ew.writeln(`Code: 'vinyldns'`)
ew.writeln(`Since: 'v4.4.0'`)
ew.writeln()
ew.writeln(`Credentials:`)
ew.writeln(` - "VINYLDNS_ACCESS_KEY": The VinylDNS API key`)
ew.writeln(` - "VINYLDNS_HOST": The VinylDNS API URL`)
ew.writeln(` - "VINYLDNS_SECRET_KEY": The VinylDNS API Secret key`)
ew.writeln()
ew.writeln(`Additional Configuration:`)
ew.writeln(` - "VINYLDNS_POLLING_INTERVAL": Time between DNS propagation check`)
ew.writeln(` - "VINYLDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`)
ew.writeln(` - "VINYLDNS_TTL": The TTL of the TXT record used for the DNS challenge`)
ew.writeln()
ew.writeln(`More information: https://go-acme.github.io/lego/dns/vinyldns`)
case "vscale":
// generated from: providers/dns/vscale/vscale.toml
ew.writeln(`Configuration for Vscale.`)

View File

@ -0,0 +1,68 @@
---
title: "VinylDNS"
date: 2019-03-03T16:39:46+01:00
draft: false
slug: vinyldns
---
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/vinyldns/vinyldns.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
Since: v4.4.0
Configuration for [VinylDNS](https://www.vinyldns.io).
<!--more-->
- Code: `vinyldns`
Here is an example bash command using the VinylDNS provider:
```bash
VINYLDNS_ACCESS_KEY=xxxxxx \
VINYLDNS_SECRET_KEY=yyyyy \
VINYLDNS_HOST=https://api.vinyldns.example.org:9443 \
lego --email myemail@example.com --dns vinyldns --domains my.example.org run
```
## Credentials
| Environment Variable Name | Description |
|-----------------------|-------------|
| `VINYLDNS_ACCESS_KEY` | The VinylDNS API key |
| `VINYLDNS_HOST` | The VinylDNS API URL |
| `VINYLDNS_SECRET_KEY` | The VinylDNS API Secret key |
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 |
|--------------------------------|-------------|
| `VINYLDNS_POLLING_INTERVAL` | Time between DNS propagation check |
| `VINYLDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
| `VINYLDNS_TTL` | The TTL of the TXT record used for the DNS challenge |
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).
The vinyldns integration makes use of dotted hostnames to ease permission management.
Users are required to have DELETE ACL level or zone admin permissions on the VinylDNS zone containing the target host.
## More information
- [API documentation](https://www.vinyldns.io/api/)
- [Go client](https://github.com/vinyldns/go-vinyldns)
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/vinyldns/vinyldns.toml -->
<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->

7
go.mod
View File

@ -42,9 +42,10 @@ require (
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2
github.com/sacloud/libsacloud v1.36.2
github.com/stretchr/testify v1.7.0
github.com/transip/gotransip/v6 v6.6.0
github.com/urfave/cli v1.22.5
github.com/vultr/govultr/v2 v2.4.0
github.com/transip/gotransip/v6 v6.2.0
github.com/urfave/cli v1.22.4
github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14
github.com/vultr/govultr/v2 v2.0.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d

27
go.sum
View File

@ -139,6 +139,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -215,8 +217,8 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs=
github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
@ -404,11 +406,16 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA=
github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@ -436,18 +443,20 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/transip/gotransip/v6 v6.6.0 h1:dAHCTZzX98H6QE2kA4R9acAXu5RPPTwMSUFtpKZF3Nk=
github.com/transip/gotransip/v6 v6.6.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
github.com/transip/gotransip/v6 v6.2.0 h1:0Z+qVsyeiQdWfcAUeJyF0IEKAPvhJwwpwPi2WGtBIiE=
github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo=
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/vultr/govultr/v2 v2.4.0 h1:6ySGGAsoOann0lmVNkS8grLvbAT2iYWnO4R1RVYFg0A=
github.com/vultr/govultr/v2 v2.4.0/go.mod h1:U+dZLAmyGD62IGykgC9JYU/zQIOkIhf93nw6dJL/47M=
github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 h1:TFXGGMHmml4rs29PdPisC/aaCzOxUu1Vsh9on/IpUfE=
github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg=
github.com/vultr/govultr/v2 v2.0.0 h1:+lAtqfWy3g9VwL7tT2Fpyad8Vv4MxOhT/NU8O5dk+EQ=
github.com/vultr/govultr/v2 v2.0.0/go.mod h1:2PsEeg+gs3p/Fo5Pw8F9mv+DUBEOlrNZ8GmCTGmhOhs=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=

View File

@ -83,6 +83,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/transip"
"github.com/go-acme/lego/v4/providers/dns/vegadns"
"github.com/go-acme/lego/v4/providers/dns/versio"
"github.com/go-acme/lego/v4/providers/dns/vinyldns"
"github.com/go-acme/lego/v4/providers/dns/vscale"
"github.com/go-acme/lego/v4/providers/dns/vultr"
"github.com/go-acme/lego/v4/providers/dns/yandex"
@ -253,6 +254,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
return versio.NewDNSProvider()
case "vultr":
return vultr.NewDNSProvider()
case "vinyldns":
return vinyldns.NewDNSProvider()
case "vscale":
return vscale.NewDNSProvider()
case "yandex":

View File

@ -0,0 +1,40 @@
{
"changeType": "Create",
"created": "2021-03-04T00:49:00Z",
"id": "27ba5c17-a217-4e8d-b662-b1dc8bee588f",
"recordSet": {
"account": "",
"created": "2021-03-04T00:49:00Z",
"id": "10000000-0000-0000-0000-000000000000",
"name": "_acme-challenge.host",
"records": [
{
"text": "O2UTPYgIzRNt5N27EVcNKDxv6goSF7ru3zi3chZXKUw"
}
],
"status": "Active",
"ttl": 30,
"type": "TXT",
"updated": "2021-03-04T00:49:00Z",
"zoneId": "00000000-0000-0000-0000-000000000000"
},
"singleBatchChangeIds": [],
"status": "Complete",
"userId": "50000000-0000-0000-0000-000000000000",
"zone": {
"account": "system",
"acl": {
"rules": []
},
"adminGroupId": "40000000-0000-0000-0000-000000000000",
"created": "2020-07-15T21:15:36Z",
"email": "Ops@company.invalid",
"id": "00000000-0000-0000-0000-000000000000",
"isTest": false,
"latestSync": "2020-07-15T21:15:36Z",
"name": "example.com.",
"shared": false,
"status": "Active",
"updated": "2021-03-03T18:02:47Z"
}
}

View File

@ -0,0 +1,40 @@
{
"changeType": "Delete",
"created": "2021-03-04T00:49:00Z",
"id": "27ba5c17-a217-4e8d-b662-b1dc8bee588f",
"recordSet": {
"account": "",
"created": "2021-03-04T00:49:00Z",
"id": "10000000-0000-0000-0000-000000000000",
"name": "_acme-challenge.host",
"records": [
{
"text": "O2UTPYgIzRNt5N27EVcNKDxv6goSF7ru3zi3chZXKUw"
}
],
"status": "Active",
"ttl": 30,
"type": "TXT",
"updated": "2021-03-04T00:49:00Z",
"zoneId": "00000000-0000-0000-0000-000000000000"
},
"singleBatchChangeIds": [],
"status": "Complete",
"userId": "50000000-0000-0000-0000-000000000000",
"zone": {
"account": "system",
"acl": {
"rules": []
},
"adminGroupId": "40000000-0000-0000-0000-000000000000",
"created": "2020-07-15T21:15:36Z",
"email": "Ops@company.invalid",
"id": "00000000-0000-0000-0000-000000000000",
"isTest": false,
"latestSync": "2020-07-15T21:15:36Z",
"name": "example.com.",
"shared": false,
"status": "Active",
"updated": "2021-03-03T18:02:47Z"
}
}

View File

@ -0,0 +1,39 @@
{
"changeType": "Delete",
"created": "2021-03-04T16:21:54Z",
"id": "20000000-0000-0000-0000-000000000000",
"recordSet": {
"account": "",
"created": "2021-03-04T16:21:54Z",
"id": "11000000-0000-0000-0000-000000000000",
"name": "_acme-challenge.host",
"records": [
{
"text": "O2UTPYgIzRNt5N27EVcNKDxv6goSF7ru3zi3chZXKUw"
}
],
"status": "Pending",
"ttl": 30,
"type": "TXT",
"zoneId": "00000000-0000-0000-0000-000000000000"
},
"singleBatchChangeIds": [],
"status": "Pending",
"userId": "50000000-0000-0000-0000-000000000000",
"zone": {
"account": "system",
"acl": {
"rules": []
},
"adminGroupId": "40000000-0000-0000-0000-000000000000",
"created": "2020-07-15T21:15:36Z",
"email": "Ops@company.invalid",
"id": "00000000-0000-0000-0000-000000000000",
"isTest": false,
"latestSync": "2020-07-15T21:15:36Z",
"name": "example.com.",
"shared": false,
"status": "Active",
"updated": "2021-03-03T18:02:47Z"
}
}

View File

@ -0,0 +1,39 @@
{
"changeType": "Create",
"created": "2021-03-04T16:21:54Z",
"id": "20000000-0000-0000-0000-000000000000",
"recordSet": {
"account": "",
"created": "2021-03-04T16:21:54Z",
"id": "11000000-0000-0000-0000-000000000000",
"name": "_acme-challenge.host",
"records": [
{
"text": "O2UTPYgIzRNt5N27EVcNKDxv6goSF7ru3zi3chZXKUw"
}
],
"status": "Pending",
"ttl": 30,
"type": "TXT",
"zoneId": "00000000-0000-0000-0000-000000000000"
},
"singleBatchChangeIds": [],
"status": "Pending",
"userId": "50000000-0000-0000-0000-000000000000",
"zone": {
"account": "system",
"acl": {
"rules": []
},
"adminGroupId": "40000000-0000-0000-0000-000000000000",
"created": "2020-07-15T21:15:36Z",
"email": "Ops@company.invalid",
"id": "00000000-0000-0000-0000-000000000000",
"isTest": false,
"latestSync": "2020-07-15T21:15:36Z",
"name": "example.com.",
"shared": false,
"status": "Active",
"updated": "2021-03-03T18:02:47Z"
}
}

View File

@ -0,0 +1,6 @@
{
"maxItems": 100,
"nameSort": "ASC",
"recordNameFilter": "_acme-challenge.host",
"recordSets": []
}

View File

@ -0,0 +1,25 @@
{
"maxItems": 100,
"nameSort": "ASC",
"recordNameFilter": "_acme-challenge.host",
"recordSets": [
{
"accessLevel": "Delete",
"account": "",
"created": "2021-03-04T00:51:43Z",
"fqdn": "_acme-challenge.host.example.com.",
"id": "30000000-0000-0000-0000-000000000000",
"name": "_acme-challenge.host",
"records": [
{
"text": "O2UTPYgIzRNt5N27EVcNKDxv6goSF7ru3zi3chZXKUw"
}
],
"status": "Active",
"ttl": 30,
"type": "TXT",
"updated": "2021-03-04T00:51:43Z",
"zoneId": "00000000-0000-0000-0000-000000000000"
}
]
}

View File

@ -0,0 +1,19 @@
{
"zone": {
"accessLevel": "Delete",
"account": "system",
"acl": {
"rules": []
},
"adminGroupId": "40000000-0000-0000-0000-000000000000",
"adminGroupName": "OpsTeam",
"created": "2020-07-15T21:15:36Z",
"email": "Ops@company.invalid",
"id": "00000000-0000-0000-0000-000000000000",
"latestSync": "2020-07-15T21:15:36Z",
"name": "example.com.",
"shared": false,
"status": "Active",
"updated": "2021-03-03T18:02:47Z"
}
}

View File

@ -0,0 +1,114 @@
package vinyldns
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"sync"
"testing"
"github.com/stretchr/testify/require"
)
func setup(t *testing.T) (*http.ServeMux, *DNSProvider) {
t.Helper()
mux := http.NewServeMux()
server := httptest.NewServer(mux)
t.Cleanup(server.Close)
config := NewDefaultConfig()
config.AccessKey = "foo"
config.SecretKey = "bar"
config.Host = server.URL
p, err := NewDNSProviderConfig(config)
require.NoError(t, err)
return mux, p
}
type mockRouter struct {
debug bool
mu sync.Mutex
routes map[string]map[string]http.HandlerFunc
}
func newMockRouter() *mockRouter {
routes := map[string]map[string]http.HandlerFunc{
http.MethodGet: {},
http.MethodPost: {},
http.MethodPut: {},
http.MethodDelete: {},
}
return &mockRouter{
routes: routes,
}
}
func (h *mockRouter) Debug() *mockRouter {
h.debug = true
return h
}
func (h *mockRouter) Get(path string, statusCode int, filename string) *mockRouter {
h.add(http.MethodGet, path, statusCode, filename)
return h
}
func (h *mockRouter) Post(path string, statusCode int, filename string) *mockRouter {
h.add(http.MethodPost, path, statusCode, filename)
return h
}
func (h *mockRouter) Put(path string, statusCode int, filename string) *mockRouter {
h.add(http.MethodPut, path, statusCode, filename)
return h
}
func (h *mockRouter) Delete(path string, statusCode int, filename string) *mockRouter {
h.add(http.MethodDelete, path, statusCode, filename)
return h
}
func (h *mockRouter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
h.mu.Lock()
defer h.mu.Unlock()
if h.debug {
fmt.Println(req)
}
rt := h.routes[req.Method]
if rt == nil {
http.NotFound(rw, req)
return
}
hdl := rt[req.URL.Path]
if hdl == nil {
http.NotFound(rw, req)
return
}
hdl(rw, req)
}
func (h *mockRouter) add(method, path string, statusCode int, filename string) {
h.routes[method][path] = func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(statusCode)
data, err := ioutil.ReadFile(fmt.Sprintf("./fixtures/%s.json", filename))
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "application/json")
_, _ = rw.Write(data)
}
}

View File

@ -0,0 +1,291 @@
// Package vinyldns implements a DNS provider for solving the DNS-01 challenge using VinylDNS.
package vinyldns
import (
"errors"
"fmt"
"strings"
"time"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/platform/config/env"
"github.com/go-acme/lego/v4/platform/wait"
"github.com/vinyldns/go-vinyldns/vinyldns"
)
// Environment variables names.
const (
envNamespace = "VINYLDNS_"
EnvAccessKey = envNamespace + "ACCESS_KEY"
EnvSecretKey = envNamespace + "SECRET_KEY"
EnvHost = envNamespace + "HOST"
EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
)
// Config is used to configure the creation of the DNSProvider.
type Config struct {
AccessKey string
SecretKey string
Host string
TTL int
PropagationTimeout time.Duration
PollingInterval time.Duration
}
// NewDefaultConfig returns a default configuration for the DNSProvider.
func NewDefaultConfig() *Config {
return &Config{
TTL: env.GetOrDefaultInt(EnvTTL, 30),
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute),
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 4*time.Second),
}
}
// DNSProvider implements the challenge.Provider interface.
type DNSProvider struct {
client *vinyldns.Client
config *Config
}
// NewDNSProvider returns a DNSProvider instance configured for VinylDNS.
// Credentials must be passed in the environment variables:
// VINYLDNS_ACCESS_KEY, VINYLDNS_SECRET_KEY, VINYLDNS_HOST.
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvAccessKey, EnvSecretKey, EnvHost)
if err != nil {
return nil, fmt.Errorf("vinyldns: %w", err)
}
config := NewDefaultConfig()
config.AccessKey = values[EnvAccessKey]
config.SecretKey = values[EnvSecretKey]
config.Host = values[EnvHost]
return NewDNSProviderConfig(config)
}
// NewDNSProviderConfig return a DNSProvider instance configured for VinylDNS.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("vinyldns: the configuration of the VinylDNS DNS provider is nil")
}
if config.AccessKey == "" || config.SecretKey == "" {
return nil, errors.New("vinyldns: credentials are missing")
}
if config.Host == "" {
return nil, errors.New("vinyldns: host is missing")
}
client := vinyldns.NewClient(vinyldns.ClientConfiguration{
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
Host: config.Host,
UserAgent: "go-acme/lego",
})
client.HTTPClient.Timeout = 30 * time.Second
return &DNSProvider{client: client, config: config}, nil
}
// Present creates a TXT record to fulfill the dns-01 challenge.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
existingRecord, err := d.getRecordSet(fqdn)
if err != nil {
return fmt.Errorf("vinyldns: %w", err)
}
record := vinyldns.Record{Text: value}
if existingRecord == nil || existingRecord.ID == "" {
err = d.createRecordSet(fqdn, []vinyldns.Record{record})
if err != nil {
return fmt.Errorf("vinyldns: %w", err)
}
return nil
}
for _, i := range existingRecord.Records {
if i.Text == value {
return nil
}
}
records := existingRecord.Records
records = append(records, record)
err = d.updateRecordSet(existingRecord, records)
if err != nil {
return fmt.Errorf("vinyldns: %w", err)
}
return nil
}
// CleanUp removes the TXT record matching the specified parameters.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
existingRecord, err := d.getRecordSet(fqdn)
if err != nil {
return fmt.Errorf("vinyldns: %w", err)
}
if existingRecord == nil || existingRecord.ID == "" || len(existingRecord.Records) == 0 {
return nil
}
var records []vinyldns.Record
for _, i := range existingRecord.Records {
if i.Text != value {
records = append(records, i)
}
}
if len(records) == 0 {
err = d.deleteRecordSet(existingRecord)
if err != nil {
return fmt.Errorf("vinyldns: %w", err)
}
return nil
}
err = d.updateRecordSet(existingRecord, records)
if err != nil {
return fmt.Errorf("vinyldns: %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) getRecordSet(fqdn string) (*vinyldns.RecordSet, error) {
zoneName, hostName, err := splitDomain(fqdn)
if err != nil {
return nil, err
}
zone, err := d.client.ZoneByName(zoneName)
if err != nil {
return nil, err
}
allRecordSets, err := d.client.RecordSetsListAll(zone.ID, vinyldns.ListFilter{NameFilter: hostName})
if err != nil {
return nil, err
}
var recordSets []vinyldns.RecordSet
for _, i := range allRecordSets {
if i.Type == "TXT" {
recordSets = append(recordSets, i)
}
}
switch {
case len(recordSets) > 1:
return nil, fmt.Errorf("ambiguous recordset definition of %s", fqdn)
case len(recordSets) == 1:
return &recordSets[0], nil
default:
return nil, nil
}
}
func (d *DNSProvider) createRecordSet(fqdn string, records []vinyldns.Record) error {
zoneName, hostName, err := splitDomain(fqdn)
if err != nil {
return err
}
zone, err := d.client.ZoneByName(zoneName)
if err != nil {
return err
}
recordSet := vinyldns.RecordSet{
Name: hostName,
ZoneID: zone.ID,
Type: "TXT",
TTL: d.config.TTL,
Records: records,
}
resp, err := d.client.RecordSetCreate(&recordSet)
if err != nil {
return err
}
return d.waitForChanges("CreateRS", resp)
}
func (d *DNSProvider) updateRecordSet(recordSet *vinyldns.RecordSet, newRecords []vinyldns.Record) error {
operation := "delete"
if len(recordSet.Records) < len(newRecords) {
operation = "add"
}
recordSet.Records = newRecords
recordSet.TTL = d.config.TTL
resp, err := d.client.RecordSetUpdate(recordSet)
if err != nil {
return err
}
return d.waitForChanges("UpdateRS - "+operation, resp)
}
func (d *DNSProvider) deleteRecordSet(existingRecord *vinyldns.RecordSet) error {
resp, err := d.client.RecordSetDelete(existingRecord.ZoneID, existingRecord.ID)
if err != nil {
return err
}
return d.waitForChanges("DeleteRS", resp)
}
func (d *DNSProvider) waitForChanges(operation string, resp *vinyldns.RecordSetUpdateResponse) error {
return wait.For("vinyldns", d.config.PropagationTimeout, d.config.PollingInterval,
func() (bool, error) {
change, err := d.client.RecordSetChange(resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID)
if err != nil {
return false, fmt.Errorf("failed to query change status: %w", err)
}
if change.Status == "Complete" {
return true, nil
}
return false, fmt.Errorf("waiting operation: %s, zoneID: %s, recordsetID: %s, changeID: %s",
operation, resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID)
},
)
}
// splitDomain splits the hostname from the authoritative zone, and returns both parts.
func splitDomain(fqdn string) (string, string, error) {
zone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return "", "", err
}
host := dns01.UnFqdn(strings.TrimSuffix(fqdn, zone))
return zone, host, nil
}

View File

@ -0,0 +1,31 @@
Name = "VinylDNS"
Description = ''''''
URL = "https://www.vinyldns.io"
Code = "vinyldns"
Since = "v4.4.0"
Example = '''
VINYLDNS_ACCESS_KEY=xxxxxx \
VINYLDNS_SECRET_KEY=yyyyy \
VINYLDNS_HOST=https://api.vinyldns.example.org:9443 \
lego --email myemail@example.com --dns vinyldns --domains my.example.org run
'''
Additional = '''
The vinyldns integration makes use of dotted hostnames to ease permission management.
Users are required to have DELETE ACL level or zone admin permissions on the VinylDNS zone containing the target host.
'''
[Configuration]
[Configuration.Credentials]
VINYLDNS_ACCESS_KEY = "The VinylDNS API key"
VINYLDNS_SECRET_KEY = "The VinylDNS API Secret key"
VINYLDNS_HOST = "The VinylDNS API URL"
[Configuration.Additional]
VINYLDNS_POLLING_INTERVAL = "Time between DNS propagation check"
VINYLDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
VINYLDNS_TTL = "The TTL of the TXT record used for the DNS challenge"
[Links]
API = "https://www.vinyldns.io/api/"
GoClient = "https://github.com/vinyldns/go-vinyldns"

View File

@ -0,0 +1,244 @@
package vinyldns
import (
"net/http"
"testing"
"time"
"github.com/go-acme/lego/v4/platform/tester"
"github.com/stretchr/testify/require"
)
const envDomain = envNamespace + "DOMAIN"
const (
targetRootDomain = "example.com"
targetDomain = "host." + targetRootDomain
zoneID = "00000000-0000-0000-0000-000000000000"
newRecordSetID = "11000000-0000-0000-0000-000000000000"
newCreateChangeID = "20000000-0000-0000-0000-000000000000"
recordID = "30000000-0000-0000-0000-000000000000"
)
var envTest = tester.NewEnvTest(
EnvAccessKey,
EnvSecretKey,
EnvHost).
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",
EnvHost: "https://example.org",
},
},
{
desc: "missing all credentials",
envVars: map[string]string{
EnvHost: "https://example.org",
},
expected: "vinyldns: some credentials information are missing: VINYLDNS_ACCESS_KEY,VINYLDNS_SECRET_KEY",
},
{
desc: "missing access key",
envVars: map[string]string{
EnvSecretKey: "456",
EnvHost: "https://example.org",
},
expected: "vinyldns: some credentials information are missing: VINYLDNS_ACCESS_KEY",
},
{
desc: "missing secret key",
envVars: map[string]string{
EnvAccessKey: "123",
EnvHost: "https://example.org",
},
expected: "vinyldns: some credentials information are missing: VINYLDNS_SECRET_KEY",
},
{
desc: "missing host",
envVars: map[string]string{
EnvAccessKey: "123",
EnvSecretKey: "456",
},
expected: "vinyldns: some credentials information are missing: VINYLDNS_HOST",
},
}
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
accessKey string
secretKey string
host string
expected string
}{
{
desc: "success",
accessKey: "123",
secretKey: "456",
host: "https://example.org",
},
{
desc: "missing all credentials",
host: "https://example.org",
expected: "vinyldns: credentials are missing",
},
{
desc: "missing access key",
secretKey: "456",
host: "https://example.org",
expected: "vinyldns: credentials are missing",
},
{
desc: "missing secret key",
accessKey: "123",
host: "https://example.org",
expected: "vinyldns: credentials are missing",
},
{
desc: "missing host",
accessKey: "123",
secretKey: "456",
expected: "vinyldns: host is missing",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
config := NewDefaultConfig()
config.AccessKey = test.accessKey
config.SecretKey = test.secretKey
config.Host = test.host
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 TestDNSProvider_Present(t *testing.T) {
testCases := []struct {
desc string
keyAuth string
handler http.Handler
}{
{
desc: "new record",
keyAuth: "123456d==",
handler: newMockRouter().
Get("/zones/name/"+targetRootDomain+".", http.StatusOK, "zoneByName").
Get("/zones/"+zoneID+"/recordsets", http.StatusOK, "recordSetsListAll-empty").
Post("/zones/"+zoneID+"/recordsets", http.StatusAccepted, "recordSetUpdate-create").
Get("/zones/"+zoneID+"/recordsets/"+newRecordSetID+"/changes/"+newCreateChangeID, http.StatusOK, "recordSetChange-create"),
},
{
desc: "existing record",
keyAuth: "123456d==",
handler: newMockRouter().
Get("/zones/name/"+targetRootDomain+".", http.StatusOK, "zoneByName").
Get("/zones/"+zoneID+"/recordsets", http.StatusOK, "recordSetsListAll"),
},
{
desc: "duplicate key",
keyAuth: "abc123!!",
handler: newMockRouter().
Get("/zones/name/"+targetRootDomain+".", http.StatusOK, "zoneByName").
Get("/zones/"+zoneID+"/recordsets", http.StatusOK, "recordSetsListAll").
Put("/zones/"+zoneID+"/recordsets/"+recordID, http.StatusAccepted, "recordSetUpdate-create").
Get("/zones/"+zoneID+"/recordsets/"+newRecordSetID+"/changes/"+newCreateChangeID, http.StatusOK, "recordSetChange-create"),
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
mux, p := setup(t)
mux.Handle("/", test.handler)
err := p.Present(targetDomain, "token"+test.keyAuth, test.keyAuth)
require.NoError(t, err)
})
}
}
func TestDNSProvider_CleanUp(t *testing.T) {
mux, p := setup(t)
mux.Handle("/", newMockRouter().
Get("/zones/name/"+targetRootDomain+".", http.StatusOK, "zoneByName").
Get("/zones/"+zoneID+"/recordsets", http.StatusOK, "recordSetsListAll").
Delete("/zones/"+zoneID+"/recordsets/"+recordID, http.StatusAccepted, "recordSetDelete").
Get("/zones/"+zoneID+"/recordsets/"+newRecordSetID+"/changes/"+newCreateChangeID, http.StatusOK, "recordSetChange-delete"),
)
err := p.CleanUp(targetDomain, "123456d==", "123456d==")
require.NoError(t, err)
}
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)
time.Sleep(2 * time.Second)
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
require.NoError(t, err)
}