You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/api/proxy/tls.go

101 lines
2.8 KiB

// 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
}