Skip to content

Commit

Permalink
NetScaler ADC ACME providers for go-acme/lego
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Tytgat <[email protected]>
  • Loading branch information
jantytgat committed Mar 29, 2024
1 parent 26b2d9a commit ab9b506
Show file tree
Hide file tree
Showing 4 changed files with 608 additions and 0 deletions.
16 changes: 16 additions & 0 deletions go.mod
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
)
32 changes: 32 additions & 0 deletions go.sum
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=
197 changes: 197 additions & 0 deletions pkg/lego/providers/dns/netscalerdns/netscalerdns.go
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)
}
Loading

0 comments on commit ab9b506

Please sign in to comment.