github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/base/addr_validation.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package base
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"crypto/x509"
    17  	"fmt"
    18  	"net"
    19  	"os"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/cockroachdb/cockroach/pkg/util/log"
    24  	"github.com/cockroachdb/errors"
    25  )
    26  
    27  // ValidateAddrs controls the address fields in the Config object
    28  // and "fills in" the blanks:
    29  // - the host part of Addr and HTTPAddr is resolved to an IP address
    30  //   if specified (it stays blank if blank to mean "all addresses").
    31  // - the host part of AdvertiseAddr is filled in if blank, either
    32  //   from Addr if non-empty or os.Hostname(). It is also checked
    33  //   for resolvability.
    34  // - non-numeric port numbers are resolved to numeric.
    35  //
    36  // The addresses fields must be guaranteed by the caller to either be
    37  // completely empty, or have both a host part and a port part
    38  // separated by a colon. In the latter case either can be empty to
    39  // indicate it's left unspecified.
    40  func (cfg *Config) ValidateAddrs(ctx context.Context) error {
    41  	// Validate the advertise address.
    42  	advHost, advPort, err := validateAdvertiseAddr(ctx,
    43  		cfg.AdvertiseAddr, "--listen-addr", cfg.Addr, "")
    44  	if err != nil {
    45  		return errors.Wrap(err, "invalid --advertise-addr")
    46  	}
    47  	cfg.AdvertiseAddr = net.JoinHostPort(advHost, advPort)
    48  
    49  	// Validate the RPC listen address.
    50  	listenHost, listenPort, err := validateListenAddr(ctx, cfg.Addr, "")
    51  	if err != nil {
    52  		return errors.Wrap(err, "invalid --listen-addr")
    53  	}
    54  	cfg.Addr = net.JoinHostPort(listenHost, listenPort)
    55  
    56  	// Validate the SQL advertise address. Use the provided advertise
    57  	// addr as default.
    58  	advSQLHost, advSQLPort, err := validateAdvertiseAddr(ctx,
    59  		cfg.SQLAdvertiseAddr, "--sql-addr", cfg.SQLAddr, advHost)
    60  	if err != nil {
    61  		return errors.Wrap(err, "invalid --advertise-sql-addr")
    62  	}
    63  	cfg.SQLAdvertiseAddr = net.JoinHostPort(advSQLHost, advSQLPort)
    64  
    65  	// Validate the SQL listen address - use the resolved listen addr as default.
    66  	sqlHost, sqlPort, err := validateListenAddr(ctx, cfg.SQLAddr, listenHost)
    67  	if err != nil {
    68  		return errors.Wrap(err, "invalid --sql-addr")
    69  	}
    70  	cfg.SQLAddr = net.JoinHostPort(sqlHost, sqlPort)
    71  
    72  	// Validate the HTTP advertise address. Use the provided advertise
    73  	// addr as default.
    74  	advHTTPHost, advHTTPPort, err := validateAdvertiseAddr(ctx,
    75  		cfg.HTTPAdvertiseAddr, "--http-addr", cfg.HTTPAddr, advHost)
    76  	if err != nil {
    77  		return errors.Wrap(err, "cannot compute public HTTP address")
    78  	}
    79  	cfg.HTTPAdvertiseAddr = net.JoinHostPort(advHTTPHost, advHTTPPort)
    80  
    81  	// Validate the HTTP address -- use the resolved listen addr
    82  	// as default.
    83  	httpHost, httpPort, err := validateListenAddr(ctx, cfg.HTTPAddr, listenHost)
    84  	if err != nil {
    85  		return errors.Wrap(err, "invalid --http-addr")
    86  	}
    87  	cfg.HTTPAddr = net.JoinHostPort(httpHost, httpPort)
    88  	return nil
    89  }
    90  
    91  // UpdateAddrs updates the listen and advertise port numbers with
    92  // those found during the call to net.Listen().
    93  //
    94  // After ValidateAddrs() the actual listen addr should be equal to the
    95  // one requested; only the port number can change because of
    96  // auto-allocation. We do check this equality here and report a
    97  // warning if any discrepancy is found.
    98  func UpdateAddrs(ctx context.Context, addr, advAddr *string, ln net.Addr) error {
    99  	desiredHost, _, err := net.SplitHostPort(*addr)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	// Update the listen port number and check the actual listen addr is
   105  	// the one requested.
   106  	lnAddr := ln.String()
   107  	lnHost, lnPort, err := net.SplitHostPort(lnAddr)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	requestedAll := (desiredHost == "" || desiredHost == "0.0.0.0" || desiredHost == "::")
   112  	listenedAll := (lnHost == "" || lnHost == "0.0.0.0" || lnHost == "::")
   113  	if (requestedAll && !listenedAll) || (!requestedAll && desiredHost != lnHost) {
   114  		log.Warningf(ctx, "requested to listen on %q, actually listening on %q", desiredHost, lnHost)
   115  	}
   116  	*addr = net.JoinHostPort(lnHost, lnPort)
   117  
   118  	// Update the advertised port number if it wasn't set to start
   119  	// with. We don't touch the advertised host, as this may have
   120  	// nothing to do with the listen address.
   121  	advHost, advPort, err := net.SplitHostPort(*advAddr)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	if advPort == "" || advPort == "0" {
   126  		advPort = lnPort
   127  	}
   128  	*advAddr = net.JoinHostPort(advHost, advPort)
   129  	return nil
   130  }
   131  
   132  // validateAdvertiseAddr validates an normalizes an address suitable
   133  // for use in gossiping - for use by other nodes. This ensures
   134  // that if the "host" part is empty, it gets filled in with
   135  // the configured listen address if any, or the canonical host name.
   136  func validateAdvertiseAddr(
   137  	ctx context.Context, advAddr, flag, listenAddr, defaultHost string,
   138  ) (string, string, error) {
   139  	listenHost, listenPort, err := getListenAddr(listenAddr, defaultHost)
   140  	if err != nil {
   141  		return "", "", errors.Wrapf(err, "invalid %s", flag)
   142  	}
   143  
   144  	advHost, advPort := "", ""
   145  	if advAddr != "" {
   146  		var err error
   147  		advHost, advPort, err = net.SplitHostPort(advAddr)
   148  		if err != nil {
   149  			return "", "", err
   150  		}
   151  	}
   152  	// If there was no port number, reuse the one from the listen
   153  	// address.
   154  	if advPort == "" || advPort == "0" {
   155  		advPort = listenPort
   156  	}
   157  	// Resolve non-numeric to numeric.
   158  	portNumber, err := net.DefaultResolver.LookupPort(ctx, "tcp", advPort)
   159  	if err != nil {
   160  		return "", "", err
   161  	}
   162  	advPort = strconv.Itoa(portNumber)
   163  
   164  	// If the advertise host is empty, then we have two cases.
   165  	if advHost == "" {
   166  		if listenHost != "" {
   167  			// If the listen address was non-empty (ie. explicit, not
   168  			// "listen on all addresses"), use that.
   169  			advHost = listenHost
   170  		} else {
   171  			// No specific listen address, use the canonical host name.
   172  			var err error
   173  			advHost, err = os.Hostname()
   174  			if err != nil {
   175  				return "", "", err
   176  			}
   177  
   178  			// As a sanity check, verify that the canonical host name
   179  			// properly resolves. It's not the full story (it could resolve
   180  			// locally but not elsewhere) but at least it prevents typos.
   181  			_, err = net.DefaultResolver.LookupIPAddr(ctx, advHost)
   182  			if err != nil {
   183  				return "", "", err
   184  			}
   185  		}
   186  	}
   187  	return advHost, advPort, nil
   188  }
   189  
   190  // validateListenAddr validates and normalizes an address suitable for
   191  // use with net.Listen(). This accepts an empty "host" part as "listen
   192  // on all interfaces" and resolves host names to IP addresses.
   193  func validateListenAddr(ctx context.Context, addr, defaultHost string) (string, string, error) {
   194  	host, port, err := getListenAddr(addr, defaultHost)
   195  	if err != nil {
   196  		return "", "", err
   197  	}
   198  	return resolveAddr(ctx, host, port)
   199  }
   200  
   201  func getListenAddr(addr, defaultHost string) (string, string, error) {
   202  	host, port := "", ""
   203  	if addr != "" {
   204  		var err error
   205  		host, port, err = net.SplitHostPort(addr)
   206  		if err != nil {
   207  			return "", "", err
   208  		}
   209  	}
   210  	if host == "" {
   211  		host = defaultHost
   212  	}
   213  	if port == "" {
   214  		port = "0"
   215  	}
   216  	return host, port, nil
   217  }
   218  
   219  // resolveAddr resolves non-numeric references to numeric references.
   220  func resolveAddr(ctx context.Context, host, port string) (string, string, error) {
   221  	resolver := net.DefaultResolver
   222  
   223  	// Resolve the port number. This may translate service names
   224  	// e.g. "postgresql" to a numeric value.
   225  	portNumber, err := resolver.LookupPort(ctx, "tcp", port)
   226  	if err != nil {
   227  		return "", "", err
   228  	}
   229  	port = strconv.Itoa(portNumber)
   230  
   231  	// Resolve the address.
   232  	if host == "" {
   233  		// Keep empty. This means "listen on all addresses".
   234  		return host, port, nil
   235  	}
   236  
   237  	addr, err := LookupAddr(ctx, resolver, host)
   238  	return addr, port, err
   239  }
   240  
   241  // LookupAddr resolves the given address/host to an IP address. If
   242  // multiple addresses are resolved, it returns the first IPv4 address
   243  // available if there is one, otherwise the first address.
   244  func LookupAddr(ctx context.Context, resolver *net.Resolver, host string) (string, error) {
   245  	// Resolve the IP address or hostname to an IP address.
   246  	addrs, err := resolver.LookupIPAddr(ctx, host)
   247  	if err != nil {
   248  		return "", err
   249  	}
   250  	if len(addrs) == 0 {
   251  		return "", fmt.Errorf("cannot resolve %q to an address", host)
   252  	}
   253  
   254  	// TODO(knz): the remainder function can be changed to return all
   255  	// resolved addresses once the server is taught to listen on
   256  	// multiple interfaces. #5816
   257  
   258  	// LookupIPAddr() can return a mix of IPv6 and IPv4
   259  	// addresses. Conventionally, the first resolved address is
   260  	// "preferred"; however, for compatibility with previous CockroachDB
   261  	// versions, we still prefer an IPv4 address if there is one.
   262  	for _, addr := range addrs {
   263  		if ip := addr.IP.To4(); ip != nil {
   264  			return ip.String(), nil
   265  		}
   266  	}
   267  	// No IPv4 address, return the first resolved address instead.
   268  	return addrs[0].String(), nil
   269  }
   270  
   271  // CheckCertificateAddrs validates the addresses inside the configured
   272  // certificates to be compatible with the configured listen and
   273  // advertise addresses. This is an advisory function (to inform/educate
   274  // the user) and not a requirement for security.
   275  // This must also be called after ValidateAddrs() and after
   276  // the certificate manager was initialized.
   277  func (cfg *Config) CheckCertificateAddrs(ctx context.Context) {
   278  	if cfg.Insecure {
   279  		return
   280  	}
   281  
   282  	// By now the certificate manager must be initialized.
   283  	cm, _ := cfg.GetCertificateManager()
   284  
   285  	// Verify that the listen and advertise addresses are compatible
   286  	// with the provided certificate.
   287  	certInfo := cm.NodeCert()
   288  	if certInfo.Error != nil {
   289  		log.Shoutf(ctx, log.Severity_ERROR,
   290  			"invalid node certificate: %v", certInfo.Error)
   291  	} else {
   292  		cert := certInfo.ParsedCertificates[0]
   293  		addrInfo := certAddrs(cert)
   294  
   295  		// Log the certificate details in any case. This will aid during troubleshooting.
   296  		log.Infof(ctx, "server certificate addresses: %s", addrInfo)
   297  
   298  		var msg bytes.Buffer
   299  		// Verify the compatibility. This requires that ValidateAddrs() has
   300  		// been called already.
   301  		host, _, err := net.SplitHostPort(cfg.AdvertiseAddr)
   302  		if err != nil {
   303  			panic("programming error: call ValidateAddrs() first")
   304  		}
   305  		if err := cert.VerifyHostname(host); err != nil {
   306  			fmt.Fprintf(&msg, "advertise address %q not in node certificate (%s)\n", host, addrInfo)
   307  		}
   308  		host, _, err = net.SplitHostPort(cfg.SQLAdvertiseAddr)
   309  		if err != nil {
   310  			panic("programming error: call ValidateAddrs() first")
   311  		}
   312  		if err := cert.VerifyHostname(host); err != nil {
   313  			fmt.Fprintf(&msg, "advertise SQL address %q not in node certificate (%s)\n", host, addrInfo)
   314  		}
   315  		if msg.Len() > 0 {
   316  			log.Shoutf(ctx, log.Severity_WARNING,
   317  				"%s"+
   318  					"Secure client connections are likely to fail.\n"+
   319  					"Consider extending the node certificate or tweak --listen-addr/--advertise-addr/--sql-addr/--advertise-sql-addr.",
   320  				msg.String())
   321  		}
   322  	}
   323  
   324  	// Verify that the http listen and advertise addresses are
   325  	// compatible with the provided certificate.
   326  	certInfo = cm.UICert()
   327  	if certInfo == nil {
   328  		// A nil UI cert means use the node cert instead;
   329  		// see details in (*CertificateManager) getEmbeddedUIServerTLSConfig()
   330  		// and (*CertificateManager) getUICertLocked().
   331  		certInfo = cm.NodeCert()
   332  	}
   333  	if certInfo.Error != nil {
   334  		log.Shoutf(ctx, log.Severity_ERROR,
   335  			"invalid UI certificate: %v", certInfo.Error)
   336  	} else {
   337  		cert := certInfo.ParsedCertificates[0]
   338  		addrInfo := certAddrs(cert)
   339  
   340  		// Log the certificate details in any case. This will aid during
   341  		// troubleshooting.
   342  		log.Infof(ctx, "web UI certificate addresses: %s", addrInfo)
   343  	}
   344  }
   345  
   346  // certAddrs formats the list of addresses included in a certificate for
   347  // printing in an error message.
   348  func certAddrs(cert *x509.Certificate) string {
   349  	// If an IP address was specified as listen/adv address, the
   350  	// hostname validation will only use the IPAddresses field. So this
   351  	// needs to be printed in all cases.
   352  	addrs := make([]string, len(cert.IPAddresses))
   353  	for i, ip := range cert.IPAddresses {
   354  		addrs[i] = ip.String()
   355  	}
   356  	// For names, the hostname validation will use DNSNames if
   357  	// the Subject Alt Name is present in the cert, otherwise
   358  	// it will use the common name. We can't parse the
   359  	// extensions here so we print both.
   360  	return fmt.Sprintf("IP=%s; DNS=%s; CN=%s",
   361  		strings.Join(addrs, ","),
   362  		strings.Join(cert.DNSNames, ","),
   363  		cert.Subject.CommonName)
   364  }