Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor ptls to clarify the difference between FIPS and non-FIPS modes #1950

Merged
merged 1 commit into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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":

Check warning on line 24 in internal/crypto/ptls/profiles.go

View check run for this annotation

Codecov / codecov/patch

internal/crypto/ptls/profiles.go#L24

Added line #L24 was not covered by tests
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())

Check warning on line 32 in internal/crypto/ptls/profiles.go

View check run for this annotation

Codecov / codecov/patch

internal/crypto/ptls/profiles.go#L31-L32

Added lines #L31 - L32 were not covered by tests
}

// 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 @@
// 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)

Check warning on line 80 in internal/crypto/ptls/ptls.go

View check run for this annotation

Codecov / codecov/patch

internal/crypto/ptls/ptls.go#L80

Added line #L80 was not covered by tests
return secureClient(opts, f)
}

Expand Down
Loading
Loading