Skip to content

Commit

Permalink
Merge pull request #1950 from vmware-tanzu/refactor_ptls
Browse files Browse the repository at this point in the history
refactor ptls to clarify the difference between FIPS and non-FIPS modes
  • Loading branch information
joshuatcasey authored May 14, 2024
2 parents f96cbea + e13f4a7 commit b28e416
Show file tree
Hide file tree
Showing 10 changed files with 409 additions and 191 deletions.
72 changes: 0 additions & 72 deletions internal/crypto/ptls/default.go

This file was deleted.

144 changes: 144 additions & 0 deletions internal/crypto/ptls/profiles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

//go:build !fips_strict

package ptls

import (
"crypto/tls"
"crypto/x509"
"os"
"path/filepath"
"runtime"

"k8s.io/apiserver/pkg/server/options"

"go.pinniped.dev/internal/plog"
)

// init prints a log message to tell the operator how Pinniped was compiled. This makes it obvious
// that they are using Pinniped in FIPS-mode or not, which is otherwise hard to observe.
func init() { //nolint:gochecknoinits
switch filepath.Base(os.Args[0]) {
case "pinniped-server", "pinniped-supervisor", "pinniped-concierge", "pinniped-concierge-kube-cert-agent":
default:
return // do not print FIPS logs if we cannot confirm that we are running a server binary
}

// this init runs before we have parsed our config to determine our log level
// thus we must use a log statement that will always print instead of conditionally print
plog.Always("this server was not compiled in FIPS-only mode",
"go version", runtime.Version())
}

// SecureTLSConfigMinTLSVersion is the minimum tls version in the format expected by tls.Config.
const SecureTLSConfigMinTLSVersion = tls.VersionTLS13

// Default TLS profile should be used by:
// A. servers whose clients are outside our control and who may reasonably wish to use TLS 1.2, and
// B. clients who need to interact with servers that might not support TLS 1.3.
// Note that this will behave differently when compiled in FIPS mode (see profiles_fips_strict.go).
func Default(rootCAs *x509.CertPool) *tls.Config {
return &tls.Config{
// Can't use SSLv3 because of POODLE and BEAST
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
//
// The Kubernetes API Server must use TLS 1.2, at a minimum,
// to protect the confidentiality of sensitive data during electronic dissemination.
// https://stigviewer.com/stig/kubernetes/2021-06-17/finding/V-242378
MinVersion: tls.VersionTLS12,

// the order does not matter in go 1.17+ https://go.dev/blog/tls-cipher-suites
// we match crypto/tls.cipherSuitesPreferenceOrder because it makes unit tests easier to write
// this list is ignored when TLS 1.3 is used
//
// as of 2021-10-19, Mozilla Guideline v5.6, Go 1.17.2, intermediate configuration, supports:
// - Firefox 27
// - Android 4.4.2
// - Chrome 31
// - Edge
// - IE 11 on Windows 7
// - Java 8u31
// - OpenSSL 1.0.1
// - Opera 20
// - Safari 9
// https://ssl-config.mozilla.org/#server=go&version=1.17.2&config=intermediate&guideline=5.6
//
// The Kubernetes API server must use approved cipher suites.
// https://stigviewer.com/stig/kubernetes/2021-06-17/finding/V-242418
CipherSuites: []uint16{
// these are all AEADs with ECDHE, some use ChaCha20Poly1305 while others use AES-GCM
// this provides forward secrecy, confidentiality and authenticity of data
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},

// enable HTTP2 for go's 1.7 HTTP Server
// setting this explicitly is only required in very specific circumstances
// it is simpler to just set it here than to try and determine if we need to
NextProtos: []string{"h2", "http/1.1"},

// optional root CAs, nil means use the host's root CA set
RootCAs: rootCAs,
}
}

// DefaultLDAP TLS profile should be used by clients who need to interact with potentially old LDAP servers
// that might not support TLS 1.3 and that might use older ciphers.
// Note that this will behave differently when compiled in FIPS mode (see profiles_fips_strict.go).
func DefaultLDAP(rootCAs *x509.CertPool) *tls.Config {
c := Default(rootCAs)
// add less secure ciphers to support the default AWS Active Directory config
c.CipherSuites = append(c.CipherSuites,
// CBC with ECDHE
// this provides forward secrecy and confidentiality of data but not authenticity
// MAC-then-Encrypt CBC ciphers are susceptible to padding oracle attacks
// See https://crypto.stackexchange.com/a/205 and https://crypto.stackexchange.com/a/224
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
)
return c
}

// Secure TLS profile should be used by:
// A. servers whose clients are entirely known by us and who may reasonably be told that they must use TLS 1.3, and
// B. clients who only need to interact with servers that are known by us to support TLS 1.3 (e.g. the Kubernetes API).
// Note that this will behave differently when compiled in FIPS mode (see profiles_fips_strict.go).
func Secure(rootCAs *x509.CertPool) *tls.Config {
// as of 2021-10-19, Mozilla Guideline v5.6, Go 1.17.2, modern configuration, supports:
// - Firefox 63
// - Android 10.0
// - Chrome 70
// - Edge 75
// - Java 11
// - OpenSSL 1.1.1
// - Opera 57
// - Safari 12.1
// https://ssl-config.mozilla.org/#server=go&version=1.17.2&config=modern&guideline=5.6
c := Default(rootCAs)
c.MinVersion = SecureTLSConfigMinTLSVersion // max out the security
c.CipherSuites = nil // TLS 1.3 ciphers are not configurable
return c
}

// SecureServing modifies the given options to have the appropriate MinTLSVersion and CipherSuites.
// This function should only be used by the implementation of ptls.SecureRecommendedOptions, which
// is called to help configure our aggregated API servers. This exists only because it needs
// to behave differently in FIPS mode.
// This function is only public so we can integration test it in ptls_fips_test.go.
// Note that this will behave differently when compiled in FIPS mode (see profiles_fips_strict.go).
func SecureServing(opts *options.SecureServingOptionsWithLoopback) {
// secureServingOptionsMinTLSVersion is the minimum tls version in the format
// expected by SecureServingOptions.MinTLSVersion from
// k8s.io/apiserver/pkg/server/options.
opts.MinTLSVersion = "VersionTLS13"
opts.CipherSuites = nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ import (
"go.pinniped.dev/internal/plog"
)

// Until goboring supports TLS 1.3, use TLS 1.2.
const SecureTLSConfigMinTLSVersion = tls.VersionTLS12

// init: see comment in profiles.go.
func init() {
switch filepath.Base(os.Args[0]) {
case "pinniped-server", "pinniped-supervisor", "pinniped-concierge", "pinniped-concierge-kube-cert-agent":
Expand All @@ -32,9 +30,16 @@ func init() {

// this init runs before we have parsed our config to determine our log level
// thus we must use a log statement that will always print instead of conditionally print
plog.Always("using boring crypto in fips only mode", "go version", runtime.Version())
plog.Always("this server was compiled to use boring crypto in FIPS-only mode",
"go version", runtime.Version())
}

// SecureTLSConfigMinTLSVersion: see comment in profiles.go.
// Until goboring supports TLS 1.3, use TLS 1.2.
const SecureTLSConfigMinTLSVersion = tls.VersionTLS12

// Default: see comment in profiles.go.
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
func Default(rootCAs *x509.CertPool) *tls.Config {
return &tls.Config{
MinVersion: tls.VersionTLS12,
Expand Down Expand Up @@ -64,16 +69,22 @@ func Default(rootCAs *x509.CertPool) *tls.Config {
}
}

// Until goboring supports TLS 1.3, make the Secure profile the same as the Default profile in FIPS mode.
func Secure(rootCAs *x509.CertPool) *tls.Config {
// DefaultLDAP: see comment in profiles.go.
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
func DefaultLDAP(rootCAs *x509.CertPool) *tls.Config {
return Default(rootCAs)
}

func DefaultLDAP(rootCAs *x509.CertPool) *tls.Config {
// Secure: see comment in profiles.go.
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
// Until goboring supports TLS 1.3, make the Secure profile the same as the Default profile in FIPS mode.
func Secure(rootCAs *x509.CertPool) *tls.Config {
return Default(rootCAs)
}

// Until goboring supports TLS 1.3, make secureServing use the same as the defaultServing profile in FIPS mode.
func secureServing(opts *options.SecureServingOptionsWithLoopback) {
// SecureServing: see comment in profiles.go.
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
// Until goboring supports TLS 1.3, make SecureServing use the same as the defaultServing profile in FIPS mode.
func SecureServing(opts *options.SecureServingOptionsWithLoopback) {
defaultServing(opts)
}
9 changes: 9 additions & 0 deletions internal/crypto/ptls/profiles_fips_strict_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package ptls

// If you are coming here to look for unit tests for the FIPS-mode profiles,
// please instead see test/integration/ptls_fips_test.go.
// CI does not currently run the unit tests in FIPS mode, so these unit tests
// were instead written as integration tests.
91 changes: 91 additions & 0 deletions internal/crypto/ptls/profiles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package ptls

import (
"crypto/tls"
"crypto/x509"
"testing"

"github.com/stretchr/testify/require"
"k8s.io/apiserver/pkg/server/options"
)

func TestDefault(t *testing.T) {
t.Parallel()

aCertPool := x509.NewCertPool()

actual := Default(aCertPool)
expected := &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
NextProtos: []string{"h2", "http/1.1"},
RootCAs: aCertPool,
}

require.Equal(t, expected, actual)
}

func TestDefaultLDAP(t *testing.T) {
t.Parallel()

aCertPool := x509.NewCertPool()

actual := DefaultLDAP(aCertPool)
expected := &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, //nolint:gosec // this is a test
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
},
NextProtos: []string{"h2", "http/1.1"},
RootCAs: aCertPool,
}

require.Equal(t, expected, actual)
}

func TestSecure(t *testing.T) {
t.Parallel()

aCertPool := x509.NewCertPool()

actual := Secure(aCertPool)
expected := &tls.Config{
MinVersion: tls.VersionTLS13,
CipherSuites: nil, // TLS 1.3 ciphers are not configurable
NextProtos: []string{"h2", "http/1.1"},
RootCAs: aCertPool,
}

require.Equal(t, expected, actual)
}

func TestSecureServing(t *testing.T) {
t.Parallel()

opts := &options.SecureServingOptionsWithLoopback{SecureServingOptions: &options.SecureServingOptions{}}
SecureServing(opts)
require.Equal(t, options.SecureServingOptionsWithLoopback{
SecureServingOptions: &options.SecureServingOptions{
MinTLSVersion: "VersionTLS13",
},
}, *opts)
}
2 changes: 1 addition & 1 deletion internal/crypto/ptls/ptls.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func DefaultRecommendedOptions(opts *options.RecommendedOptions, f RestConfigFun
// certain well known clients which we expect will always use modern TLS settings (like the Kube API server).
// It returns a PrepareServerConfigFunc which must be used on a RecommendedConfig before passing it to RecommendedOptions.ApplyTo().
func SecureRecommendedOptions(opts *options.RecommendedOptions, f RestConfigFunc) (PrepareServerConfigFunc, error) {
secureServing(opts.SecureServing)
SecureServing(opts.SecureServing)
return secureClient(opts, f)
}

Expand Down
Loading

0 comments on commit b28e416

Please sign in to comment.