...

Source file src/github.com/letsencrypt/boulder/cmd/config.go

Documentation: github.com/letsencrypt/boulder/cmd

     1  package cmd
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    14  	"google.golang.org/grpc/resolver"
    15  
    16  	"github.com/letsencrypt/boulder/config"
    17  	"github.com/letsencrypt/boulder/core"
    18  )
    19  
    20  // PasswordConfig contains a path to a file containing a password.
    21  type PasswordConfig struct {
    22  	PasswordFile string `validate:"required"`
    23  }
    24  
    25  // Pass returns a password, extracted from the PasswordConfig's PasswordFile
    26  func (pc *PasswordConfig) Pass() (string, error) {
    27  	// Make PasswordConfigs optional, for backwards compatibility.
    28  	if pc.PasswordFile == "" {
    29  		return "", nil
    30  	}
    31  	contents, err := os.ReadFile(pc.PasswordFile)
    32  	if err != nil {
    33  		return "", err
    34  	}
    35  	return strings.TrimRight(string(contents), "\n"), nil
    36  }
    37  
    38  // ServiceConfig contains config items that are common to all our services, to
    39  // be embedded in other config structs.
    40  type ServiceConfig struct {
    41  	// DebugAddr is the address to run the /debug handlers on.
    42  	DebugAddr string `validate:"hostname_port"`
    43  	GRPC      *GRPCServerConfig
    44  	TLS       TLSConfig
    45  
    46  	// HealthCheckInterval is the duration between deep health checks of the
    47  	// service. Defaults to 5 seconds.
    48  	HealthCheckInterval config.Duration `validate:"-"`
    49  }
    50  
    51  // DBConfig defines how to connect to a database. The connect string is
    52  // stored in a file separate from the config, because it can contain a password,
    53  // which we want to keep out of configs.
    54  type DBConfig struct {
    55  	// A file containing a connect URL for the DB.
    56  	DBConnectFile string `validate:"required"`
    57  
    58  	// MaxOpenConns sets the maximum number of open connections to the
    59  	// database. If MaxIdleConns is greater than 0 and MaxOpenConns is
    60  	// less than MaxIdleConns, then MaxIdleConns will be reduced to
    61  	// match the new MaxOpenConns limit. If n < 0, then there is no
    62  	// limit on the number of open connections.
    63  	MaxOpenConns int `validate:"min=-1"`
    64  
    65  	// MaxIdleConns sets the maximum number of connections in the idle
    66  	// connection pool. If MaxOpenConns is greater than 0 but less than
    67  	// MaxIdleConns, then MaxIdleConns will be reduced to match the
    68  	// MaxOpenConns limit. If n < 0, no idle connections are retained.
    69  	MaxIdleConns int `validate:"min=-1"`
    70  
    71  	// ConnMaxLifetime sets the maximum amount of time a connection may
    72  	// be reused. Expired connections may be closed lazily before reuse.
    73  	// If d < 0, connections are not closed due to a connection's age.
    74  	ConnMaxLifetime config.Duration `validate:"-"`
    75  
    76  	// ConnMaxIdleTime sets the maximum amount of time a connection may
    77  	// be idle. Expired connections may be closed lazily before reuse.
    78  	// If d < 0, connections are not closed due to a connection's idle
    79  	// time.
    80  	ConnMaxIdleTime config.Duration `validate:"-"`
    81  }
    82  
    83  // URL returns the DBConnect URL represented by this DBConfig object, loading it
    84  // from the file on disk. Leading and trailing whitespace is stripped.
    85  func (d *DBConfig) URL() (string, error) {
    86  	url, err := os.ReadFile(d.DBConnectFile)
    87  	return strings.TrimSpace(string(url)), err
    88  }
    89  
    90  type SMTPConfig struct {
    91  	PasswordConfig
    92  	Server   string `validate:"required"`
    93  	Port     string `validate:"required,numeric,min=1,max=65535"`
    94  	Username string `validate:"required"`
    95  }
    96  
    97  // PAConfig specifies how a policy authority should connect to its
    98  // database, what policies it should enforce, and what challenges
    99  // it should offer.
   100  type PAConfig struct {
   101  	DBConfig   `validate:"-"`
   102  	Challenges map[core.AcmeChallenge]bool `validate:"omitempty,dive,keys,oneof=http-01 dns-01 tls-alpn-01,endkeys"`
   103  }
   104  
   105  // CheckChallenges checks whether the list of challenges in the PA config
   106  // actually contains valid challenge names
   107  func (pc PAConfig) CheckChallenges() error {
   108  	if len(pc.Challenges) == 0 {
   109  		return errors.New("empty challenges map in the Policy Authority config is not allowed")
   110  	}
   111  	for c := range pc.Challenges {
   112  		if !c.IsValid() {
   113  			return fmt.Errorf("invalid challenge in PA config: %s", c)
   114  		}
   115  	}
   116  	return nil
   117  }
   118  
   119  // HostnamePolicyConfig specifies a file from which to load a policy regarding
   120  // what hostnames to issue for.
   121  type HostnamePolicyConfig struct {
   122  	HostnamePolicyFile string `validate:"required"`
   123  }
   124  
   125  // TLSConfig represents certificates and a key for authenticated TLS.
   126  type TLSConfig struct {
   127  	CertFile   string `validate:"required"`
   128  	KeyFile    string `validate:"required"`
   129  	CACertFile string `validate:"required"`
   130  }
   131  
   132  // Load reads and parses the certificates and key listed in the TLSConfig, and
   133  // returns a *tls.Config suitable for either client or server use. Prometheus
   134  // metrics for various certificate fields will be exported.
   135  func (t *TLSConfig) Load(scope prometheus.Registerer) (*tls.Config, error) {
   136  	if t == nil {
   137  		return nil, fmt.Errorf("nil TLS section in config")
   138  	}
   139  	if t.CertFile == "" {
   140  		return nil, fmt.Errorf("nil CertFile in TLSConfig")
   141  	}
   142  	if t.KeyFile == "" {
   143  		return nil, fmt.Errorf("nil KeyFile in TLSConfig")
   144  	}
   145  	if t.CACertFile == "" {
   146  		return nil, fmt.Errorf("nil CACertFile in TLSConfig")
   147  	}
   148  	caCertBytes, err := os.ReadFile(t.CACertFile)
   149  	if err != nil {
   150  		return nil, fmt.Errorf("reading CA cert from %q: %s", t.CACertFile, err)
   151  	}
   152  	rootCAs := x509.NewCertPool()
   153  	if ok := rootCAs.AppendCertsFromPEM(caCertBytes); !ok {
   154  		return nil, fmt.Errorf("parsing CA certs from %s failed", t.CACertFile)
   155  	}
   156  	cert, err := tls.LoadX509KeyPair(t.CertFile, t.KeyFile)
   157  	if err != nil {
   158  		return nil, fmt.Errorf("loading key pair from %q and %q: %s",
   159  			t.CertFile, t.KeyFile, err)
   160  	}
   161  
   162  	tlsNotBefore := prometheus.NewGaugeVec(
   163  		prometheus.GaugeOpts{
   164  			Name: "tlsconfig_notbefore_seconds",
   165  			Help: "TLS certificate NotBefore field expressed as Unix epoch time",
   166  		},
   167  		[]string{"serial"})
   168  	err = scope.Register(tlsNotBefore)
   169  	if err != nil {
   170  		are := prometheus.AlreadyRegisteredError{}
   171  		if errors.As(err, &are) {
   172  			tlsNotBefore = are.ExistingCollector.(*prometheus.GaugeVec)
   173  		} else {
   174  			return nil, err
   175  		}
   176  	}
   177  
   178  	tlsNotAfter := prometheus.NewGaugeVec(
   179  		prometheus.GaugeOpts{
   180  			Name: "tlsconfig_notafter_seconds",
   181  			Help: "TLS certificate NotAfter field expressed as Unix epoch time",
   182  		},
   183  		[]string{"serial"})
   184  	err = scope.Register(tlsNotAfter)
   185  	if err != nil {
   186  		are := prometheus.AlreadyRegisteredError{}
   187  		if errors.As(err, &are) {
   188  			tlsNotAfter = are.ExistingCollector.(*prometheus.GaugeVec)
   189  		} else {
   190  			return nil, err
   191  		}
   192  	}
   193  
   194  	leaf, err := x509.ParseCertificate(cert.Certificate[0])
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	serial := leaf.SerialNumber.String()
   200  	tlsNotBefore.WithLabelValues(serial).Set(float64(leaf.NotBefore.Unix()))
   201  	tlsNotAfter.WithLabelValues(serial).Set(float64(leaf.NotAfter.Unix()))
   202  
   203  	return &tls.Config{
   204  		RootCAs:      rootCAs,
   205  		ClientCAs:    rootCAs,
   206  		ClientAuth:   tls.RequireAndVerifyClientCert,
   207  		Certificates: []tls.Certificate{cert},
   208  		// Set the only acceptable TLS to v1.2 and v1.3.
   209  		MinVersion: tls.VersionTLS12,
   210  		MaxVersion: tls.VersionTLS13,
   211  		// CipherSuites will be ignored for TLS v1.3.
   212  		CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
   213  	}, nil
   214  }
   215  
   216  // SyslogConfig defines the config for syslogging.
   217  // 3 means "error", 4 means "warning", 6 is "info" and 7 is "debug".
   218  // Configuring a given level causes all messages at that level and below to
   219  // be logged.
   220  type SyslogConfig struct {
   221  	// When absent or zero, this causes no logs to be emitted on stdout/stderr.
   222  	// Errors and warnings will be emitted on stderr if the configured level
   223  	// allows.
   224  	StdoutLevel int `validate:"min=-1,max=7"`
   225  	// When absent or zero, this defaults to logging all messages of level 6
   226  	// or below. To disable syslog logging entirely, set this to -1.
   227  	SyslogLevel int `validate:"min=-1,max=7"`
   228  }
   229  
   230  // ServiceDomain contains the service and domain name the gRPC or bdns provider
   231  // will use to construct a SRV DNS query to lookup backends.
   232  type ServiceDomain struct {
   233  	// Service is the service name to be used for SRV lookups. For example: if
   234  	// record is 'foo.service.consul', then the Service is 'foo'.
   235  	Service string `validate:"required"`
   236  
   237  	// Domain is the domain name to be used for SRV lookups. For example: if the
   238  	// record is 'foo.service.consul', then the Domain is 'service.consul'.
   239  	Domain string `validate:"required"`
   240  }
   241  
   242  // GRPCClientConfig contains the information necessary to setup a gRPC client
   243  // connection. The following field combinations are allowed:
   244  //
   245  // ServerIPAddresses, [Timeout]
   246  // ServerAddress, DNSAuthority, [Timeout], [HostOverride]
   247  // SRVLookup, DNSAuthority, [Timeout], [HostOverride], [SRVResolver]
   248  // SRVLookups, DNSAuthority, [Timeout], [HostOverride], [SRVResolver]
   249  type GRPCClientConfig struct {
   250  	// DNSAuthority is a single <hostname|IPv4|[IPv6]>:<port> of the DNS server
   251  	// to be used for resolution of gRPC backends. If the address contains a
   252  	// hostname the gRPC client will resolve it via the system DNS. If the
   253  	// address contains a port, the client will use it directly, otherwise port
   254  	// 53 is used.
   255  	DNSAuthority string `validate:"required_with=SRVLookup SRVLookups,omitempty,ip|hostname|hostname_port"`
   256  
   257  	// SRVLookup contains the service and domain name the gRPC client will use
   258  	// to construct a SRV DNS query to lookup backends. For example: if the
   259  	// resource record is 'foo.service.consul', then the 'Service' is 'foo' and
   260  	// the 'Domain' is 'service.consul'. The expected dNSName to be
   261  	// authenticated in the server certificate would be 'foo.service.consul'.
   262  	//
   263  	// Note: The 'proto' field of the SRV record MUST contain 'tcp' and the
   264  	// 'port' field MUST be a valid port. In a Consul configuration file you
   265  	// would specify 'foo.service.consul' as:
   266  	//
   267  	// services {
   268  	//   id      = "some-unique-id-1"
   269  	//   name    = "foo"
   270  	//   address = "10.77.77.77"
   271  	//   port    = 8080
   272  	//   tags    = ["tcp"]
   273  	// }
   274  	// services {
   275  	//   id      = "some-unique-id-2"
   276  	//   name    = "foo"
   277  	//   address = "10.88.88.88"
   278  	//   port    = 8080
   279  	//   tags    = ["tcp"]
   280  	// }
   281  	//
   282  	// If you've added the above to your Consul configuration file (and reloaded
   283  	// Consul) then you should be able to resolve the following dig query:
   284  	//
   285  	// $ dig @10.55.55.10 -t SRV _foo._tcp.service.consul +short
   286  	// 1 1 8080 0a585858.addr.dc1.consul.
   287  	// 1 1 8080 0a4d4d4d.addr.dc1.consul.
   288  	SRVLookup *ServiceDomain `validate:"required_without_all=SRVLookups ServerAddress ServerIPAddresses"`
   289  
   290  	// SRVLookups allows you to pass multiple SRV records to the gRPC client.
   291  	// The gRPC client will resolves each SRV record and use the results to
   292  	// construct a list of backends to connect to. For more details, see the
   293  	// documentation for the SRVLookup field. Note: while you can pass multiple
   294  	// targets to the gRPC client using this field, all of the targets will use
   295  	// the same HostOverride and TLS configuration.
   296  	SRVLookups []*ServiceDomain `validate:"required_without_all=SRVLookup ServerAddress ServerIPAddresses"`
   297  
   298  	// SRVResolver is an optional override to indicate that a specific
   299  	// implementation of the SRV resolver should be used. The default is 'srv'
   300  	// For more details, see the documentation in:
   301  	// grpc/internal/resolver/dns/dns_resolver.go.
   302  	SRVResolver string `validate:"excluded_with=ServerAddress ServerIPAddresses,isdefault|oneof=srv nonce-srv"`
   303  
   304  	// ServerAddress is a single <hostname|IPv4|[IPv6]>:<port> or `:<port>` that
   305  	// the gRPC client will, if necessary, resolve via DNS and then connect to.
   306  	// If the address provided is 'foo.service.consul:8080' then the dNSName to
   307  	// be authenticated in the server certificate would be 'foo.service.consul'.
   308  	//
   309  	// In a Consul configuration file you would specify 'foo.service.consul' as:
   310  	//
   311  	// services {
   312  	//   id      = "some-unique-id-1"
   313  	//   name    = "foo"
   314  	//   address = "10.77.77.77"
   315  	// }
   316  	// services {
   317  	//   id      = "some-unique-id-2"
   318  	//   name    = "foo"
   319  	//   address = "10.88.88.88"
   320  	// }
   321  	//
   322  	// If you've added the above to your Consul configuration file (and reloaded
   323  	// Consul) then you should be able to resolve the following dig query:
   324  	//
   325  	// $ dig A @10.55.55.10 foo.service.consul +short
   326  	// 10.77.77.77
   327  	// 10.88.88.88
   328  	ServerAddress string `validate:"required_without_all=ServerIPAddresses SRVLookup SRVLookups,omitempty,hostname_port"`
   329  
   330  	// ServerIPAddresses is a comma separated list of IP addresses, in the
   331  	// format `<IPv4|[IPv6]>:<port>` or `:<port>`, that the gRPC client will
   332  	// connect to. If the addresses provided are ["10.77.77.77", "10.88.88.88"]
   333  	// then the iPAddress' to be authenticated in the server certificate would
   334  	// be '10.77.77.77' and '10.88.88.88'.
   335  	ServerIPAddresses []string `validate:"required_without_all=ServerAddress SRVLookup SRVLookups,omitempty,dive,hostname_port"`
   336  
   337  	// HostOverride is an optional override for the dNSName the client will
   338  	// verify in the certificate presented by the server.
   339  	HostOverride string `validate:"excluded_with=ServerIPAddresses,omitempty,hostname"`
   340  	Timeout      config.Duration
   341  
   342  	// NoWaitForReady turns off our (current) default of setting grpc.WaitForReady(true).
   343  	// This means if all of a GRPC client's backends are down, it will error immediately.
   344  	// The current default, grpc.WaitForReady(true), means that if all of a GRPC client's
   345  	// backends are down, it will wait until either one becomes available or the RPC
   346  	// times out.
   347  	NoWaitForReady bool
   348  }
   349  
   350  // MakeTargetAndHostOverride constructs the target URI that the gRPC client will
   351  // connect to and the hostname (only for 'ServerAddress' and 'SRVLookup') that
   352  // will be validated during the mTLS handshake. An error is returned if the
   353  // provided configuration is invalid.
   354  func (c *GRPCClientConfig) MakeTargetAndHostOverride() (string, string, error) {
   355  	var hostOverride string
   356  	if c.ServerAddress != "" {
   357  		if c.ServerIPAddresses != nil || c.SRVLookup != nil {
   358  			return "", "", errors.New(
   359  				"both 'serverAddress' and 'serverIPAddresses' or 'SRVLookup' in gRPC client config. Only one should be provided",
   360  			)
   361  		}
   362  		// Lookup backends using DNS A records.
   363  		targetHost, _, err := net.SplitHostPort(c.ServerAddress)
   364  		if err != nil {
   365  			return "", "", err
   366  		}
   367  
   368  		hostOverride = targetHost
   369  		if c.HostOverride != "" {
   370  			hostOverride = c.HostOverride
   371  		}
   372  		return fmt.Sprintf("dns://%s/%s", c.DNSAuthority, c.ServerAddress), hostOverride, nil
   373  
   374  	} else if c.SRVLookup != nil {
   375  		if c.DNSAuthority == "" {
   376  			return "", "", errors.New("field 'dnsAuthority' is required in gRPC client config with SRVLookup")
   377  		}
   378  		scheme, err := c.makeSRVScheme()
   379  		if err != nil {
   380  			return "", "", err
   381  		}
   382  		if c.ServerIPAddresses != nil {
   383  			return "", "", errors.New(
   384  				"both 'SRVLookup' and 'serverIPAddresses' in gRPC client config. Only one should be provided",
   385  			)
   386  		}
   387  		// Lookup backends using DNS SRV records.
   388  		targetHost := c.SRVLookup.Service + "." + c.SRVLookup.Domain
   389  
   390  		hostOverride = targetHost
   391  		if c.HostOverride != "" {
   392  			hostOverride = c.HostOverride
   393  		}
   394  		return fmt.Sprintf("%s://%s/%s", scheme, c.DNSAuthority, targetHost), hostOverride, nil
   395  
   396  	} else if c.SRVLookups != nil {
   397  		if c.DNSAuthority == "" {
   398  			return "", "", errors.New("field 'dnsAuthority' is required in gRPC client config with SRVLookups")
   399  		}
   400  		scheme, err := c.makeSRVScheme()
   401  		if err != nil {
   402  			return "", "", err
   403  		}
   404  		if c.ServerIPAddresses != nil {
   405  			return "", "", errors.New(
   406  				"both 'SRVLookups' and 'serverIPAddresses' in gRPC client config. Only one should be provided",
   407  			)
   408  		}
   409  		// Lookup backends using multiple DNS SRV records.
   410  		var targetHosts []string
   411  		for _, s := range c.SRVLookups {
   412  			targetHosts = append(targetHosts, s.Service+"."+s.Domain)
   413  		}
   414  		if c.HostOverride != "" {
   415  			hostOverride = c.HostOverride
   416  		}
   417  		return fmt.Sprintf("%s://%s/%s", scheme, c.DNSAuthority, strings.Join(targetHosts, ",")), hostOverride, nil
   418  
   419  	} else {
   420  		if c.ServerIPAddresses == nil {
   421  			return "", "", errors.New(
   422  				"neither 'serverAddress', 'SRVLookup', 'SRVLookups' nor 'serverIPAddresses' in gRPC client config. One should be provided",
   423  			)
   424  		}
   425  		// Specify backends as a list of IP addresses.
   426  		return "static:///" + strings.Join(c.ServerIPAddresses, ","), "", nil
   427  	}
   428  }
   429  
   430  // makeSRVScheme returns the scheme to use for SRV lookups. If the SRVResolver
   431  // field is empty, it returns "srv". Otherwise it checks that the specified
   432  // SRVResolver is registered with the gRPC runtime and returns it.
   433  func (c *GRPCClientConfig) makeSRVScheme() (string, error) {
   434  	if c.SRVResolver == "" {
   435  		return "srv", nil
   436  	}
   437  	rb := resolver.Get(c.SRVResolver)
   438  	if rb == nil {
   439  		return "", fmt.Errorf("resolver %q is not registered", c.SRVResolver)
   440  	}
   441  	return c.SRVResolver, nil
   442  }
   443  
   444  // GRPCServerConfig contains the information needed to start a gRPC server.
   445  type GRPCServerConfig struct {
   446  	Address string `json:"address" validate:"hostname_port"`
   447  	// Services is a map of service names to configuration specific to that service.
   448  	// These service names must match the service names advertised by gRPC itself,
   449  	// which are identical to the names set in our gRPC .proto files prefixed by
   450  	// the package names set in those files (e.g. "ca.CertificateAuthority").
   451  	Services map[string]GRPCServiceConfig `json:"services" validate:"required,dive,required"`
   452  	// MaxConnectionAge specifies how long a connection may live before the server sends a GoAway to the
   453  	// client. Because gRPC connections re-resolve DNS after a connection close,
   454  	// this controls how long it takes before a client learns about changes to its
   455  	// backends.
   456  	// https://pkg.go.dev/google.golang.org/grpc/keepalive#ServerParameters
   457  	MaxConnectionAge config.Duration `validate:"required"`
   458  }
   459  
   460  // GRPCServiceConfig contains the information needed to configure a gRPC service.
   461  type GRPCServiceConfig struct {
   462  	// PerServiceClientNames is a map of gRPC service names to client certificate
   463  	// SANs. The upstream listening server will reject connections from clients
   464  	// which do not appear in this list, and the server interceptor will reject
   465  	// RPC calls for this service from clients which are not listed here.
   466  	ClientNames []string `json:"clientNames" validate:"min=1,dive,hostname,required"`
   467  }
   468  
   469  // OpenTelemetryConfig configures tracing via OpenTelemetry.
   470  // To enable tracing, set a nonzero SampleRatio and configure an Endpoint
   471  type OpenTelemetryConfig struct {
   472  	// Endpoint to connect to with the OTLP protocol over gRPC.
   473  	// It should be of the form "localhost:4317"
   474  	//
   475  	// It always connects over plaintext, and so is only intended to connect
   476  	// to a local OpenTelemetry collector. This should not be used over an
   477  	// insecure network.
   478  	Endpoint string
   479  
   480  	// SampleRatio is the ratio of new traces to head sample.
   481  	// This only affects new traces without a parent with its own sampling
   482  	// decision, and otherwise use the parent's sampling decision.
   483  	//
   484  	// Set to something between 0 and 1, where 1 is sampling all traces.
   485  	// This is primarily meant as a pressure relief if the Endpoint we connect to
   486  	// is being overloaded, and we otherwise handle sampling in the collectors.
   487  	// See otel trace.ParentBased and trace.TraceIDRatioBased for details.
   488  	SampleRatio float64
   489  }
   490  
   491  // OpenTelemetryHTTPConfig configures the otelhttp server tracing.
   492  type OpenTelemetryHTTPConfig struct {
   493  	// TrustIncomingSpans should only be set true if there's a trusted service
   494  	// connecting to Boulder, such as a load balancer that's tracing-aware.
   495  	// If false, the default, incoming traces won't be set as the parent.
   496  	// See otelhttp.WithPublicEndpoint
   497  	TrustIncomingSpans bool
   498  }
   499  
   500  // Options returns the otelhttp options for this configuration. They can be
   501  // passed to otelhttp.NewHandler or Boulder's wrapper, measured_http.New.
   502  func (c *OpenTelemetryHTTPConfig) Options() []otelhttp.Option {
   503  	var options []otelhttp.Option
   504  	if !c.TrustIncomingSpans {
   505  		options = append(options, otelhttp.WithPublicEndpoint())
   506  	}
   507  	return options
   508  }
   509  
   510  // DNSProvider contains the configuration for a DNS provider in the bdns package
   511  // which supports dynamic reloading of its backends.
   512  type DNSProvider struct {
   513  	// DNSAuthority is the single <hostname|IPv4|[IPv6]>:<port> of the DNS
   514  	// server to be used for resolution of DNS backends. If the address contains
   515  	// a hostname it will be resolved via the system DNS. If the port is left
   516  	// unspecified it will default to '53'. If this field is left unspecified
   517  	// the system DNS will be used for resolution of DNS backends.
   518  	DNSAuthority string `validate:"required,ip|hostname|hostname_port"`
   519  
   520  	// SRVLookup contains the service and domain name used to construct a SRV
   521  	// DNS query to lookup DNS backends. 'Domain' is required. 'Service' is
   522  	// optional and will be defaulted to 'dns' if left unspecified.
   523  	//
   524  	// Usage: If the resource record is 'unbound.service.consul', then the
   525  	// 'Service' is 'unbound' and the 'Domain' is 'service.consul'. The expected
   526  	// dNSName to be authenticated in the server certificate would be
   527  	// 'unbound.service.consul'. The 'proto' field of the SRV record MUST
   528  	// contain 'udp' and the 'port' field MUST be a valid port. In a Consul
   529  	// configuration file you would specify 'unbound.service.consul' as:
   530  	//
   531  	// services {
   532  	//   id      = "unbound-1" // Must be unique
   533  	//   name    = "unbound"
   534  	//   address = "10.77.77.77"
   535  	//   port    = 53
   536  	//   tags    = ["udp"]
   537  	// }
   538  	//
   539  	// services {
   540  	//   id      = "unbound-2" // Must be unique
   541  	//   name    = "unbound"
   542  	//   address = "10.88.88.88"
   543  	//   port    = 53
   544  	//   tags    = ["udp"]
   545  	// }
   546  	//
   547  	// If you've added the above to your Consul configuration file (and reloaded
   548  	// Consul) then you should be able to resolve the following dig query:
   549  	//
   550  	// $ dig @10.55.55.10 -t SRV _unbound._udp.service.consul +short
   551  	// 1 1 53 0a585858.addr.dc1.consul.
   552  	// 1 1 53 0a4d4d4d.addr.dc1.consul.
   553  	SRVLookup ServiceDomain `validate:"required"`
   554  }
   555  

View as plain text