Merge pull request #3167 from vmware/gofips140
CodeQL / Analyze (go) (push) Failing after 1m30s
CodeQL / Analyze (javascript) (push) Failing after 44s

Support building Pinniped using GOFIPS140
This commit is contained in:
Ryan Richard
2026-07-07 14:12:50 -07:00
committed by GitHub
17 changed files with 450 additions and 91 deletions
+6 -1
View File
@@ -24,6 +24,11 @@ ARG TARGETARCH
# If provided, must be a comma-separated list of Go build tags.
ARG ADDITIONAL_BUILD_TAGS
# Set this if you would like to enable Go's built-in FIPS compliance module.
# E.g. GOFIPS140=certified or GOFIPS140=v1.0.0
# See https://go.dev/doc/security/fips140 for details.
ARG GOFIPS140=off
# Build the statically linked (CGO_ENABLED=0) binary.
# Mount source, build cache, and module cache for performance reasons.
# See https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/
@@ -31,7 +36,7 @@ RUN \
--mount=target=. \
--mount=type=cache,target=/cache/gocache \
--mount=type=cache,target=/cache/gomodcache \
export GOCACHE=/cache/gocache GOMODCACHE=/cache/gomodcache CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH && \
export GOCACHE=/cache/gocache GOMODCACHE=/cache/gomodcache CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH GOFIPS140=$GOFIPS140 && \
go build -tags $ADDITIONAL_BUILD_TAGS -v -trimpath -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-concierge-kube-cert-agent ./cmd/pinniped-concierge-kube-cert-agent/... && \
go build -tags $ADDITIONAL_BUILD_TAGS -v -trimpath -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-server ./cmd/pinniped-server/... && \
ln -s /usr/local/bin/pinniped-server /usr/local/bin/pinniped-concierge && \
+3
View File
@@ -3,6 +3,9 @@
# Copyright 2022-2026 the Pinniped contributors. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# Note: This dockerfile is for using boring crypto. If you would like to
# use Go's built-in GOFIPS140 instead, please use the top-level Dockerfile.
# this dockerfile is used to produce a binary of Pinniped that uses
# only fips-allowable ciphers. Note that this is provided only as
# an example. Pinniped has no official support for fips and using
+1
View File
@@ -233,6 +233,7 @@ if [[ "$do_build" == "yes" ]]; then
else
log_note "Docker building the app with KUBE_GIT_VERSION='$testing_version'"
# DOCKER_BUILDKIT=1 is optional on MacOS but required on linux.
# To build the server with GOFIPS140, add a build arg here, e.g.: --build-arg "GOFIPS140=certified"
DOCKER_BUILDKIT=1 docker build . --tag "$registry_repo_tag" --build-arg "KUBE_GIT_VERSION=$testing_version"
fi
fi
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2024-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package ptls
@@ -209,7 +209,7 @@ func validateAllowedCiphers(
// Note that it will return different values depending on if the code was compiled in FIPS or non-FIPS mode.
func allHardcodedAllowedCipherSuites() []*tls.CipherSuite {
// First append all secure and LDAP cipher suites.
result := translateIDIntoSecureCipherSuites(append(secureCipherSuiteIDs, additionalSecureCipherSuiteIDsOnlyForLDAPClients...))
result := translateIDIntoSecureCipherSuites(append(secureCipherSuiteIDs(), additionalSecureCipherSuiteIDsOnlyForLDAPClients()...))
// Then append any insecure cipher suites that might be allowed.
// insecureCipherSuiteIDs is empty except when compiled in FIPS mode.
@@ -218,7 +218,7 @@ func allHardcodedAllowedCipherSuites() []*tls.CipherSuite {
continue
}
if slices.Contains(insecureCipherSuiteIDs, golangInsecureCipherSuite.ID) {
if slices.Contains(insecureCipherSuiteIDs(), golangInsecureCipherSuite.ID) {
result = append(result, golangInsecureCipherSuite)
}
}
+85 -23
View File
@@ -1,12 +1,13 @@
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Note that everything in this file is overridden by profiles_fips_strict.go when Pinniped is built in FIPS-only mode.
// Note that everything in this file is overridden by profiles_fips_strict.go when Pinniped is built in legacy boring crypto FIPS-only mode.
//go:build !fips_strict
package ptls
import (
"crypto/fips140"
"crypto/tls"
"crypto/x509"
"os"
@@ -18,8 +19,25 @@ import (
"go.pinniped.dev/internal/plog"
)
var (
// secureCipherSuiteIDs is the list of TLS ciphers to use for both clients and servers when using TLS 1.2.
// secureCipherSuiteIDs is the list of TLS ciphers to use for both clients and servers when using TLS 1.2.
func secureCipherSuiteIDs() []uint16 {
if fips140.Enabled() {
// FIPS allows the use of these ciphers which golang considers secure.
// For GOFIPS140, you can find Go's supported list in the Go source code at src/crypto/tls/defaults_fips140.go.
return []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,
// These are considered "insecure" by Go because they are returned by tls.InsecureCipherSuites().
// So although they are included in Go's src/crypto/tls/defaults_fips140.go, they will not actually be used.
// tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
// tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
}
}
// The list for non-FIPS builds.
//
// 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.
@@ -41,7 +59,7 @@ var (
//
// These are all AEADs with ECDHE, some use ChaCha20Poly1305 while others use AES-GCM,
// which provides forward secrecy, confidentiality and authenticity of data.
secureCipherSuiteIDs = []uint16{ //nolint:gochecknoglobals // please treat this as a const
return []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,
@@ -49,16 +67,27 @@ var (
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
}
}
// insecureCipherSuiteIDs is a list of additional ciphers that should be allowed for both clients
// and servers when using TLS 1.2.
//
// This list is empty when compiled in non-FIPS mode, so we will not use any insecure ciphers in non-FIPS mode.
insecureCipherSuiteIDs []uint16 //nolint:gochecknoglobals // please treat this as a const
// insecureCipherSuiteIDs is a list of additional ciphers that should be allowed for both clients
// and servers when using TLS 1.2.
//
// This list is empty when compiled in non-FIPS mode, so we will not use any insecure ciphers in non-FIPS mode.
// It should also be empty for GOFIPS140, because it does not use any ciphers considered insecure by Go.
func insecureCipherSuiteIDs() []uint16 {
return nil
}
// additionalSecureCipherSuiteIDsOnlyForLDAPClients are additional ciphers to use only for LDAP clients
// when using TLS 1.2. These can be used when the Pinniped Supervisor is making calls to an LDAP server
// configured by an LDAPIdentityProvider or ActiveDirectoryIdentityProvider.
// additionalSecureCipherSuiteIDsOnlyForLDAPClients are additional ciphers to use only for LDAP clients
// when using TLS 1.2. These can be used when the Pinniped Supervisor is making calls to an LDAP server
// configured by an LDAPIdentityProvider or ActiveDirectoryIdentityProvider.
func additionalSecureCipherSuiteIDsOnlyForLDAPClients() []uint16 {
if fips140.Enabled() {
// For GOFIPS140, we do not want to add any less secure ciphers to the list.
return nil
}
// The list for non-FIPS builds.
//
// Adds less secure ciphers to support the default AWS Active Directory config.
//
@@ -66,13 +95,13 @@ var (
// these provide 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
additionalSecureCipherSuiteIDsOnlyForLDAPClients = []uint16{ //nolint:gochecknoglobals // please treat this as a const
return []uint16{
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,
}
)
}
// 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.
@@ -85,35 +114,60 @@ func init() { //nolint:gochecknoinits
// 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",
plog.Always("this server was not compiled in legacy boring crypto FIPS-only mode",
"go version", runtime.Version(),
"SecureProfileMinTLSVersionForNonFIPS", tls.VersionName(SecureProfileMinTLSVersionForNonFIPS))
"GOFIPS140 enabled", fips140.Enabled(),
"GOFIPS140 version", fips140.Version(),
"GOFIPS140 enforced (typically for testing only)", fips140.Enforced(),
"SecureProfileMinTLSVersionForNonFIPS (only applies when FIPS is disabled)", tls.VersionName(SecureProfileMinTLSVersionForNonFIPS),
)
}
// 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).
// Note that this will behave differently when compiled in legacy boring crypto FIPS mode (see profiles_fips_strict.go).
// Default returns a tls.Config with a minimum of TLS1.2+ and a few ciphers that can be further constrained by configuration.
func Default(rootCAs *x509.CertPool) *tls.Config {
ciphers := translateIDIntoSecureCipherSuites(secureCipherSuiteIDs)
if fips140.Enabled() {
config := buildTLSConfig(rootCAs, allHardcodedAllowedCipherSuites(), getUserConfiguredAllowedCipherSuitesForTLSOneDotTwo())
// GOFIPS140 supports both TLS v1.2 and v1.3.
// You can find Go's supported list in the Go source code at src/crypto/tls/defaults_fips140.go.
config.MaxVersion = tls.VersionTLS13
return config
}
ciphers := translateIDIntoSecureCipherSuites(secureCipherSuiteIDs())
return buildTLSConfig(rootCAs, ciphers, getUserConfiguredAllowedCipherSuitesForTLSOneDotTwo())
}
// 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).
// Note that this will behave differently when compiled in legacy boring crypto FIPS mode (see profiles_fips_strict.go).
func DefaultLDAP(rootCAs *x509.CertPool) *tls.Config {
ciphers := translateIDIntoSecureCipherSuites(secureCipherSuiteIDs)
ciphers = append(ciphers, translateIDIntoSecureCipherSuites(additionalSecureCipherSuiteIDsOnlyForLDAPClients)...)
if fips140.Enabled() {
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
// In FIPS mode, this is not any different from the Default profile.
return Default(rootCAs)
}
ciphers := translateIDIntoSecureCipherSuites(secureCipherSuiteIDs())
ciphers = append(ciphers, translateIDIntoSecureCipherSuites(additionalSecureCipherSuiteIDsOnlyForLDAPClients())...)
return buildTLSConfig(rootCAs, ciphers, getUserConfiguredAllowedCipherSuitesForTLSOneDotTwo())
}
// 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).
// Note that this will behave differently when compiled in legacy boring crypto FIPS mode (see profiles_fips_strict.go).
func Secure(rootCAs *x509.CertPool) *tls.Config {
if fips140.Enabled() {
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
// Until it is safe to assume that a FIPS-compiled k8s server supports TLS 1.3, continue to
// make the Secure profile the same as the Default profile in FIPS mode, to allow both TLS 1.2 and 1.3.
return Default(rootCAs)
}
// as of 2021-10-19, Mozilla Guideline v5.6, Go 1.17.2, modern configuration, supports:
// - Firefox 63
// - Android 10.0
@@ -141,6 +195,14 @@ func Secure(rootCAs *x509.CertPool) *tls.Config {
// 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) {
if fips140.Enabled() {
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
// Until it is safe to assume that a FIPS-compiled k8s server supports TLS 1.3, continue to
// make SecureServing use the same as the defaultServing profile in FIPS mode, to allow both TLS 1.2 and 1.3.
defaultServing(opts)
return
}
// secureServingOptionsMinTLSVersion is the minimum tls version in the format
// expected by SecureServingOptions.MinTLSVersion from
// k8s.io/apiserver/pkg/server/options.
+28 -21
View File
@@ -1,7 +1,7 @@
// Copyright 2022-2025 the Pinniped contributors. All Rights Reserved.
// Copyright 2022-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// This file overrides profiles.go when Pinniped is built in FIPS-only mode.
// This file overrides profiles.go when Pinniped is built in FIPS-only mode using the legacy boring crypto compiler.
//go:build fips_strict
package ptls
@@ -20,34 +20,41 @@ import (
"go.pinniped.dev/internal/plog"
)
// The union of these three variables is all the FIPS-approved TLS 1.2 ciphers.
// secureCipherSuiteIDs(), insecureCipherSuiteIDs(), and additionalSecureCipherSuiteIDsOnlyForLDAPClients()
// together determine the ciphers to be used for TLS 1.2.
// The union of these three functions' results is all the FIPS-approved TLS 1.2 ciphers.
// If this list does not match the boring crypto compiler's list then the TestFIPSCipherSuites integration
// test should fail, which indicates that this list needs to be updated.
var (
// secureCipherSuiteIDs is the list of TLS ciphers to use for both clients and servers when using TLS 1.2.
//
// FIPS allows the use of these ciphers which golang considers secure.
secureCipherSuiteIDs = []uint16{
// secureCipherSuiteIDs is the list of TLS ciphers to use for both clients and servers when using TLS 1.2.
//
// FIPS allows the use of these ciphers which golang considers secure.
func secureCipherSuiteIDs() []uint16 {
return []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,
}
}
// insecureCipherSuiteIDs is a list of additional ciphers that should be allowed for both clients
// and servers when using TLS 1.2.
//
// Previous versions of FIPS allowed the use of some specific ciphers that golang considers insecure.
// Go 1.24 does not anymore, so now this list is empty.
insecureCipherSuiteIDs []uint16
// insecureCipherSuiteIDs is a list of additional ciphers that should be allowed for both clients
// and servers when using TLS 1.2.
//
// Previous versions of FIPS allowed the use of some specific ciphers that golang considers insecure.
// Go 1.24 does not anymore, so now this list is empty.
func insecureCipherSuiteIDs() []uint16 {
return nil
}
// additionalSecureCipherSuiteIDsOnlyForLDAPClients are additional ciphers to use only for LDAP clients
// when using TLS 1.2. These can be used when the Pinniped Supervisor is making calls to an LDAP server
// configured by an LDAPIdentityProvider or ActiveDirectoryIdentityProvider.
//
// When compiled in FIPS mode, there are no extras for LDAP clients.
additionalSecureCipherSuiteIDsOnlyForLDAPClients []uint16
)
// additionalSecureCipherSuiteIDsOnlyForLDAPClients are additional ciphers to use only for LDAP clients
// when using TLS 1.2. These can be used when the Pinniped Supervisor is making calls to an LDAP server
// configured by an LDAPIdentityProvider or ActiveDirectoryIdentityProvider.
//
// When compiled in FIPS mode, there are no extras for LDAP clients.
func additionalSecureCipherSuiteIDsOnlyForLDAPClients() []uint16 {
return nil
}
// init: see comment in profiles.go.
func init() {
+26 -5
View File
@@ -1,15 +1,26 @@
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//go:build !fips_strict
package tlsserver
import "crypto/tls"
import (
"crypto/fips140"
"crypto/tls"
)
// GetExpectedTLS13Ciphers returns the expected TLS 1.3 cipher for a non-FIPS build.
// GetExpectedTLS13Ciphers returns the expected TLS 1.3 cipher.
// TLS 1.3 ciphers are not configurable, so we can hard-code them here.
func GetExpectedTLS13Ciphers() []uint16 {
// TLS 1.3 ciphers are not configurable, so we can hard-code them here.
if fips140.Enabled() {
return []uint16{
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
// tls.TLS_CHACHA20_POLY1305_SHA256 is not supported by GOFIPS140
}
}
return []uint16{
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
@@ -18,8 +29,18 @@ func GetExpectedTLS13Ciphers() []uint16 {
}
// GetExpectedTLS13CipherNMapKeyExchangeInfoValue returns the expected key exchange info value
// which is shown by nmap in parenthesis next to the cipher name for a non-FIPS build.
// which is shown by nmap in parentheses next to the cipher name.
func GetExpectedTLS13CipherNMapKeyExchangeInfoValue(cipher uint16) string {
if fips140.Enabled() {
switch cipher {
case tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384:
return "secp256r1"
default:
return "unknown key exchange value"
}
}
switch cipher {
case tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
@@ -1,13 +1,14 @@
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// This file overrides tls13_ciphers.go when Pinniped is built in FIPS-only mode using the legacy boring crypto compiler.
//go:build fips_strict
package tlsserver
import "crypto/tls"
// GetExpectedTLS13Ciphers returns the expected TLS 1.3 cipher for a FIPS build.
// GetExpectedTLS13Ciphers returns the expected TLS 1.3 cipher for a legacy boring crypto FIPS build.
func GetExpectedTLS13Ciphers() []uint16 {
// TLS 1.3 ciphers are not configurable, so we can hard-code them here.
return []uint16{
@@ -18,7 +19,7 @@ func GetExpectedTLS13Ciphers() []uint16 {
}
// GetExpectedTLS13CipherNMapKeyExchangeInfoValue returns the expected key exchange info value
// which is shown by nmap in parenthesis next to the cipher name for a FIPS build.
// which is shown by nmap in parentheses next to the cipher name for a legacy boring crypto FIPS build.
func GetExpectedTLS13CipherNMapKeyExchangeInfoValue(cipher uint16) string {
switch cipher {
case tls.TLS_AES_128_GCM_SHA256,
+36 -7
View File
@@ -10,23 +10,52 @@ menu:
parent: reference
---
By default, the Pinniped supervisor and concierge use ciphers that
are not supported by FIPS 140-2. If you are deploying Pinniped in an
are not supported by FIPS 140-2 or 140-3. If you are deploying Pinniped in an
environment with FIPS compliance requirements, you will have to build
the binaries yourself using the `fips_strict` build tag and Golang's
`GOEXPERIMENT=boringcrypto` compiler option.
the binaries yourself.
The Pinniped team provides an [example Dockerfile](https://github.com/vmware/pinniped/blob/main/hack/Dockerfile_fips)
demonstrating how you can build Pinniped images in a FIPS compatible way.
However, we do not provide official support for FIPS configuration.
The open source Pinniped project does not provide official support for FIPS configuration.
We provide this for informational purposes only.
To build Pinniped use our example FIPS Dockerfile, you can run:
There are two options for building FIPS-compatible server binaries.
## Using Go's GOFIPS140
The top-level [Dockerfile](https://github.com/vmware/pinniped/blob/main/Dockerfile) has a build arg to allow you
to optionally enable GOFIPS140. Before choosing which value to set for this build arg, please refer to the
Go [announcement of GOFIPS140](https://go.dev/blog/fips140) and [documentation for GOFIPS140](https://go.dev/doc/security/fips140).
## The old option: Using boring crypto
The Pinniped team provides an [example Dockerfile](https://github.com/vmware/pinniped/blob/main/hack/Dockerfile_fips)
demonstrating how you can build Pinniped images using boring crypto.
This sample dockerfile uses the `fips_strict` build tag and Golang's `GOEXPERIMENT=boringcrypto` compiler option.
Note that the Go team has announced the deprecation of their support for boring crypto, so this method may
stop working in some future version of Go.
## The build commands
To build the Pinniped container image, first clone and repo and checkout the release tag:
```bash
$ git clone git@github.com:vmware/pinniped.git
$ cd pinniped
$ git checkout {{< latestversion >}}
```
If you choose to use GOFIPS140, then choose the value of GOFIPS140 that you prefer and run:
```bash
# For example, if you prefer to use "certified"...
$ docker build . --build-arg "GOFIPS140=certified"
```
If you choose to use the old boring crypto, then instead run:
```bash
$ docker build -f hack/Dockerfile_fips .
```
Push the image to your preferred container image repository.
Now you can deploy [the concierge]({{< ref "install-concierge" >}}) and [the supervisor]({{< ref "install-supervisor" >}})
by specifying this image instead of the standard Pinniped image in your `values.yaml` or `deployment.yaml` file.
@@ -1542,7 +1542,8 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
stdout, stderr := testlib.RunNmapSSLEnum(t, "127.0.0.1", 10445)
require.Empty(t, stderr)
require.Contains(t, stdout, testlib.GetExpectedCiphers(ptls.Default(nil), testlib.DefaultCipherSuitePreference), "stdout:\n%s", stdout)
expectedCiphers := testlib.GetExpectedCiphers(ptls.Default(nil), testlib.DefaultCipherSuitePreference())
require.Contains(t, stdout, expectedCiphers, "wantedToContain:\n%s\nactual stdout:\n%s", expectedCiphers, stdout)
})
})
+42 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2024-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//go:build !fips_strict
@@ -8,12 +8,16 @@ package integration
import (
"crypto/tls"
"testing"
"go.pinniped.dev/test/testlib"
)
// TestLimitedCiphersNotFIPS_Disruptive will confirm that the Pinniped Supervisor and Concierge expose only those
// ciphers listed in configuration, when compiled in non-FIPS mode.
// This does not test the CLI, since it does not have a feature to limit cipher suites.
func TestLimitedCiphersNotFIPS_Disruptive(t *testing.T) {
testlib.SkipTestWhenUsingGOFIPS140(t)
performLimitedCiphersTest(t,
// The user-configured ciphers for both the Supervisor and Concierge.
// This is a subset of the hardcoded ciphers from profiles.go.
@@ -41,3 +45,40 @@ func TestLimitedCiphersNotFIPS_Disruptive(t *testing.T) {
},
)
}
// TestLimitedCiphersFIPS_Disruptive will confirm that the Pinniped Supervisor and Concierge expose only those
// ciphers listed in configuration, when compiled in GOFIPS140 mode.
// This does not test the CLI, since it does not have a feature to limit cipher suites.
func TestLimitedCiphersGOFIPS140_Disruptive(t *testing.T) {
testlib.SkipTestUnlessUsingGOFIPS140(t)
performLimitedCiphersTest(t,
// The user-configured ciphers for both the Supervisor and Concierge.
// This is a subset of the hardcoded ciphers from profiles_fips_strict.go.
[]string{
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
},
// Expected server configuration for the Supervisor's OIDC endpoints.
&tls.Config{
MinVersion: tls.VersionTLS12, // Supervisor OIDC always allows TLS 1.2 clients to connect
MaxVersion: tls.VersionTLS13,
CipherSuites: []uint16{
// Supervisor OIDC endpoints configured with EC certs use only EC ciphers.
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
},
},
// Expected server configuration for the Supervisor and Concierge aggregated API endpoints.
&tls.Config{
MinVersion: tls.VersionTLS12, // always allow TLS 1.2 in fips mode
MaxVersion: tls.VersionTLS13,
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,
},
},
)
}
+19 -16
View File
@@ -1,6 +1,7 @@
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// This file overrides ptls_gofips140_test.go when Pinniped is built in FIPS-only mode using the legacy boring crypto compiler.
//go:build fips_strict
package integration
@@ -59,15 +60,17 @@ func TestFIPSCipherSuites_Parallel(t *testing.T) {
}
// Every profile should use the same cipher suites in FIPS mode, because FIPS requires these ciphers.
// Please treat this as a read-only const.
var expectedFIPSCipherSuites = []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,
func expectedFIPSCipherSuites() []uint16 {
// These are the expected values for boring crypto.
return []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,
}
}
func TestDefault_Parallel(t *testing.T) {
func TestFIPSDefault_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
aCertPool := x509.NewCertPool()
@@ -76,7 +79,7 @@ func TestDefault_Parallel(t *testing.T) {
expected := &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
CipherSuites: expectedFIPSCipherSuites,
CipherSuites: expectedFIPSCipherSuites(),
NextProtos: []string{"h2", "http/1.1"},
RootCAs: aCertPool,
}
@@ -84,7 +87,7 @@ func TestDefault_Parallel(t *testing.T) {
require.Equal(t, expected, actual)
}
func TestDefaultLDAP_Parallel(t *testing.T) {
func TestFIPSDefaultLDAP_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
aCertPool := x509.NewCertPool()
@@ -93,7 +96,7 @@ func TestDefaultLDAP_Parallel(t *testing.T) {
expected := &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
CipherSuites: expectedFIPSCipherSuites,
CipherSuites: expectedFIPSCipherSuites(),
NextProtos: []string{"h2", "http/1.1"},
RootCAs: aCertPool,
}
@@ -101,7 +104,7 @@ func TestDefaultLDAP_Parallel(t *testing.T) {
require.Equal(t, expected, actual)
}
func TestSecure_Parallel(t *testing.T) {
func TestFIPSSecure_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
aCertPool := x509.NewCertPool()
@@ -110,7 +113,7 @@ func TestSecure_Parallel(t *testing.T) {
expected := &tls.Config{
MinVersion: tls.VersionTLS12, // allow TLS 1.2 in FIPS mode
MaxVersion: tls.VersionTLS13,
CipherSuites: expectedFIPSCipherSuites,
CipherSuites: expectedFIPSCipherSuites(),
NextProtos: []string{"h2", "http/1.1"},
RootCAs: aCertPool,
}
@@ -118,14 +121,14 @@ func TestSecure_Parallel(t *testing.T) {
require.Equal(t, expected, actual)
}
func TestSecureServing_Parallel(t *testing.T) {
func TestFIPSSecureServing_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
opts := &options.SecureServingOptionsWithLoopback{SecureServingOptions: &options.SecureServingOptions{}}
ptls.SecureServing(opts)
expectedFIPSCipherSuiteNames := make([]string, len(expectedFIPSCipherSuites))
for i, suite := range expectedFIPSCipherSuites {
expectedFIPSCipherSuiteNames := make([]string, len(expectedFIPSCipherSuites()))
for i, suite := range expectedFIPSCipherSuites() {
expectedFIPSCipherSuiteNames[i] = tls.CipherSuiteName(suite)
}
+148
View File
@@ -0,0 +1,148 @@
// Copyright 2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//go:build !fips_strict
package integration
import (
"crypto/tls"
"crypto/x509"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"k8s.io/apiserver/pkg/server/options"
"k8s.io/client-go/util/cert"
"go.pinniped.dev/internal/crypto/ptls"
"go.pinniped.dev/internal/testutil/tlsserver"
"go.pinniped.dev/test/testlib"
)
// Note: Everything in this file is an integration test only because we do not support build tags on unit tests.
// These are effectively unit tests for the ptls package when compiled in FIPS mode.
// TestFIPSCipherSuites_Parallel ensures that if the list of default FIPS cipher suites changes, then we will know.
// If this test ever fails during a golang upgrade, then we may need to change which ciphers we are using in
// the ptls package in FIPS mode.
func TestFIPSCipherSuites_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
testlib.SkipTestUnlessUsingGOFIPS140(t)
server, ca := tlsserver.TestServerIPv4(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// use the default fips config which contains a hard coded list of cipher suites
// that should be equal to the default list of fips cipher suites.
// assert that the client hello response has the same tls config as this test server.
tlsserver.AssertTLS(t, r, ptls.Default)
}), tlsserver.RecordTLSHello)
pool, err := cert.NewPoolFromBytes(ca)
require.NoError(t, err)
// create a tls config that does not explicitly set cipher suites,
// and therefore uses goboring or native fips's default fips ciphers.
defaultConfig := &tls.Config{
RootCAs: pool,
NextProtos: ptls.Default(nil).NextProtos, // we do not care about field for this test, so just make it match
}
transport := http.Transport{
TLSClientConfig: defaultConfig,
ForceAttemptHTTP2: true,
}
// make a request against the test server, which will validate that the
// tls config of the client without explicitly set ciphers
// is the same as the tls config of the test server with explicitly
// set ciphers from ptls.
request, _ := http.NewRequestWithContext(t.Context(), "GET", server.URL, nil)
response, err := transport.RoundTrip(request)
t.Cleanup(func() {
require.NoError(t, response.Body.Close())
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
}
// Every profile should use the same cipher suites in FIPS mode, because FIPS requires these ciphers.
func expectedFIPSCipherSuites() []uint16 {
// These are the expected values for Go's "native" GOFIPS140.
return []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,
}
}
func TestFIPSDefault_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
testlib.SkipTestUnlessUsingGOFIPS140(t)
aCertPool := x509.NewCertPool()
actual := ptls.Default(aCertPool)
expected := &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
CipherSuites: expectedFIPSCipherSuites(),
NextProtos: []string{"h2", "http/1.1"},
RootCAs: aCertPool,
}
require.Equal(t, expected, actual)
}
func TestFIPSDefaultLDAP_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
testlib.SkipTestUnlessUsingGOFIPS140(t)
aCertPool := x509.NewCertPool()
actual := ptls.DefaultLDAP(aCertPool)
expected := &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
CipherSuites: expectedFIPSCipherSuites(),
NextProtos: []string{"h2", "http/1.1"},
RootCAs: aCertPool,
}
require.Equal(t, expected, actual)
}
func TestFIPSSecure_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
testlib.SkipTestUnlessUsingGOFIPS140(t)
aCertPool := x509.NewCertPool()
actual := ptls.Secure(aCertPool)
expected := &tls.Config{
MinVersion: tls.VersionTLS12, // allow TLS 1.2 in FIPS mode
MaxVersion: tls.VersionTLS13,
CipherSuites: expectedFIPSCipherSuites(),
NextProtos: []string{"h2", "http/1.1"},
RootCAs: aCertPool,
}
require.Equal(t, expected, actual)
}
func TestFIPSSecureServing_Parallel(t *testing.T) {
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
testlib.SkipTestUnlessUsingGOFIPS140(t)
opts := &options.SecureServingOptionsWithLoopback{SecureServingOptions: &options.SecureServingOptions{}}
ptls.SecureServing(opts)
expectedFIPSCipherSuiteNames := make([]string, len(expectedFIPSCipherSuites()))
for i, suite := range expectedFIPSCipherSuites() {
expectedFIPSCipherSuiteNames[i] = tls.CipherSuiteName(suite)
}
require.Equal(t, options.SecureServingOptionsWithLoopback{
SecureServingOptions: &options.SecureServingOptions{
CipherSuites: expectedFIPSCipherSuiteNames,
MinTLSVersion: "VersionTLS12", // allow TLS 1.2 in FIPS mode
},
}, *opts)
}
+10 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2021-2025 the Pinniped contributors. All Rights Reserved.
// Copyright 2021-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package integration
@@ -94,7 +94,9 @@ func TestSecureTLSConciergeAggregatedAPI_Parallel(t *testing.T) {
stdout, stderr := testlib.RunNmapSSLEnum(t, "127.0.0.1", 10446)
require.Empty(t, stderr)
require.Contains(t, stdout, testlib.GetExpectedCiphers(ptls.Secure(nil), testlib.DefaultCipherSuitePreference), "stdout:\n%s", stdout)
expectedCiphers := testlib.GetExpectedCiphers(ptls.Secure(nil), testlib.DefaultCipherSuitePreference())
require.Contains(t, stdout, expectedCiphers,
"wantedToContain:\n%s\nactual stdout:\n%s", expectedCiphers, stdout)
}
// TLS checks safe to run in parallel with serial tests, see main_test.go.
@@ -109,7 +111,9 @@ func TestSecureTLSSupervisorAggregatedAPI_Parallel(t *testing.T) {
stdout, stderr := testlib.RunNmapSSLEnum(t, "127.0.0.1", 10447)
require.Empty(t, stderr)
require.Contains(t, stdout, testlib.GetExpectedCiphers(ptls.Secure(nil), testlib.DefaultCipherSuitePreference), "stdout:\n%s", stdout)
expectedCiphers := testlib.GetExpectedCiphers(ptls.Secure(nil), testlib.DefaultCipherSuitePreference())
require.Contains(t, stdout, expectedCiphers,
"wantedToContain:\n%s\nactual stdout:\n%s", expectedCiphers, stdout)
}
func TestSecureTLSSupervisor(t *testing.T) {
@@ -151,7 +155,9 @@ func TestSecureTLSSupervisor(t *testing.T) {
defaultECDSAOnly.CipherSuites = ciphers
require.Empty(t, stderr)
require.Contains(t, stdout, testlib.GetExpectedCiphers(defaultECDSAOnly, testlib.DefaultCipherSuitePreference), "stdout:\n%s", stdout)
expectedCiphers := testlib.GetExpectedCiphers(defaultECDSAOnly, testlib.DefaultCipherSuitePreference())
require.Contains(t, stdout, expectedCiphers,
"wantedToContain:\n%s\nactual stdout:\n%s", expectedCiphers, stdout)
}
type fakeT struct {
+6 -2
View File
@@ -1,12 +1,16 @@
// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2022-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// This file overrides securetls_preference_nonfips.go when Pinniped is built in FIPS-only mode using the legacy boring crypto compiler.
//go:build fips_strict
package testlib
// DefaultCipherSuitePreference returns an expected value for tests.
// Because of a bug in nmap, the cipher suite preference is
// incorrectly shown as 'client' in some cases.
// in fips-only mode, it correctly shows the cipher preference
// as 'server', while in non-fips mode it shows as 'client'.
const DefaultCipherSuitePreference = "server"
func DefaultCipherSuitePreference() string {
return "server"
}
+10 -2
View File
@@ -1,12 +1,20 @@
// Copyright 2022-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2022-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//go:build !fips_strict
package testlib
import "crypto/fips140"
// DefaultCipherSuitePreference returns an expected value for tests.
// Because of a bug in nmap, the cipher suite preference is
// incorrectly shown as 'client' in some cases.
// in fips-only mode, it correctly shows the cipher preference
// as 'server', while in non-fips mode it shows as 'client'.
const DefaultCipherSuitePreference = "client"
func DefaultCipherSuitePreference() string {
if fips140.Enabled() {
return "server"
}
return "client"
}
+21 -2
View File
@@ -1,9 +1,12 @@
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2026 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package testlib
import "testing"
import (
"crypto/fips140"
"testing"
)
// SkipUnlessIntegration skips the current test if `-short` has been passed to `go test`.
func SkipUnlessIntegration(t *testing.T) {
@@ -14,6 +17,22 @@ func SkipUnlessIntegration(t *testing.T) {
}
}
func SkipTestWhenUsingGOFIPS140(t *testing.T) {
t.Helper()
if fips140.Enabled() {
t.Skip("this test is skipped when using GOFIPS140")
}
}
func SkipTestUnlessUsingGOFIPS140(t *testing.T) {
t.Helper()
if !fips140.Enabled() {
t.Skip("this test requires GOFIPS140")
}
}
func SkipTestWhenLDAPIsUnavailable(t *testing.T, env *TestEnv) {
t.Helper()