0e3018f2cf
Signed-off-by: gotgelf <gotgelf@gmail.com>
167 lines
6.5 KiB
Go
167 lines
6.5 KiB
Go
/*
|
|
*
|
|
* 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
|
|
}
|