Skip to content

Commit

Permalink
bluecat: rewrite provider implementation (#1627)
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez authored Apr 19, 2022
1 parent 6641400 commit 6b8d5a0
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 320 deletions.
17 changes: 14 additions & 3 deletions docs/content/dns/zz_gen_bluecat.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ Configuration for [Bluecat](https://www.bluecatnetworks.com).

- Code: `bluecat`

{{% notice note %}}
_Please contribute by adding a CLI example._
{{% /notice %}}
Here is an example bash command using the Bluecat provider:

```bash
BLUECAT_PASSWORD=mypassword \
BLUECAT_DNS_VIEW=myview \
BLUECAT_USER_NAME=myusername \
BLUECAT_CONFIG_NAME=myconfig \
BLUECAT_SERVER_URL=https://bam.example.com \
BLUECAT_TTL=30 \
lego --email [email protected] --dns bluecat --domains my.example.org run
```



Expand Down Expand Up @@ -54,6 +62,9 @@ More information [here](/lego/dns/#configuration-and-credentials).



## More information

- [API documentation](https://docs.bluecatnetworks.com/r/Address-Manager-API-Guide/REST-API/9.1.0)

<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
<!-- providers/dns/bluecat/bluecat.toml -->
Expand Down
120 changes: 54 additions & 66 deletions providers/dns/bluecat/bluecat.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,15 @@
package bluecat

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"time"

"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/log"
"github.com/go-acme/lego/v4/platform/config/env"
)

const (
configType = "Configuration"
viewType = "View"
zoneType = "Zone"
txtType = "TXTRecord"
"github.com/go-acme/lego/v4/providers/dns/bluecat/internal"
)

// Environment variables names.
Expand All @@ -30,6 +22,7 @@ const (
EnvPassword = envNamespace + "PASSWORD"
EnvConfigName = envNamespace + "CONFIG_NAME"
EnvDNSView = envNamespace + "DNS_VIEW"
EnvDebug = envNamespace + "DEBUG"

EnvTTL = envNamespace + "TTL"
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
Expand All @@ -48,6 +41,7 @@ type Config struct {
PollingInterval time.Duration
TTL int
HTTPClient *http.Client
Debug bool
}

// NewDefaultConfig returns a default configuration for the DNSProvider.
Expand All @@ -59,20 +53,24 @@ func NewDefaultConfig() *Config {
HTTPClient: &http.Client{
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
},
Debug: env.GetOrDefaultBool(EnvDebug, false),
}
}

// DNSProvider implements the challenge.Provider interface.
type DNSProvider struct {
config *Config
token string
client *internal.Client
}

// NewDNSProvider returns a DNSProvider instance configured for Bluecat DNS.
// Credentials must be passed in the environment variables: BLUECAT_SERVER_URL, BLUECAT_USER_NAME and BLUECAT_PASSWORD.
// BLUECAT_SERVER_URL should have the scheme, hostname, and port (if required) of the authoritative Bluecat BAM server.
// The REST endpoint will be appended.
// In addition, the Configuration name and external DNS View Name must be passed in BLUECAT_CONFIG_NAME and BLUECAT_DNS_VIEW.
// Credentials must be passed in the environment variables:
// - BLUECAT_SERVER_URL
// It should have the scheme, hostname, and port (if required) of the authoritative Bluecat BAM server.
// The REST endpoint will be appended.
// - BLUECAT_USER_NAME and BLUECAT_PASSWORD
// - BLUECAT_CONFIG_NAME (the Configuration name)
// - BLUECAT_DNS_VIEW (external DNS View Name)
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.Get(EnvServerURL, EnvUserName, EnvPassword, EnvConfigName, EnvDNSView)
if err != nil {
Expand All @@ -99,114 +97,104 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
return nil, errors.New("bluecat: credentials missing")
}

return &DNSProvider{config: config}, nil
client := internal.NewClient(config.BaseURL)

if config.HTTPClient != nil {
client.HTTPClient = config.HTTPClient
}

return &DNSProvider{config: config, client: client}, nil
}

// Present creates a TXT record using the specified parameters
// This will *not* create a subzone to contain the TXT record,
// so make sure the FQDN specified is within an extant zone.
// This will *not* create a sub-zone to contain the TXT record,
// so make sure the FQDN specified is within an existent zone.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)

err := d.login()
err := d.client.Login(d.config.UserName, d.config.Password)
if err != nil {
return err
return fmt.Errorf("bluecat: login: %w", err)
}

viewID, err := d.lookupViewID(d.config.DNSView)
viewID, err := d.client.LookupViewID(d.config.ConfigName, d.config.DNSView)
if err != nil {
return err
return fmt.Errorf("bluecat: lookupViewID: %w", err)
}

parentZoneID, name, err := d.lookupParentZoneID(viewID, fqdn)
parentZoneID, name, err := d.client.LookupParentZoneID(viewID, fqdn)
if err != nil {
return err
return fmt.Errorf("bluecat: lookupParentZoneID: %w", err)
}

queryArgs := map[string]string{
"parentId": strconv.FormatUint(uint64(parentZoneID), 10),
if d.config.Debug {
log.Infof("fqdn: %s; viewID: %d; ZoneID: %d; zone: %s", fqdn, viewID, parentZoneID, name)
}

body := bluecatEntity{
txtRecord := internal.Entity{
Name: name,
Type: "TXTRecord",
Type: internal.TXTType,
Properties: fmt.Sprintf("ttl=%d|absoluteName=%s|txt=%s|", d.config.TTL, fqdn, value),
}

resp, err := d.sendRequest(http.MethodPost, "addEntity", body, queryArgs)
_, err = d.client.AddEntity(parentZoneID, txtRecord)
if err != nil {
return err
return fmt.Errorf("bluecat: add TXT record: %w", err)
}
defer resp.Body.Close()

addTxtBytes, _ := io.ReadAll(resp.Body)
addTxtResp := string(addTxtBytes)
// addEntity responds only with body text containing the ID of the created record
_, err = strconv.ParseUint(addTxtResp, 10, 64)
err = d.client.Deploy(parentZoneID)
if err != nil {
return fmt.Errorf("bluecat: addEntity request failed: %s", addTxtResp)
return fmt.Errorf("bluecat: deploy: %w", err)
}

err = d.deploy(parentZoneID)
err = d.client.Logout()
if err != nil {
return err
return fmt.Errorf("bluecat: logout: %w", err)
}

return d.logout()
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)

err := d.login()
err := d.client.Login(d.config.UserName, d.config.Password)
if err != nil {
return err
return fmt.Errorf("bluecat: login: %w", err)
}

viewID, err := d.lookupViewID(d.config.DNSView)
viewID, err := d.client.LookupViewID(d.config.ConfigName, d.config.DNSView)
if err != nil {
return err
return fmt.Errorf("bluecat: lookupViewID: %w", err)
}

parentID, name, err := d.lookupParentZoneID(viewID, fqdn)
parentZoneID, name, err := d.client.LookupParentZoneID(viewID, fqdn)
if err != nil {
return err
}

queryArgs := map[string]string{
"parentId": strconv.FormatUint(uint64(parentID), 10),
"name": name,
"type": txtType,
return fmt.Errorf("bluecat: lookupParentZoneID: %w", err)
}

resp, err := d.sendRequest(http.MethodGet, "getEntityByName", nil, queryArgs)
txtRecord, err := d.client.GetEntityByName(parentZoneID, name, internal.TXTType)
if err != nil {
return err
return fmt.Errorf("bluecat: get TXT record: %w", err)
}
defer resp.Body.Close()

var txtRec entityResponse
err = json.NewDecoder(resp.Body).Decode(&txtRec)
err = d.client.Delete(txtRecord.ID)
if err != nil {
return fmt.Errorf("bluecat: %w", err)
}
queryArgs = map[string]string{
"objectId": strconv.FormatUint(uint64(txtRec.ID), 10),
return fmt.Errorf("bluecat: delete TXT record: %w", err)
}

resp, err = d.sendRequest(http.MethodDelete, http.MethodDelete, nil, queryArgs)
err = d.client.Deploy(parentZoneID)
if err != nil {
return err
return fmt.Errorf("bluecat: deploy: %w", err)
}
defer resp.Body.Close()

err = d.deploy(parentID)
err = d.client.Logout()
if err != nil {
return err
return fmt.Errorf("bluecat: logout: %w", err)
}

return d.logout()
return nil
}

// Timeout returns the timeout and interval to use when checking for DNS propagation.
Expand Down
13 changes: 12 additions & 1 deletion providers/dns/bluecat/bluecat.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ URL = "https://www.bluecatnetworks.com"
Code = "bluecat"
Since = "v0.5.0"

Example = ''''''
Example = '''
BLUECAT_PASSWORD=mypassword \
BLUECAT_DNS_VIEW=myview \
BLUECAT_USER_NAME=myusername \
BLUECAT_CONFIG_NAME=myconfig \
BLUECAT_SERVER_URL=https://bam.example.com \
BLUECAT_TTL=30 \
lego --email [email protected] --dns bluecat --domains my.example.org run
'''

[Configuration]
[Configuration.Credentials]
Expand All @@ -18,3 +26,6 @@ Example = ''''''
BLUECAT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
BLUECAT_TTL = "The TTL of the TXT record used for the DNS challenge"
BLUECAT_HTTP_TIMEOUT = "API request timeout"

[Links]
API = "https://docs.bluecatnetworks.com/r/Address-Manager-API-Guide/REST-API/9.1.0"
Loading

0 comments on commit 6b8d5a0

Please sign in to comment.