This allows arbitrary programs to have an easy mechanism to make connections through Boundary using Go. Internal cluster tests and test helpers have been migrated to use this package, as well as the `connect` command. --------- Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
101 lines
2.8 KiB
Go
101 lines
2.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package proxy
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
)
|
|
|
|
// clientTlsConfig creates a TLS configuration to connect to a worker proxy, or
|
|
// returns a cached config
|
|
//
|
|
// Supported options: WithWorkerHost. If provided will use that host (minus
|
|
// port) for the SNI header. Otherwise, it will use the first worker host
|
|
// provided in the session authorization data.
|
|
func (p *ClientProxy) clientTlsConfig(opt ...Option) (*tls.Config, error) {
|
|
if p.clientTlsConf != nil {
|
|
return p.clientTlsConf, nil
|
|
}
|
|
|
|
const op = "proxy.clientTlsConfig"
|
|
if p.sessionAuthzData == nil {
|
|
return nil, fmt.Errorf("%s: nil session authorization data", op)
|
|
}
|
|
if len(p.sessionAuthzData.WorkerInfo) == 0 {
|
|
return nil, fmt.Errorf("%s: no worker info", op)
|
|
}
|
|
|
|
opts, err := getOpts(opt...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: error getting options: %w", op, err)
|
|
}
|
|
|
|
parsedCert, err := x509.ParseCertificate(p.sessionAuthzData.Certificate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: unable to decode mTLS certificate: %w", op, err)
|
|
}
|
|
|
|
workerHostRaw := opts.WithWorkerHost
|
|
if workerHostRaw == "" {
|
|
workerHostRaw = p.sessionAuthzData.WorkerInfo[0].Address
|
|
}
|
|
workerHost, _, err := net.SplitHostPort(workerHostRaw)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "missing port") {
|
|
workerHost = workerHostRaw
|
|
} else {
|
|
return nil, fmt.Errorf("%s: error splitting worker host/port: %w", op, err)
|
|
}
|
|
}
|
|
|
|
certPool := x509.NewCertPool()
|
|
certPool.AddCert(parsedCert)
|
|
|
|
tlsConf := &tls.Config{
|
|
Certificates: []tls.Certificate{
|
|
{
|
|
Certificate: [][]byte{p.sessionAuthzData.Certificate},
|
|
PrivateKey: ed25519.PrivateKey(p.sessionAuthzData.PrivateKey),
|
|
Leaf: parsedCert,
|
|
},
|
|
},
|
|
ServerName: workerHost,
|
|
MinVersion: tls.VersionTLS13,
|
|
NextProtos: []string{"http/1.1", p.sessionAuthzData.SessionId},
|
|
|
|
// This is set this way so we can make use of VerifyConnection, which we
|
|
// set on this TLS config below. We are not skipping verification!
|
|
InsecureSkipVerify: true,
|
|
}
|
|
|
|
// We disable normal DNS SAN behavior as we don't rely on DNS or IP
|
|
// addresses for security and want to avoid issues with including localhost
|
|
// etc.
|
|
verifyOpts := x509.VerifyOptions{
|
|
DNSName: p.sessionAuthzData.SessionId,
|
|
Roots: certPool,
|
|
KeyUsages: []x509.ExtKeyUsage{
|
|
x509.ExtKeyUsageClientAuth,
|
|
x509.ExtKeyUsageServerAuth,
|
|
},
|
|
}
|
|
tlsConf.VerifyConnection = func(cs tls.ConnectionState) error {
|
|
// Go will not run this without at least one peer certificate, but
|
|
// doesn't hurt to check
|
|
if len(cs.PeerCertificates) == 0 {
|
|
return fmt.Errorf("%s: no peer certificates provided", op)
|
|
}
|
|
_, err := cs.PeerCertificates[0].Verify(verifyOpts)
|
|
return err
|
|
}
|
|
|
|
p.clientTlsConf = tlsConf
|
|
return tlsConf, nil
|
|
}
|