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 }