-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NetScaler ADC ACME providers for go-acme/lego
Signed-off-by: Jan Tytgat <[email protected]>
- Loading branch information
Showing
4 changed files
with
608 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,19 @@ | ||
module github.com/corelayer/go-netscaleradc-acme | ||
|
||
go 1.22.1 | ||
|
||
require ( | ||
github.com/corelayer/go-netscaleradc-nitro v0.3.3 | ||
github.com/go-acme/lego/v4 v4.16.1 | ||
) | ||
|
||
require ( | ||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect | ||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect | ||
github.com/miekg/dns v1.1.58 // indirect | ||
golang.org/x/crypto v0.19.0 // indirect | ||
golang.org/x/mod v0.14.0 // indirect | ||
golang.org/x/net v0.20.0 // indirect | ||
golang.org/x/sys v0.17.0 // indirect | ||
golang.org/x/tools v0.17.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= | ||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= | ||
github.com/corelayer/go-netscaleradc-nitro v0.3.3 h1:cTuHACbQWRtrqHchgupr3Z+ZrRnv1FHnuxAzymNsws4= | ||
github.com/corelayer/go-netscaleradc-nitro v0.3.3/go.mod h1:t1QnWMQfU5RL/MKrjA+oTuNp+nXRrPX4352jiwbqYCo= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ= | ||
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE= | ||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= | ||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= | ||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | ||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= | ||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= | ||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= | ||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= | ||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= | ||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= | ||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= | ||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= | ||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= | ||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
/* | ||
* Copyright 2024 CoreLayer BV | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package netscalerdns | ||
|
||
import ( | ||
"fmt" | ||
"log/slog" | ||
|
||
"github.com/corelayer/go-netscaleradc-nitro/pkg/nitro" | ||
"github.com/corelayer/go-netscaleradc-nitro/pkg/nitro/resource/config" | ||
"github.com/corelayer/go-netscaleradc-nitro/pkg/nitro/resource/controllers" | ||
"github.com/go-acme/lego/v4/challenge/dns01" | ||
"github.com/go-acme/lego/v4/platform/config/env" | ||
) | ||
|
||
const ( | ||
envNamespace = "NETSCALERADC_DNS_" | ||
|
||
envName = envNamespace + "NAME" | ||
envAddress = envNamespace + "ADDRESS" | ||
envUsername = envNamespace + "USER" | ||
envPassword = envNamespace + "PASS" | ||
envUseSsl = envNamespace + "USE_SSL" | ||
envValidateServerCertificate = envNamespace + "VALIDATE_SERVER_CERTIFICATE" | ||
envTimeout = envNamespace + "TIMEOUT" | ||
|
||
ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS = "netscalerdns" | ||
) | ||
|
||
func newProviderConfig() (*providerConfig, error) { | ||
var ( | ||
err error | ||
values map[string]string | ||
) | ||
|
||
values, err = env.Get(envName, envAddress, envUsername, envPassword) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &providerConfig{ | ||
Name: values[envName], | ||
Address: values[envAddress], | ||
Username: values[envUsername], | ||
Password: values[envPassword], | ||
UseSsl: env.GetOrDefaultBool(envUseSsl, true), | ||
ValidateServerCertificate: env.GetOrDefaultBool(envValidateServerCertificate, true), | ||
Timeout: env.GetOrDefaultInt(envTimeout, 5000), | ||
}, nil | ||
} | ||
|
||
type providerConfig struct { | ||
Name string | ||
Address string | ||
Username string | ||
Password string | ||
UseSsl bool | ||
ValidateServerCertificate bool | ||
Timeout int | ||
} | ||
|
||
func (c providerConfig) GetClient() (*nitro.Client, error) { | ||
return nitro.NewClient(c.Name, c.Address, c.getCredentials(), c.getConnectionSettings()) | ||
} | ||
|
||
func (c providerConfig) getCredentials() nitro.Credentials { | ||
return nitro.Credentials{ | ||
Username: c.Username, | ||
Password: c.Password, | ||
} | ||
} | ||
|
||
func (c providerConfig) getConnectionSettings() nitro.ConnectionSettings { | ||
return nitro.ConnectionSettings{ | ||
UseSsl: c.UseSsl, | ||
Timeout: c.Timeout, | ||
UserAgent: "", | ||
ValidateServerCertificate: c.ValidateServerCertificate, | ||
LogTlsSecrets: false, | ||
LogTlsSecretsDestination: "", | ||
AutoLogin: false, | ||
} | ||
} | ||
|
||
func NewNetScalerDnsProvider(maxRetries int) (*DNSProvider, error) { | ||
var ( | ||
err error | ||
c *providerConfig | ||
n *nitro.Client | ||
p *DNSProvider | ||
) | ||
c, err = newProviderConfig() | ||
if err != nil { | ||
slog.Error("failed to initialize client configuration from environment", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "error", err) | ||
return nil, err | ||
} | ||
|
||
n, err = c.GetClient() | ||
if err != nil { | ||
slog.Error("failed to initialize nitro client", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "error", err) | ||
return nil, err | ||
} | ||
|
||
p = &DNSProvider{ | ||
nitroClient: n, | ||
maxRetries: maxRetries, | ||
} | ||
p.initialize() | ||
return &DNSProvider{ | ||
maxRetries: maxRetries, | ||
}, nil | ||
} | ||
|
||
// DNSProvider manages ACME requests for NetScaler ADC Authoritative DNS service | ||
type DNSProvider struct { | ||
nitroClient *nitro.Client | ||
dnsTxtRec *controllers.DnsTxtRecController | ||
maxRetries int | ||
} | ||
|
||
// Present the ACME challenge to the provider. | ||
// domain is the fqdn for which the challenge will be provided | ||
// Parameter endpoint is the path to which ACME will look for the challenge (/.well-known/acme-challenge/<token>) | ||
// Parameter keyAuth is the value which must be returned for a successful challenge | ||
func (p *DNSProvider) Present(domain string, token string, keyAuth string) error { | ||
var err error | ||
slog.Info("present acme challenge", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain) | ||
|
||
// Get challenge information to | ||
info := dns01.GetChallengeInfo(domain, keyAuth) | ||
|
||
// Add DNS record to ADNS zone on NetScaler ADC | ||
slog.Debug("create dns record", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain) | ||
if _, err = p.dnsTxtRec.Add(info.FQDN, []string{info.Value}, 30); err != nil { | ||
slog.Error("failed to create dns record", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain, "error", err) | ||
return fmt.Errorf("failed to create dns record %s: %w", domain, err) | ||
} | ||
|
||
slog.Debug("finished presenting acme challenge", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain) | ||
return nil | ||
} | ||
|
||
func (p *DNSProvider) CleanUp(domain string, token string, keyAuth string) error { | ||
var err error | ||
slog.Info("cleanup acme challenge", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain) | ||
|
||
// Get DNS01 Challenge info | ||
info := dns01.GetChallengeInfo(domain, keyAuth) | ||
|
||
slog.Debug("delete dns record", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain) | ||
var res *nitro.Response[config.DnsTxtRec] | ||
// Limit data transfer by limiting returned fields | ||
if res, err = p.dnsTxtRec.Get(info.FQDN, []string{"string", "recordid"}); err != nil { | ||
slog.Error("failed to get record id", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain, "error", err) | ||
return fmt.Errorf("failed to get record id %s: %w", domain, err) | ||
|
||
} | ||
|
||
for _, rec := range res.Data { | ||
slog.Debug("processing dns record", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain, "recordid", rec.RecordId) | ||
// Loop over array of returned records | ||
for _, data := range rec.Data { | ||
// Only remove record if keyAuth matches the current acme request | ||
if data != info.Value { | ||
slog.Debug("skipping dns record", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain) | ||
continue | ||
} | ||
|
||
if _, err = p.dnsTxtRec.Delete(info.FQDN, rec.RecordId); err != nil { | ||
slog.Error("failed to delete dns record", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain, "error", err) | ||
return fmt.Errorf("failed to delete dns record %s: %w", domain, err) | ||
} | ||
slog.Debug("deleted dns record", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain) | ||
} | ||
} | ||
|
||
slog.Debug("finished cleaning up acme challenge", "provider", ACME_CHALLENGE_PROVIDER_NETSCALER_ADNS, "domain", domain) | ||
return nil | ||
} | ||
|
||
func (p *DNSProvider) initialize() { | ||
p.dnsTxtRec = controllers.NewDnsTxtRecController(p.nitroClient) | ||
} |
Oops, something went wrong.