infoblox.go raw

   1  // Package infoblox implements a DNS provider for solving the DNS-01 challenge using on prem infoblox DNS.
   2  package infoblox
   3  
   4  import (
   5  	"errors"
   6  	"fmt"
   7  	"strconv"
   8  	"sync"
   9  	"time"
  10  
  11  	"github.com/go-acme/lego/v4/challenge"
  12  	"github.com/go-acme/lego/v4/challenge/dns01"
  13  	"github.com/go-acme/lego/v4/platform/config/env"
  14  	"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
  15  	infoblox "github.com/infobloxopen/infoblox-go-client/v2"
  16  )
  17  
  18  // Environment variables names.
  19  const (
  20  	envNamespace = "INFOBLOX_"
  21  
  22  	EnvHost          = envNamespace + "HOST"
  23  	EnvPort          = envNamespace + "PORT"
  24  	EnvUsername      = envNamespace + "USERNAME"
  25  	EnvPassword      = envNamespace + "PASSWORD"
  26  	EnvDNSView       = envNamespace + "DNS_VIEW"
  27  	EnvWApiVersion   = envNamespace + "WAPI_VERSION"
  28  	EnvSSLVerify     = envNamespace + "SSL_VERIFY"
  29  	EnvCACertificate = envNamespace + "CA_CERTIFICATE"
  30  
  31  	EnvTTL                = envNamespace + "TTL"
  32  	EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
  33  	EnvPollingInterval    = envNamespace + "POLLING_INTERVAL"
  34  	EnvHTTPTimeout        = envNamespace + "HTTP_TIMEOUT"
  35  )
  36  
  37  const defaultPoolConnections = 10
  38  
  39  var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
  40  
  41  // Config is used to configure the creation of the DNSProvider.
  42  type Config struct {
  43  	// Host is the URL of the grid manager.
  44  	Host string
  45  	// Port is the Port for the grid manager.
  46  	Port string
  47  
  48  	// Username the user for accessing API.
  49  	Username string
  50  	// Password the password for accessing API.
  51  	Password string
  52  
  53  	// DNSView is the dns view to put new records and search from.
  54  	DNSView string
  55  	// WapiVersion is the version of web api used.
  56  	WapiVersion string
  57  
  58  	// SSLVerify is whether or not to verify the ssl of the server being hit.
  59  	SSLVerify bool
  60  
  61  	// CACertificate is the path to the CA certificate (PEM encoded).
  62  	CACertificate string
  63  
  64  	PropagationTimeout time.Duration
  65  	PollingInterval    time.Duration
  66  	TTL                int
  67  	HTTPTimeout        int
  68  }
  69  
  70  // NewDefaultConfig returns a default configuration for the DNSProvider.
  71  func NewDefaultConfig() *Config {
  72  	return &Config{
  73  		DNSView:       env.GetOrDefaultString(EnvDNSView, "External"),
  74  		WapiVersion:   env.GetOrDefaultString(EnvWApiVersion, "2.11"),
  75  		Port:          env.GetOrDefaultString(EnvPort, "443"),
  76  		SSLVerify:     env.GetOrDefaultBool(EnvSSLVerify, true),
  77  		CACertificate: env.GetOrDefaultString(EnvCACertificate, ""),
  78  
  79  		TTL:                env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
  80  		PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
  81  		PollingInterval:    env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
  82  		HTTPTimeout:        env.GetOrDefaultInt(EnvHTTPTimeout, 30),
  83  	}
  84  }
  85  
  86  // DNSProvider implements the challenge.Provider interface.
  87  type DNSProvider struct {
  88  	config          *Config
  89  	transportConfig infoblox.TransportConfig
  90  	ibConfig        infoblox.HostConfig
  91  	ibAuth          infoblox.AuthConfig
  92  
  93  	recordRefs   map[string]string
  94  	recordRefsMu sync.Mutex
  95  }
  96  
  97  // NewDNSProvider returns a DNSProvider instance configured for Infoblox.
  98  // Credentials must be passed in the environment variables:
  99  // INFOBLOX_USERNAME, INFOBLOX_PASSWORD
 100  // INFOBLOX_HOST, INFOBLOX_PORT
 101  // INFOBLOX_DNS_VIEW, INFOBLOX_WAPI_VERSION
 102  // INFOBLOX_SSL_VERIFY.
 103  func NewDNSProvider() (*DNSProvider, error) {
 104  	values, err := env.Get(EnvHost, EnvUsername, EnvPassword)
 105  	if err != nil {
 106  		return nil, fmt.Errorf("infoblox: %w", err)
 107  	}
 108  
 109  	config := NewDefaultConfig()
 110  	config.Host = values[EnvHost]
 111  	config.Username = values[EnvUsername]
 112  	config.Password = values[EnvPassword]
 113  
 114  	return NewDNSProviderConfig(config)
 115  }
 116  
 117  // NewDNSProviderConfig return a DNSProvider instance configured for HyperOne.
 118  func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
 119  	if config == nil {
 120  		return nil, errors.New("infoblox: the configuration of the DNS provider is nil")
 121  	}
 122  
 123  	if config.Host == "" {
 124  		return nil, errors.New("infoblox: missing host")
 125  	}
 126  
 127  	if config.Username == "" || config.Password == "" {
 128  		return nil, errors.New("infoblox: missing credentials")
 129  	}
 130  
 131  	var sslVerify string
 132  	if config.CACertificate != "" {
 133  		sslVerify = config.CACertificate
 134  	} else {
 135  		sslVerify = strconv.FormatBool(config.SSLVerify)
 136  	}
 137  
 138  	return &DNSProvider{
 139  		config:          config,
 140  		transportConfig: infoblox.NewTransportConfig(sslVerify, config.HTTPTimeout, defaultPoolConnections),
 141  		ibConfig: infoblox.HostConfig{
 142  			Host:    config.Host,
 143  			Version: config.WapiVersion,
 144  			Port:    config.Port,
 145  		},
 146  		ibAuth: infoblox.AuthConfig{
 147  			Username: config.Username,
 148  			Password: config.Password,
 149  		},
 150  		recordRefs: make(map[string]string),
 151  	}, nil
 152  }
 153  
 154  // Timeout returns the timeout and interval to use when checking for DNS propagation.
 155  func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
 156  	return d.config.PropagationTimeout, d.config.PollingInterval
 157  }
 158  
 159  // Present creates a TXT record to fulfill the dns-01 challenge.
 160  func (d *DNSProvider) Present(domain, token, keyAuth string) error {
 161  	info := dns01.GetChallengeInfo(domain, keyAuth)
 162  
 163  	connector, err := infoblox.NewConnector(d.ibConfig, d.ibAuth, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{})
 164  	if err != nil {
 165  		return fmt.Errorf("infoblox: %w", err)
 166  	}
 167  
 168  	defer func() { _ = connector.Logout() }()
 169  
 170  	objectManager := infoblox.NewObjectManager(connector, useragent.Get(), "")
 171  
 172  	record, err := objectManager.CreateTXTRecord(d.config.DNSView, dns01.UnFqdn(info.EffectiveFQDN), info.Value, uint32(d.config.TTL), true, "lego", nil)
 173  	if err != nil {
 174  		return fmt.Errorf("infoblox: could not create TXT record for %s: %w", domain, err)
 175  	}
 176  
 177  	d.recordRefsMu.Lock()
 178  	d.recordRefs[token] = record.Ref
 179  	d.recordRefsMu.Unlock()
 180  
 181  	return nil
 182  }
 183  
 184  // CleanUp removes the TXT record matching the specified parameters.
 185  func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
 186  	info := dns01.GetChallengeInfo(domain, keyAuth)
 187  
 188  	connector, err := infoblox.NewConnector(d.ibConfig, d.ibAuth, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{})
 189  	if err != nil {
 190  		return fmt.Errorf("infoblox: %w", err)
 191  	}
 192  
 193  	defer func() { _ = connector.Logout() }()
 194  
 195  	objectManager := infoblox.NewObjectManager(connector, useragent.Get(), "")
 196  
 197  	// gets the record's unique ref from when we created it
 198  	d.recordRefsMu.Lock()
 199  	recordRef, ok := d.recordRefs[token]
 200  	d.recordRefsMu.Unlock()
 201  
 202  	if !ok {
 203  		return fmt.Errorf("infoblox: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token)
 204  	}
 205  
 206  	_, err = objectManager.DeleteTXTRecord(recordRef)
 207  	if err != nil {
 208  		return fmt.Errorf("infoblox: could not delete TXT record for %s: %w", domain, err)
 209  	}
 210  
 211  	// Delete record ref from map
 212  	d.recordRefsMu.Lock()
 213  	delete(d.recordRefs, token)
 214  	d.recordRefsMu.Unlock()
 215  
 216  	return nil
 217  }
 218