diff --git a/README.md b/README.md index ded11cdc..ae174fe6 100644 --- a/README.md +++ b/README.md @@ -56,17 +56,18 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). | [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/) | [Infomaniak](https://go-acme.github.io/lego/dns/infomaniak/) | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | -| [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/) | -| [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/) | +| [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/) | [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/) | [Sonic](https://go-acme.github.io/lego/dns/sonic/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | +| [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | +| [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | +| [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | | | diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 79166661..fa9a71a3 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -56,6 +56,7 @@ func allDNSCodes() string { "hurricane", "hyperone", "iij", + "infoblox", "infomaniak", "inwx", "ionos", @@ -992,6 +993,32 @@ func displayDNSHelp(name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/iij`) + case "infoblox": + // generated from: providers/dns/infoblox/infoblox.toml + ew.writeln(`Configuration for Infoblox.`) + ew.writeln(`Code: 'infoblox'`) + ew.writeln(`Since: 'v4.4.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "INFOBLOX_HOST": Host URI`) + ew.writeln(` - "INFOBLOX_PASSWORD": Account Password`) + ew.writeln(` - "INFOBLOX_USER": Account Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "INFOBLOX_HTTP_TIMEOUT": HTTP request timeout`) + ew.writeln(` - "INFOBLOX_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "INFOBLOX_PORT": The port for the infoblox grid manager, default: 443`) + ew.writeln(` - "INFOBLOX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "INFOBLOX_SSL_VERIFY": Whether or not to verify the TLS certificate, default: true`) + ew.writeln(` - "INFOBLOX_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "INFOBLOX_VIEW": The view for the TXT records, default: External`) + ew.writeln(` - "INFOBLOX_WAPI_VERSION": The version of WAPI being used, default: 2.11`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/infoblox`) + case "infomaniak": // generated from: providers/dns/infomaniak/infomaniak.toml ew.writeln(`Configuration for Infomaniak.`) diff --git a/docs/content/dns/zz_gen_infoblox.md b/docs/content/dns/zz_gen_infoblox.md new file mode 100644 index 00000000..d192aa7e --- /dev/null +++ b/docs/content/dns/zz_gen_infoblox.md @@ -0,0 +1,72 @@ +--- +title: "Infoblox" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: infoblox +--- + + + + + +Since: v4.4.0 + +Configuration for [Infoblox](https://www.infoblox.com/). + + + + +- Code: `infoblox` + +Here is an example bash command using the Infoblox provider: + +```bash +INFOBLOX_USER=api-user-529 \ +INFOBLOX_PASSWORD=b9841238feb177a84330febba8a83208921177bffe733 \ +INFOBLOX_HOST=infoblox.example.org +lego --email myemail@example.com --dns infoblox --domains my.example.org run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `INFOBLOX_HOST` | Host URI | +| `INFOBLOX_PASSWORD` | Account Password | +| `INFOBLOX_USER` | Account Username | + +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 | +|--------------------------------|-------------| +| `INFOBLOX_HTTP_TIMEOUT` | HTTP request timeout | +| `INFOBLOX_POLLING_INTERVAL` | Time between DNS propagation check | +| `INFOBLOX_PORT` | The port for the infoblox grid manager, default: 443 | +| `INFOBLOX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `INFOBLOX_SSL_VERIFY` | Whether or not to verify the TLS certificate, default: true | +| `INFOBLOX_TTL` | The TTL of the TXT record used for the DNS challenge | +| `INFOBLOX_VIEW` | The view for the TXT records, default: External | +| `INFOBLOX_WAPI_VERSION` | The version of WAPI being used, default: 2.11 | + +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). + +When creating an API's user ensure it has the proper permissions for the view you are working with. + + + +## More information + +- [API documentation](https://your.infoblox.server/wapidoc/) +- [Go client](https://github.com/infobloxopen/infoblox-go-client) + + + + diff --git a/go.mod b/go.mod index c59a2dc5..8924313e 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/gophercloud/gophercloud v0.16.0 github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df + github.com/infobloxopen/infoblox-go-client v1.1.1 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/labbsr0x/bindman-dns-webhook v1.0.2 github.com/linode/linodego v0.25.3 diff --git a/go.sum b/go.sum index 291c7b1d..5cb7f527 100644 --- a/go.sum +++ b/go.sum @@ -124,6 +124,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -234,11 +235,14 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU= +github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= github.com/jarcoal/httpmock v1.0.6 h1:e81vOSexXU3mJuJ4l//geOmKIt+Vkxerk1feQBC8D0g= github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -348,7 +352,9 @@ github.com/nrdcg/porkbun v0.1.1/go.mod h1:JWl/WKnguWos4mjfp4YizvvToigk9qpQwrodOk github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU= @@ -719,6 +725,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= @@ -733,6 +740,7 @@ gopkg.in/ns1/ns1-go.v2 v2.4.4/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYh gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index fb6deb35..870adb9d 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -47,6 +47,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/hurricane" "github.com/go-acme/lego/v4/providers/dns/hyperone" "github.com/go-acme/lego/v4/providers/dns/iij" + "github.com/go-acme/lego/v4/providers/dns/infoblox" "github.com/go-acme/lego/v4/providers/dns/infomaniak" "github.com/go-acme/lego/v4/providers/dns/inwx" "github.com/go-acme/lego/v4/providers/dns/ionos" @@ -181,6 +182,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return hyperone.NewDNSProvider() case "iij": return iij.NewDNSProvider() + case "infoblox": + return infoblox.NewDNSProvider() case "infomaniak": return infomaniak.NewDNSProvider() case "inwx": diff --git a/providers/dns/infoblox/infoblox.go b/providers/dns/infoblox/infoblox.go new file mode 100644 index 00000000..4572ff4c --- /dev/null +++ b/providers/dns/infoblox/infoblox.go @@ -0,0 +1,200 @@ +// Package infoblox implements a DNS provider for solving the DNS-01 challenge using on prem infoblox DNS. +package infoblox + +import ( + "errors" + "fmt" + "strconv" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + infoblox "github.com/infobloxopen/infoblox-go-client" +) + +// Environment variables names. +const ( + envNamespace = "INFOBLOX_" + + EnvHost = envNamespace + "HOST" + EnvPort = envNamespace + "PORT" + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + EnvDNSView = envNamespace + "DNS_VIEW" + EnvWApiVersion = envNamespace + "WAPI_VERSION" + EnvSSLVerify = envNamespace + "SSL_VERIFY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +const ( + defaultPoolConnections = 10 + defaultUserAgent = "go-acme/lego" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + // Host is the URL of the grid manager. + Host string + // Port is the Port for the grid manager. + Port string + + // Username the user for accessing API. + Username string + // Password the password for accessing API. + Password string + + // DNSView is the dns view to put new records and search from. + DNSView string + // WapiVersion is the version of web api used. + WapiVersion string + + // SSLVerify is whether or not to verify the ssl of the server being hit. + SSLVerify bool + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPTimeout int +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + DNSView: env.GetOrDefaultString(EnvDNSView, "External"), + WapiVersion: env.GetOrDefaultString(EnvWApiVersion, "2.11"), + Port: env.GetOrDefaultString(EnvPort, "443"), + SSLVerify: env.GetOrDefaultBool(EnvSSLVerify, true), + + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPTimeout: env.GetOrDefaultInt(EnvHTTPTimeout, 30), + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + transportConfig infoblox.TransportConfig + ibConfig infoblox.HostConfig + + recordRefs map[string]string + recordRefsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Infoblox. +// Credentials must be passed in the environment variables: +// INFOBLOX_USER, INFOBLOX_PASSWORD +// INFOBLOX_HOST, INFOBLOX_PORT +// INFOBLOX_VIEW, INFOBLOX_WAPI_VERSION +// INFOBLOX_SSL_VERIFY. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvHost, EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("infoblox: %w", err) + } + + config := NewDefaultConfig() + config.Host = values[EnvHost] + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for HyperOne. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("infoblox: the configuration of the DNS provider is nil") + } + + if config.Host == "" { + return nil, errors.New("infoblox: missing host") + } + + if config.Username == "" || config.Password == "" { + return nil, errors.New("infoblox: missing credentials") + } + + return &DNSProvider{ + config: config, + transportConfig: infoblox.NewTransportConfig(strconv.FormatBool(config.SSLVerify), config.HTTPTimeout, defaultPoolConnections), + ibConfig: infoblox.HostConfig{ + Host: config.Host, + Version: config.WapiVersion, + Port: config.Port, + Username: config.Username, + Password: config.Password, + }, + recordRefs: make(map[string]string), + }, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// 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) + + connector, err := infoblox.NewConnector(d.ibConfig, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{}) + if err != nil { + return fmt.Errorf("infoblox: %w", err) + } + + defer func() { _ = connector.Logout() }() + + objectManager := infoblox.NewObjectManager(connector, defaultUserAgent, "") + + record, err := objectManager.CreateTXTRecord(dns01.UnFqdn(fqdn), value, uint(d.config.TTL), d.config.DNSView) + if err != nil { + return fmt.Errorf("infoblox: could not create TXT record for %s: %w", domain, err) + } + + d.recordRefsMu.Lock() + d.recordRefs[token] = record.Ref + d.recordRefsMu.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) + + connector, err := infoblox.NewConnector(d.ibConfig, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{}) + if err != nil { + return fmt.Errorf("infoblox: %w", err) + } + + defer func() { _ = connector.Logout() }() + + objectManager := infoblox.NewObjectManager(connector, defaultUserAgent, "") + + // gets the record's unique ref from when we created it + d.recordRefsMu.Lock() + recordRef, ok := d.recordRefs[token] + d.recordRefsMu.Unlock() + if !ok { + return fmt.Errorf("infoblox: unknown record ID for '%s' '%s'", fqdn, token) + } + + _, err = objectManager.DeleteTXTRecord(recordRef) + if err != nil { + return fmt.Errorf("infoblox: could not delete TXT record for %s: %w", domain, err) + } + + // Delete record ref from map + d.recordRefsMu.Lock() + delete(d.recordRefs, token) + d.recordRefsMu.Unlock() + + return nil +} diff --git a/providers/dns/infoblox/infoblox.toml b/providers/dns/infoblox/infoblox.toml new file mode 100644 index 00000000..a3c8bc9d --- /dev/null +++ b/providers/dns/infoblox/infoblox.toml @@ -0,0 +1,36 @@ +Name = "Infoblox" +Description = '''''' +URL = "https://www.infoblox.com/" +Code = "infoblox" +Since = "v4.4.0" + +Example = ''' +INFOBLOX_USER=api-user-529 \ +INFOBLOX_PASSWORD=b9841238feb177a84330febba8a83208921177bffe733 \ +INFOBLOX_HOST=infoblox.example.org +lego --email myemail@example.com --dns infoblox --domains my.example.org run +''' + +Additional = ''' +When creating an API's user ensure it has the proper permissions for the view you are working with. +''' + +[Configuration] + [Configuration.Credentials] + INFOBLOX_USER = "Account Username" + INFOBLOX_PASSWORD = "Account Password" + INFOBLOX_HOST = "Host URI" + [Configuration.Additional] + INFOBLOX_VIEW = "The view for the TXT records, default: External" + INFOBLOX_WAPI_VERSION = "The version of WAPI being used, default: 2.11" + INFOBLOX_PORT = "The port for the infoblox grid manager, default: 443" + INFOBLOX_SSL_VERIFY = "Whether or not to verify the TLS certificate, default: true" + INFOBLOX_POLLING_INTERVAL = "Time between DNS propagation check" + INFOBLOX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + INFOBLOX_TTL = "The TTL of the TXT record used for the DNS challenge" + INFOBLOX_HTTP_TIMEOUT = "HTTP request timeout" + + +[Links] + API = "https://your.infoblox.server/wapidoc/" + GoClient = "https://github.com/infobloxopen/infoblox-go-client" diff --git a/providers/dns/infoblox/infoblox_test.go b/providers/dns/infoblox/infoblox_test.go new file mode 100644 index 00000000..45434e0e --- /dev/null +++ b/providers/dns/infoblox/infoblox_test.go @@ -0,0 +1,172 @@ +package infoblox + +import ( + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvHost, + EnvPort, + EnvUsername, + EnvPassword, + EnvSSLVerify, +).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvHost: "example.com", + EnvUsername: "user", + EnvPassword: "secret", + EnvSSLVerify: "false", + }, + }, + { + desc: "missing host", + envVars: map[string]string{ + EnvHost: "", + EnvUsername: "user", + EnvPassword: "secret", + EnvSSLVerify: "false", + }, + expected: "infoblox: some credentials information are missing: INFOBLOX_HOST", + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvHost: "example.com", + EnvUsername: "", + EnvPassword: "secret", + EnvSSLVerify: "false", + }, + expected: "infoblox: some credentials information are missing: INFOBLOX_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvHost: "example.com", + EnvUsername: "user", + EnvPassword: "", + EnvSSLVerify: "false", + }, + expected: "infoblox: some credentials information are missing: INFOBLOX_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) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + host string + username string + password string + expected string + }{ + { + desc: "success", + host: "example.com", + username: "user", + password: "secret", + }, + { + desc: "missing host", + host: "", + username: "user", + password: "secret", + expected: "infoblox: missing host", + }, + { + desc: "missing username", + host: "example.com", + username: "", + password: "secret", + expected: "infoblox: missing credentials", + }, + { + desc: "missing password", + host: "example.com", + username: "user", + password: "", + expected: "infoblox: missing credentials", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Host = test.host + config.Username = test.username + config.Password = test.password + config.SSLVerify = false + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } 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) + + time.Sleep(2 * time.Second) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +}