...

Source file src/go.etcd.io/etcd/client/v3/internal/endpoint/endpoint.go

Documentation: go.etcd.io/etcd/client/v3/internal/endpoint

     1  // Copyright 2021 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package endpoint
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"net/url"
    21  	"path"
    22  	"strings"
    23  )
    24  
    25  type CredsRequirement int
    26  
    27  const (
    28  	// CREDS_REQUIRE - Credentials/certificate required for thi type of connection.
    29  	CREDS_REQUIRE CredsRequirement = iota
    30  	// CREDS_DROP - Credentials/certificate not needed and should get ignored.
    31  	CREDS_DROP
    32  	// CREDS_OPTIONAL - Credentials/certificate might be used if supplied
    33  	CREDS_OPTIONAL
    34  )
    35  
    36  func extractHostFromHostPort(ep string) string {
    37  	host, _, err := net.SplitHostPort(ep)
    38  	if err != nil {
    39  		return ep
    40  	}
    41  	return host
    42  }
    43  
    44  // mustSplit2 returns the values from strings.SplitN(s, sep, 2).
    45  // If sep is not found, it returns ("", "", false) instead.
    46  func mustSplit2(s, sep string) (string, string) {
    47  	spl := strings.SplitN(s, sep, 2)
    48  	if len(spl) < 2 {
    49  		panic(fmt.Errorf("token '%v' expected to have separator sep: `%v`", s, sep))
    50  	}
    51  	return spl[0], spl[1]
    52  }
    53  
    54  func schemeToCredsRequirement(schema string) CredsRequirement {
    55  	switch schema {
    56  	case "https", "unixs":
    57  		return CREDS_REQUIRE
    58  	case "http":
    59  		return CREDS_DROP
    60  	case "unix":
    61  		// Preserving previous behavior from:
    62  		// https://github.com/etcd-io/etcd/blob/dae29bb719dd69dc119146fc297a0628fcc1ccf8/client/v3/client.go#L212
    63  		// that likely was a bug due to missing 'fallthrough'.
    64  		// At the same time it seems legit to let the users decide whether they
    65  		// want credential control or not (and 'unixs' schema is not a standard thing).
    66  		return CREDS_OPTIONAL
    67  	case "":
    68  		return CREDS_OPTIONAL
    69  	default:
    70  		return CREDS_OPTIONAL
    71  	}
    72  }
    73  
    74  // This function translates endpoints names supported by etcd server into
    75  // endpoints as supported by grpc with additional information
    76  // (server_name for cert validation, requireCreds - whether certs are needed).
    77  // The main differences:
    78  //   - etcd supports unixs & https names as opposed to unix & http to
    79  //     distinguish need to configure certificates.
    80  //   - etcd support http(s) names as opposed to tcp supported by grpc/dial method.
    81  //   - etcd supports unix(s)://local-file naming schema
    82  //     (as opposed to unix:local-file canonical name used by grpc for current dir files).
    83  //   - Within the unix(s) schemas, the last segment (filename) without 'port' (content after colon)
    84  //     is considered serverName - to allow local testing of cert-protected communication.
    85  //
    86  // See more:
    87  //   - https://github.com/grpc/grpc-go/blob/26c143bd5f59344a4b8a1e491e0f5e18aa97abc7/internal/grpcutil/target.go#L47
    88  //   - https://golang.org/pkg/net/#Dial
    89  //   - https://github.com/grpc/grpc/blob/master/doc/naming.md
    90  func translateEndpoint(ep string) (addr string, serverName string, requireCreds CredsRequirement) {
    91  	if strings.HasPrefix(ep, "unix:") || strings.HasPrefix(ep, "unixs:") {
    92  		if strings.HasPrefix(ep, "unix:///") || strings.HasPrefix(ep, "unixs:///") {
    93  			// absolute path case
    94  			schema, absolutePath := mustSplit2(ep, "://")
    95  			return "unix://" + absolutePath, path.Base(absolutePath), schemeToCredsRequirement(schema)
    96  		}
    97  		if strings.HasPrefix(ep, "unix://") || strings.HasPrefix(ep, "unixs://") {
    98  			// legacy etcd local path
    99  			schema, localPath := mustSplit2(ep, "://")
   100  			return "unix:" + localPath, path.Base(localPath), schemeToCredsRequirement(schema)
   101  		}
   102  		schema, localPath := mustSplit2(ep, ":")
   103  		return "unix:" + localPath, path.Base(localPath), schemeToCredsRequirement(schema)
   104  	}
   105  
   106  	if strings.Contains(ep, "://") {
   107  		url, err := url.Parse(ep)
   108  		if err != nil {
   109  			return ep, ep, CREDS_OPTIONAL
   110  		}
   111  		if url.Scheme == "http" || url.Scheme == "https" {
   112  			return url.Host, url.Host, schemeToCredsRequirement(url.Scheme)
   113  		}
   114  		return ep, url.Host, schemeToCredsRequirement(url.Scheme)
   115  	}
   116  	// Handles plain addresses like 10.0.0.44:437.
   117  	return ep, ep, CREDS_OPTIONAL
   118  }
   119  
   120  // RequiresCredentials returns whether given endpoint requires
   121  // credentials/certificates for connection.
   122  func RequiresCredentials(ep string) CredsRequirement {
   123  	_, _, requireCreds := translateEndpoint(ep)
   124  	return requireCreds
   125  }
   126  
   127  // Interpret endpoint parses an endpoint of the form
   128  // (http|https)://<host>*|(unix|unixs)://<path>)
   129  // and returns low-level address (supported by 'net') to connect to,
   130  // and a server name used for x509 certificate matching.
   131  func Interpret(ep string) (address string, serverName string) {
   132  	addr, serverName, _ := translateEndpoint(ep)
   133  	return addr, serverName
   134  }
   135  

View as plain text