123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- /*
- *
- * Copyright 2023 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
- // Package fallback provides default implementations of fallback options when S2A fails.
- package fallback
- import (
- "context"
- "crypto/tls"
- "fmt"
- "net"
- "google.golang.org/grpc/credentials"
- "google.golang.org/grpc/grpclog"
- )
- const (
- alpnProtoStrH2 = "h2"
- alpnProtoStrHTTP = "http/1.1"
- defaultHTTPSPort = "443"
- )
- // FallbackTLSConfigGRPC is a tls.Config used by the DefaultFallbackClientHandshakeFunc function.
- // It supports GRPC use case, thus the alpn is set to 'h2'.
- var FallbackTLSConfigGRPC = tls.Config{
- MinVersion: tls.VersionTLS13,
- ClientSessionCache: nil,
- NextProtos: []string{alpnProtoStrH2},
- }
- // FallbackTLSConfigHTTP is a tls.Config used by the DefaultFallbackDialerAndAddress func.
- // It supports the HTTP use case and the alpn is set to both 'http/1.1' and 'h2'.
- var FallbackTLSConfigHTTP = tls.Config{
- MinVersion: tls.VersionTLS13,
- ClientSessionCache: nil,
- NextProtos: []string{alpnProtoStrH2, alpnProtoStrHTTP},
- }
- // ClientHandshake establishes a TLS connection and returns it, plus its auth info.
- // Inputs:
- //
- // targetServer: the server attempted with S2A.
- // conn: the tcp connection to the server at address targetServer that was passed into S2A's ClientHandshake func.
- // If fallback is successful, the `conn` should be closed.
- // err: the error encountered when performing the client-side TLS handshake with S2A.
- type ClientHandshake func(ctx context.Context, targetServer string, conn net.Conn, err error) (net.Conn, credentials.AuthInfo, error)
- // DefaultFallbackClientHandshakeFunc returns a ClientHandshake function,
- // which establishes a TLS connection to the provided fallbackAddr, returns the new connection and its auth info.
- // Example use:
- //
- // transportCreds, _ = s2a.NewClientCreds(&s2a.ClientOptions{
- // S2AAddress: s2aAddress,
- // FallbackOpts: &s2a.FallbackOptions{ // optional
- // FallbackClientHandshakeFunc: fallback.DefaultFallbackClientHandshakeFunc(fallbackAddr),
- // },
- // })
- //
- // The fallback server's certificate must be verifiable using OS root store.
- // The fallbackAddr is expected to be a network address, e.g. example.com:port. If port is not specified,
- // it uses default port 443.
- // In the returned function's TLS config, ClientSessionCache is explicitly set to nil to disable TLS resumption,
- // and min TLS version is set to 1.3.
- func DefaultFallbackClientHandshakeFunc(fallbackAddr string) (ClientHandshake, error) {
- var fallbackDialer = tls.Dialer{Config: &FallbackTLSConfigGRPC}
- return defaultFallbackClientHandshakeFuncInternal(fallbackAddr, fallbackDialer.DialContext)
- }
- func defaultFallbackClientHandshakeFuncInternal(fallbackAddr string, dialContextFunc func(context.Context, string, string) (net.Conn, error)) (ClientHandshake, error) {
- fallbackServerAddr, err := processFallbackAddr(fallbackAddr)
- if err != nil {
- if grpclog.V(1) {
- grpclog.Infof("error processing fallback address [%s]: %v", fallbackAddr, err)
- }
- return nil, err
- }
- return func(ctx context.Context, targetServer string, conn net.Conn, s2aErr error) (net.Conn, credentials.AuthInfo, error) {
- fbConn, fbErr := dialContextFunc(ctx, "tcp", fallbackServerAddr)
- if fbErr != nil {
- grpclog.Infof("dialing to fallback server %s failed: %v", fallbackServerAddr, fbErr)
- return nil, nil, fmt.Errorf("dialing to fallback server %s failed: %v; S2A client handshake with %s error: %w", fallbackServerAddr, fbErr, targetServer, s2aErr)
- }
- tc, success := fbConn.(*tls.Conn)
- if !success {
- grpclog.Infof("the connection with fallback server is expected to be tls but isn't")
- return nil, nil, fmt.Errorf("the connection with fallback server is expected to be tls but isn't; S2A client handshake with %s error: %w", targetServer, s2aErr)
- }
- tlsInfo := credentials.TLSInfo{
- State: tc.ConnectionState(),
- CommonAuthInfo: credentials.CommonAuthInfo{
- SecurityLevel: credentials.PrivacyAndIntegrity,
- },
- }
- if grpclog.V(1) {
- grpclog.Infof("ConnectionState.NegotiatedProtocol: %v", tc.ConnectionState().NegotiatedProtocol)
- grpclog.Infof("ConnectionState.HandshakeComplete: %v", tc.ConnectionState().HandshakeComplete)
- grpclog.Infof("ConnectionState.ServerName: %v", tc.ConnectionState().ServerName)
- }
- conn.Close()
- return fbConn, tlsInfo, nil
- }, nil
- }
- // DefaultFallbackDialerAndAddress returns a TLS dialer and the network address to dial.
- // Example use:
- //
- // fallbackDialer, fallbackServerAddr := fallback.DefaultFallbackDialerAndAddress(fallbackAddr)
- // dialTLSContext := s2a.NewS2aDialTLSContextFunc(&s2a.ClientOptions{
- // S2AAddress: s2aAddress, // required
- // FallbackOpts: &s2a.FallbackOptions{
- // FallbackDialer: &s2a.FallbackDialer{
- // Dialer: fallbackDialer,
- // ServerAddr: fallbackServerAddr,
- // },
- // },
- // })
- //
- // The fallback server's certificate should be verifiable using OS root store.
- // The fallbackAddr is expected to be a network address, e.g. example.com:port. If port is not specified,
- // it uses default port 443.
- // In the returned function's TLS config, ClientSessionCache is explicitly set to nil to disable TLS resumption,
- // and min TLS version is set to 1.3.
- func DefaultFallbackDialerAndAddress(fallbackAddr string) (*tls.Dialer, string, error) {
- fallbackServerAddr, err := processFallbackAddr(fallbackAddr)
- if err != nil {
- if grpclog.V(1) {
- grpclog.Infof("error processing fallback address [%s]: %v", fallbackAddr, err)
- }
- return nil, "", err
- }
- return &tls.Dialer{Config: &FallbackTLSConfigHTTP}, fallbackServerAddr, nil
- }
- func processFallbackAddr(fallbackAddr string) (string, error) {
- var fallbackServerAddr string
- var err error
- if fallbackAddr == "" {
- return "", fmt.Errorf("empty fallback address")
- }
- _, _, err = net.SplitHostPort(fallbackAddr)
- if err != nil {
- // fallbackAddr does not have port suffix
- fallbackServerAddr = net.JoinHostPort(fallbackAddr, defaultHTTPSPort)
- } else {
- // FallbackServerAddr already has port suffix
- fallbackServerAddr = fallbackAddr
- }
- return fallbackServerAddr, nil
- }
|