ContainerImage.Pinniped/internal/testutil/tlsserver/tlsserver.go

119 lines
3.0 KiB
Go
Raw Permalink Normal View History

// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package tlsserver
import (
"context"
"crypto/tls"
"encoding/pem"
"fmt"
"net"
"net/http"
"net/http/httptest"
"reflect"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/util/httpstream"
"go.pinniped.dev/internal/crypto/ptls"
)
type ctxKey int
const (
mapKey ctxKey = iota + 1
helloKey
)
func TLSTestServer(t *testing.T, handler http.Handler, f func(*httptest.Server)) *httptest.Server {
t.Helper()
server := httptest.NewUnstartedServer(handler)
server.TLS = ptls.Default(nil) // mimic API server config
if f != nil {
f(server)
}
server.StartTLS()
t.Cleanup(server.Close)
return server
}
func TLSTestServerCA(server *httptest.Server) []byte {
return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: server.Certificate().Raw,
})
}
func RecordTLSHello(server *httptest.Server) {
server.Config.ConnContext = func(ctx context.Context, _ net.Conn) context.Context {
return context.WithValue(ctx, mapKey, &sync.Map{})
}
server.TLS.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
m, ok := getCtxMap(info.Context())
if !ok {
return nil, fmt.Errorf("could not find ctx map")
}
if actual, loaded := m.LoadOrStore(helloKey, info); loaded && !reflect.DeepEqual(info, actual) {
return nil, fmt.Errorf("different client hello seen")
}
return nil, nil
}
}
func AssertTLS(t *testing.T, r *http.Request, tlsConfigFunc ptls.ConfigFunc) {
t.Helper()
m, ok := getCtxMap(r.Context())
require.True(t, ok)
h, ok := m.Load(helloKey)
require.True(t, ok)
info, ok := h.(*tls.ClientHelloInfo)
require.True(t, ok)
tlsConfig := tlsConfigFunc(nil)
supportedVersions := []uint16{tlsConfig.MinVersion}
ciphers := tlsConfig.CipherSuites
if secureTLSConfig := ptls.Secure(nil); tlsConfig.MinVersion != secureTLSConfig.MinVersion {
supportedVersions = append([]uint16{secureTLSConfig.MinVersion}, supportedVersions...)
ciphers = append(ciphers, secureTLSConfig.CipherSuites...)
}
protos := tlsConfig.NextProtos
if httpstream.IsUpgradeRequest(r) {
protos = tlsConfig.NextProtos[1:]
}
// use assert instead of require to not break the http.Handler with a panic
ok1 := assert.Equal(t, supportedVersions, info.SupportedVersions)
ok2 := assert.Equal(t, cipherSuiteIDsToStrings(ciphers), cipherSuiteIDsToStrings(info.CipherSuites))
ok3 := assert.Equal(t, protos, info.SupportedProtos)
if all := ok1 && ok2 && ok3; !all {
t.Errorf("insecure TLS detected for %q %q %q upgrade=%v supportedVersions=%v ciphers=%v protos=%v",
r.Proto, r.Method, r.URL.String(), httpstream.IsUpgradeRequest(r), ok1, ok2, ok3)
}
}
func cipherSuiteIDsToStrings(ids []uint16) []string {
cipherSuites := make([]string, 0, len(ids))
for _, id := range ids {
cipherSuites = append(cipherSuites, tls.CipherSuiteName(id))
}
return cipherSuites
}
func getCtxMap(ctx context.Context) (*sync.Map, bool) {
m, ok := ctx.Value(mapKey).(*sync.Map)
return m, ok
}