From ef5a697991b8c9c3dfa283fe17658d56defd453e Mon Sep 17 00:00:00 2001 From: Hugo Landau Date: Wed, 27 Nov 2024 15:35:57 +0000 Subject: [PATCH] Allow requiring use of a post-quantum-safe KEM (#5601) * Allow configuration of mandatory PQ KEM: implementation * Allow configuration of mandatory PQ KEM: docs * Allow configuration of mandatory PQ KEM: tests --------- Signed-off-by: Hugo Landau Signed-off-by: Marcos Yacob Co-authored-by: Marcos Yacob --- cmd/spire-agent/cli/run/run.go | 8 +++ cmd/spire-agent/cli/run/run_test.go | 27 ++++++++++ cmd/spire-server/cli/run/run.go | 8 +++ cmd/spire-server/cli/run/run_test.go | 27 ++++++++++ doc/plugin_server_upstreamauthority_spire.md | 3 +- doc/spire_agent.md | 1 + doc/spire_server.md | 1 + pkg/agent/agent.go | 2 + pkg/agent/attestor/node/node.go | 3 ++ pkg/agent/client/client.go | 5 ++ pkg/agent/client/dial.go | 9 ++++ pkg/agent/config.go | 4 ++ pkg/agent/manager/config.go | 3 ++ pkg/agent/svid/rotator.go | 1 + pkg/agent/svid/rotator_config.go | 5 ++ pkg/common/tlspolicy/tlspolicy.go | 49 +++++++++++++++++++ pkg/common/tlspolicy/tlspolicy_test.go | 35 +++++++++++++ pkg/server/bundle/client/client.go | 10 ++++ pkg/server/config.go | 4 ++ pkg/server/credtemplate/builder.go | 2 + pkg/server/endpoints/config.go | 5 ++ pkg/server/endpoints/endpoints.go | 8 +++ pkg/server/endpoints/endpoints_test.go | 29 +++++++++-- .../plugin/upstreamauthority/spire/spire.go | 10 +++- .../spire/spire_server_client.go | 11 ++++- pkg/server/server.go | 1 + test/fixture/config/server_good_posix.conf | 3 ++ test/fixture/config/server_good_windows.conf | 1 + 28 files changed, 267 insertions(+), 8 deletions(-) create mode 100644 pkg/common/tlspolicy/tlspolicy.go create mode 100644 pkg/common/tlspolicy/tlspolicy_test.go diff --git a/cmd/spire-agent/cli/run/run.go b/cmd/spire-agent/cli/run/run.go index e2522bca27..8b789881f7 100644 --- a/cmd/spire-agent/cli/run/run.go +++ b/cmd/spire-agent/cli/run/run.go @@ -39,6 +39,7 @@ import ( "github.com/spiffe/spire/pkg/common/log" "github.com/spiffe/spire/pkg/common/pemutil" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/tlspolicy" ) const ( @@ -121,6 +122,7 @@ type experimentalConfig struct { NamedPipeName string `hcl:"named_pipe_name"` AdminNamedPipeName string `hcl:"admin_named_pipe_name"` UseSyncAuthorizedEntries bool `hcl:"use_sync_authorized_entries"` + RequirePQKEM bool `hcl:"require_pq_kem"` Flags fflag.RawConfig `hcl:"feature_flags"` } @@ -591,6 +593,12 @@ func NewAgentConfig(c *Config, logOptions []log.Option, allowUnknownConfig bool) ac.AvailabilityTarget = t } + ac.TLSPolicy = tlspolicy.Policy{ + RequirePQKEM: c.Agent.Experimental.RequirePQKEM, + } + + tlspolicy.LogPolicy(ac.TLSPolicy, log.NewHCLogAdapter(logger, "tlspolicy")) + if cmp.Diff(experimentalConfig{}, c.Agent.Experimental) != "" { logger.Warn("Experimental features have been enabled. Please see doc/upgrading.md for upgrade and compatibility considerations for experimental features.") } diff --git a/cmd/spire-agent/cli/run/run_test.go b/cmd/spire-agent/cli/run/run_test.go index a4938ef2fc..29ab95b732 100644 --- a/cmd/spire-agent/cli/run/run_test.go +++ b/cmd/spire-agent/cli/run/run_test.go @@ -615,6 +615,16 @@ func TestMergeInput(t *testing.T) { require.Equal(t, "bar", c.Agent.TrustDomain) }, }, + { + msg: "require_pq_kem should be configurable by file", + fileInput: func(c *Config) { + c.Agent.Experimental.RequirePQKEM = true + }, + cliInput: func(c *agentConfig) {}, + test: func(t *testing.T, c *Config) { + require.True(t, c.Agent.Experimental.RequirePQKEM) + }, + }, } cases = append(cases, mergeInputCasesOS()...) @@ -1012,6 +1022,23 @@ func TestNewAgentConfig(t *testing.T) { require.Nil(t, c) }, }, + + { + msg: "require PQ KEM is disabled (default)", + input: func(c *Config) {}, + test: func(t *testing.T, c *agent.Config) { + require.Equal(t, false, c.TLSPolicy.RequirePQKEM) + }, + }, + { + msg: "require PQ KEM is enabled", + input: func(c *Config) { + c.Agent.Experimental.RequirePQKEM = true + }, + test: func(t *testing.T, c *agent.Config) { + require.Equal(t, true, c.TLSPolicy.RequirePQKEM) + }, + }, } cases = append(cases, newAgentConfigCasesOS(t)...) for _, testCase := range cases { diff --git a/cmd/spire-server/cli/run/run.go b/cmd/spire-server/cli/run/run.go index 97804a41b9..3ba95d9c3d 100644 --- a/cmd/spire-server/cli/run/run.go +++ b/cmd/spire-server/cli/run/run.go @@ -36,6 +36,7 @@ import ( "github.com/spiffe/spire/pkg/common/health" "github.com/spiffe/spire/pkg/common/log" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/spiffe/spire/pkg/server" "github.com/spiffe/spire/pkg/server/authpolicy" bundleClient "github.com/spiffe/spire/pkg/server/bundle/client" @@ -109,6 +110,7 @@ type experimentalConfig struct { EventsBasedCache bool `hcl:"events_based_cache"` PruneEventsOlderThan string `hcl:"prune_events_older_than"` SQLTransactionTimeout string `hcl:"sql_transaction_timeout"` + RequirePQKEM bool `hcl:"require_pq_kem"` Flags fflag.RawConfig `hcl:"feature_flags"` @@ -509,6 +511,12 @@ func NewServerConfig(c *Config, logOptions []log.Option, allowUnknownConfig bool sc.ProfilingFreq = c.Server.ProfilingFreq sc.ProfilingNames = c.Server.ProfilingNames + sc.TLSPolicy = tlspolicy.Policy{ + RequirePQKEM: c.Server.Experimental.RequirePQKEM, + } + + tlspolicy.LogPolicy(sc.TLSPolicy, log.NewHCLogAdapter(logger, "tlspolicy")) + for _, adminID := range c.Server.AdminIDs { id, err := spiffeid.FromString(adminID) if err != nil { diff --git a/cmd/spire-server/cli/run/run_test.go b/cmd/spire-server/cli/run/run_test.go index cb089b1a10..9a9135e14d 100644 --- a/cmd/spire-server/cli/run/run_test.go +++ b/cmd/spire-server/cli/run/run_test.go @@ -64,6 +64,7 @@ func TestParseConfigGood(t *testing.T) { _, ok := trustDomainConfig.EndpointProfile.(bundleClient.HTTPSWebProfile) assert.True(t, ok) assert.True(t, c.Server.AuditLogEnabled) + assert.True(t, c.Server.Experimental.RequirePQKEM) testParseConfigGoodOS(t, c) // Parse/reprint cycle trims outer whitespace @@ -455,6 +456,16 @@ func TestMergeInput(t *testing.T) { require.True(t, c.Server.AuditLogEnabled) }, }, + { + msg: "require_pq_kem should be configurable by file", + fileInput: func(c *Config) { + c.Server.Experimental.RequirePQKEM = true + }, + cliFlags: []string{}, + test: func(t *testing.T, c *Config) { + require.True(t, c.Server.Experimental.RequirePQKEM) + }, + }, } cases = append(cases, mergeInputCasesOS(t)...) @@ -1160,6 +1171,22 @@ func TestNewServerConfig(t *testing.T) { }, c.AdminIDs) }, }, + { + msg: "require PQ KEM is disabled (default)", + input: func(c *Config) {}, + test: func(t *testing.T, c *server.Config) { + require.Equal(t, false, c.TLSPolicy.RequirePQKEM) + }, + }, + { + msg: "require PQ KEM is enabled", + input: func(c *Config) { + c.Server.Experimental.RequirePQKEM = true + }, + test: func(t *testing.T, c *server.Config) { + require.Equal(t, true, c.TLSPolicy.RequirePQKEM) + }, + }, } cases = append(cases, newServerConfigCasesOS(t)...) diff --git a/doc/plugin_server_upstreamauthority_spire.md b/doc/plugin_server_upstreamauthority_spire.md index 83bdb6140a..780aa89b35 100644 --- a/doc/plugin_server_upstreamauthority_spire.md +++ b/doc/plugin_server_upstreamauthority_spire.md @@ -19,7 +19,8 @@ These are the current experimental configurations: | experimental | Description | Default | |------------------------------|-----------------------------------------------------------------------------------------------------------|---------| -| workload_api_named_pipe_name | Pipe name of the Workload API named pipe (Windows only; e.g. pipe name of the SPIRE Agent API named pipe) | +| workload_api_named_pipe_name | Pipe name of the Workload API named pipe (Windows only; e.g. pipe name of the SPIRE Agent API named pipe) | | +| require_pq_kem | Require use of a post-quantum-safe key exchange method for TLS handshakes | false | Sample configuration (Unix): diff --git a/doc/spire_agent.md b/doc/spire_agent.md index b747d99b83..456bc44cce 100644 --- a/doc/spire_agent.md +++ b/doc/spire_agent.md @@ -78,6 +78,7 @@ This may be useful for templating configuration files, for example across differ | `named_pipe_name` | Pipe name to bind the SPIRE Agent API named pipe (Windows only) | \spire-agent\public\api | | `sync_interval` | Sync interval with SPIRE server with exponential backoff | 5 sec | | `use_sync_authorized_entries` | Use SyncAuthorizedEntries API for periodically synchronization of authorized entries | false | +| `require_pq_kem` | Require use of a post-quantum-safe key exchange method for TLS handshakes | false | ### Initial trust bundle configuration diff --git a/doc/spire_server.md b/doc/spire_server.md index 468583185f..f2651b121b 100644 --- a/doc/spire_server.md +++ b/doc/spire_server.md @@ -98,6 +98,7 @@ This may be useful for templating configuration files, for example across differ | `sql_transaction_timeout` | Maximum time an SQL transaction could take, used by the events based cache to determine when an event id is unlikely to be used anymore. | 24h | | `auth_opa_policy_engine` | The [auth opa_policy engine](/doc/authorization_policy_engine.md) used for authorization decisions | default SPIRE authorization policy | | `named_pipe_name` | Pipe name of the SPIRE Server API named pipe (Windows only) | \spire-server\private\api | +| `require_pq_kem` | Require use of a post-quantum-safe key exchange method for TLS handshakes | false | | ratelimit | Description | Default | |:--------------|----------------------------------------------------------------------------------------------------------------------------------------------------|---------| diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index ebe7aa7855..550369201f 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -260,6 +260,7 @@ func (a *Agent) attest(ctx context.Context, sto storage.Storage, cat catalog.Cat Log: a.c.Log.WithField(telemetry.SubsystemName, telemetry.Attestor), ServerAddress: a.c.ServerAddress, NodeAttestor: na, + TLSPolicy: a.c.TLSPolicy, } return node_attestor.New(&config).Attest(ctx) } @@ -284,6 +285,7 @@ func (a *Agent) newManager(ctx context.Context, sto storage.Storage, cat catalog SVIDStoreCache: cache, NodeAttestor: na, RotationStrategy: rotationutil.NewRotationStrategy(a.c.AvailabilityTarget), + TLSPolicy: a.c.TLSPolicy, } mgr := manager.New(config) diff --git a/pkg/agent/attestor/node/node.go b/pkg/agent/attestor/node/node.go index f31113dc52..c7d0cdca3e 100644 --- a/pkg/agent/attestor/node/node.go +++ b/pkg/agent/attestor/node/node.go @@ -25,6 +25,7 @@ import ( "github.com/spiffe/spire/pkg/common/telemetry" telemetry_agent "github.com/spiffe/spire/pkg/common/telemetry/agent" telemetry_common "github.com/spiffe/spire/pkg/common/telemetry/common" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/spiffe/spire/pkg/common/util" "github.com/spiffe/spire/pkg/common/x509util" "github.com/zeebo/errs" @@ -58,6 +59,7 @@ type Config struct { Log logrus.FieldLogger ServerAddress string NodeAttestor nodeattestor.NodeAttestor + TLSPolicy tlspolicy.Policy } type attestor struct { @@ -256,6 +258,7 @@ func (a *attestor) serverConn(ctx context.Context, bundle *spiffebundle.Bundle) Address: a.c.ServerAddress, TrustDomain: a.c.TrustDomain, GetBundle: bundle.X509Authorities, + TLSPolicy: a.c.TLSPolicy, }) } diff --git a/pkg/agent/client/client.go b/pkg/agent/client/client.go index 1ec3522f58..e4699b0663 100644 --- a/pkg/agent/client/client.go +++ b/pkg/agent/client/client.go @@ -20,6 +20,7 @@ import ( "github.com/spiffe/spire-api-sdk/proto/spire/api/types" "github.com/spiffe/spire/pkg/common/bundleutil" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/spiffe/spire/proto/spire/common" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -92,6 +93,9 @@ type Config struct { // RotMtx is used to prevent the creation of new connections during SVID rotations RotMtx *sync.RWMutex + + // TLSPolicy determines the post-quantum-safe policy to apply to all TLS connections. + TLSPolicy tlspolicy.Policy } type client struct { @@ -371,6 +375,7 @@ func (c *client) dial(ctx context.Context) (*grpc.ClientConn, error) { } return agentCert }, + TLSPolicy: c.c.TLSPolicy, dialContext: c.dialContext, }) } diff --git a/pkg/agent/client/dial.go b/pkg/agent/client/dial.go index 031572b833..2b6689af28 100644 --- a/pkg/agent/client/dial.go +++ b/pkg/agent/client/dial.go @@ -14,6 +14,7 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig" "github.com/spiffe/go-spiffe/v2/svid/x509svid" "github.com/spiffe/spire/pkg/common/idutil" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/spiffe/spire/pkg/common/x509util" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -38,6 +39,9 @@ type DialServerConfig struct { // certificate to present to the server during the TLS handshake. GetAgentCertificate func() *tls.Certificate + // TLSPolicy determines the post-quantum-safe policy to apply to all TLS connections. + TLSPolicy tlspolicy.Policy + // dialContext is an optional constructor for the grpc client connection. dialContext func(ctx context.Context, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) } @@ -57,6 +61,11 @@ func DialServer(ctx context.Context, config DialServerConfig) (*grpc.ClientConn, tlsConfig = tlsconfig.MTLSClientConfig(newX509SVIDSource(config.GetAgentCertificate), bundleSource, authorizer) } + err = tlspolicy.ApplyPolicy(tlsConfig, config.TLSPolicy) + if err != nil { + return nil, err + } + ctx, cancel := context.WithTimeout(ctx, defaultDialTimeout) defer cancel() diff --git a/pkg/agent/config.go b/pkg/agent/config.go index f1a28b6249..6d0ba53c77 100644 --- a/pkg/agent/config.go +++ b/pkg/agent/config.go @@ -12,6 +12,7 @@ import ( "github.com/spiffe/spire/pkg/common/catalog" "github.com/spiffe/spire/pkg/common/health" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/tlspolicy" ) type Config struct { @@ -103,6 +104,9 @@ type Config struct { // AvailabilityTarget controls how frequently rotate SVIDs AvailabilityTarget time.Duration + + // TLSPolicy determines the post-quantum-safe TLS policy to apply to all TLS connections. + TLSPolicy tlspolicy.Policy } func New(c *Config) *Agent { diff --git a/pkg/agent/manager/config.go b/pkg/agent/manager/config.go index b21b43be2b..215b82da49 100644 --- a/pkg/agent/manager/config.go +++ b/pkg/agent/manager/config.go @@ -18,6 +18,7 @@ import ( "github.com/spiffe/spire/pkg/agent/workloadkey" "github.com/spiffe/spire/pkg/common/rotationutil" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/tlspolicy" ) // Config holds a cache manager configuration @@ -43,6 +44,7 @@ type Config struct { DisableLRUCache bool NodeAttestor nodeattestor.NodeAttestor RotationStrategy *rotationutil.RotationStrategy + TLSPolicy tlspolicy.Policy // Clk is the clock the manager will use to get time Clk clock.Clock @@ -83,6 +85,7 @@ func newManager(c *Config) *manager { NodeAttestor: c.NodeAttestor, Reattestable: c.Reattestable, RotationStrategy: c.RotationStrategy, + TLSPolicy: c.TLSPolicy, } svidRotator, client := svid.NewRotator(rotCfg) diff --git a/pkg/agent/svid/rotator.go b/pkg/agent/svid/rotator.go index a608505f4f..85a1487596 100644 --- a/pkg/agent/svid/rotator.go +++ b/pkg/agent/svid/rotator.go @@ -367,6 +367,7 @@ func (r *rotator) serverConn(ctx context.Context, bundle *spiffebundle.Bundle) ( Address: r.c.ServerAddr, TrustDomain: r.c.TrustDomain, GetBundle: bundle.X509Authorities, + TLSPolicy: r.c.TLSPolicy, }) } diff --git a/pkg/agent/svid/rotator_config.go b/pkg/agent/svid/rotator_config.go index 203c194ec0..b38498142e 100644 --- a/pkg/agent/svid/rotator_config.go +++ b/pkg/agent/svid/rotator_config.go @@ -17,6 +17,7 @@ import ( "github.com/spiffe/spire/pkg/common/backoff" "github.com/spiffe/spire/pkg/common/rotationutil" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/tlspolicy" ) const DefaultRotatorInterval = 5 * time.Second @@ -43,6 +44,9 @@ type RotatorConfig struct { Clk clock.Clock RotationStrategy *rotationutil.RotationStrategy + + // TLSPolicy determines the post-quantum-safe policy for TLS connections. + TLSPolicy tlspolicy.Policy } func NewRotator(c *RotatorConfig) (Rotator, client.Client) { @@ -85,6 +89,7 @@ func newRotator(c *RotatorConfig) (*rotator, client.Client) { } return s.SVID, s.Key, rootCAs }, + TLSPolicy: c.TLSPolicy, } client := client.New(cfg) diff --git a/pkg/common/tlspolicy/tlspolicy.go b/pkg/common/tlspolicy/tlspolicy.go new file mode 100644 index 0000000000..bfbf382ad4 --- /dev/null +++ b/pkg/common/tlspolicy/tlspolicy.go @@ -0,0 +1,49 @@ +// Package tlspolicy provides for configuration and enforcement of policies +// relating to TLS. +package tlspolicy + +import ( + "crypto/tls" + + "github.com/hashicorp/go-hclog" +) + +// Policy describes policy options to be applied to a TLS configuration. +// +// A zero-initialised Policy provides reasonable defaults. +type Policy struct { + // RequirePQKEM determines if a post-quantum-safe KEM should be required for + // TLS connections. + RequirePQKEM bool +} + +// Not exported by crypto/tls, so we define it here from the I-D. +const x25519Kyber768Draft00 tls.CurveID = 0x6399 + +// LogPolicy logs an informational message reporting the configured policy, +// aiding administrators to determine what policy options have been +// successfully enabled. +func LogPolicy(policy Policy, logger hclog.Logger) { + if policy.RequirePQKEM { + logger.Debug("Experimental option 'require_pq_kem' is enabled; all TLS connections will require use of a post-quantum safe KEM") + } +} + +// ApplyPolicy applies the policy options in policy to a given tls.Config, +// which is assumed to have already been obtained from the go-spiffe tlsconfig +// package. +func ApplyPolicy(config *tls.Config, policy Policy) error { + if policy.RequirePQKEM { + // List only known PQ-safe KEMs as valid curves. + config.CurvePreferences = []tls.CurveID{ + x25519Kyber768Draft00, + } + + // Require TLS 1.3, as all PQ-safe KEMs require it anyway. + if config.MinVersion < tls.VersionTLS13 { + config.MinVersion = tls.VersionTLS13 + } + } + + return nil +} diff --git a/pkg/common/tlspolicy/tlspolicy_test.go b/pkg/common/tlspolicy/tlspolicy_test.go new file mode 100644 index 0000000000..1233e801cd --- /dev/null +++ b/pkg/common/tlspolicy/tlspolicy_test.go @@ -0,0 +1,35 @@ +package tlspolicy + +import ( + "crypto/tls" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestApplyPolicy(t *testing.T) { + require := require.New(t) + + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + } + err := ApplyPolicy(tlsConfig, Policy{}) + require.NoError(err) + + require.Equal(0, len(tlsConfig.CurvePreferences)) + require.Equal(uint16(tls.VersionTLS12), tlsConfig.MinVersion) + + tlsConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + CurvePreferences: []tls.CurveID{ + x25519Kyber768Draft00, tls.CurveP256, + }, + } + err = ApplyPolicy(tlsConfig, Policy{ + RequirePQKEM: true, + }) + require.NoError(err) + + require.Equal(tlsConfig.CurvePreferences, []tls.CurveID{x25519Kyber768Draft00}) + require.Equal(tlsConfig.MinVersion, uint16(tls.VersionTLS13)) +} diff --git a/pkg/server/bundle/client/client.go b/pkg/server/bundle/client/client.go index 8b1adbd35f..2462a0917b 100644 --- a/pkg/server/bundle/client/client.go +++ b/pkg/server/bundle/client/client.go @@ -13,6 +13,7 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig" "github.com/spiffe/spire/pkg/common/bundleutil" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/zeebo/errs" ) @@ -38,6 +39,10 @@ type ClientConfig struct { //revive:disable-line:exported name stutter is intent // is authenticated via Web PKI. SPIFFEAuth *SPIFFEAuthConfig + // TLSPolicy specifies the post-quantum-security policy used for TLS + // connections. + TLSPolicy tlspolicy.Policy + // mutateTransportHook is a hook to influence the transport used during // tests. mutateTransportHook func(*http.Transport) @@ -66,6 +71,11 @@ func NewClient(config ClientConfig) (Client, error) { authorizer := tlsconfig.AuthorizeID(endpointID) transport.TLSClientConfig = tlsconfig.TLSClientConfig(bundle, authorizer) + + err := tlspolicy.ApplyPolicy(transport.TLSClientConfig, config.TLSPolicy) + if err != nil { + return nil, err + } } if config.mutateTransportHook != nil { config.mutateTransportHook(transport) diff --git a/pkg/server/config.go b/pkg/server/config.go index fdbef83671..c25a76208b 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -10,6 +10,7 @@ import ( common "github.com/spiffe/spire/pkg/common/catalog" "github.com/spiffe/spire/pkg/common/health" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/tlspolicy" loggerv1 "github.com/spiffe/spire/pkg/server/api/logger/v1" "github.com/spiffe/spire/pkg/server/authpolicy" bundle_client "github.com/spiffe/spire/pkg/server/bundle/client" @@ -120,6 +121,9 @@ type Config struct { // calculation (prefer the TTL passed by the downstream caller, then fall // back to the default X509 CA TTL). UseLegacyDownstreamX509CATTL bool + + // TLSPolicy determines the policy settings to apply to all TLS connections. + TLSPolicy tlspolicy.Policy } type ExperimentalConfig struct { diff --git a/pkg/server/credtemplate/builder.go b/pkg/server/credtemplate/builder.go index 150d2dd9e2..fb2a4e1ae4 100644 --- a/pkg/server/credtemplate/builder.go +++ b/pkg/server/credtemplate/builder.go @@ -15,6 +15,7 @@ import ( "github.com/go-jose/go-jose/v4/jwt" "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/spire/pkg/common/idutil" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/spiffe/spire/pkg/common/x509util" "github.com/spiffe/spire/pkg/server/api" "github.com/spiffe/spire/pkg/server/plugin/credentialcomposer" @@ -111,6 +112,7 @@ type Config struct { CredentialComposers []credentialcomposer.CredentialComposer NewSerialNumber func() (*big.Int, error) UseLegacyDownstreamX509CATTL bool + TLSPolicy tlspolicy.Policy } type Builder struct { diff --git a/pkg/server/endpoints/config.go b/pkg/server/endpoints/config.go index eaa739f577..9b97328940 100644 --- a/pkg/server/endpoints/config.go +++ b/pkg/server/endpoints/config.go @@ -14,6 +14,7 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/spire/pkg/common/bundleutil" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/spiffe/spire/pkg/server/api" agentv1 "github.com/spiffe/spire/pkg/server/api/agent/v1" bundlev1 "github.com/spiffe/spire/pkg/server/api/bundle/v1" @@ -107,6 +108,10 @@ type Config struct { // calculation (prefer the TTL passed by the downstream caller, then fall // back to the default X509 CA TTL). UseLegacyDownstreamX509CATTL bool + + // TLSPolicy determines the post-quantum-safe policy used for all TLS + // connections. + TLSPolicy tlspolicy.Policy } func (c *Config) maybeMakeBundleEndpointServer() (Server, func(context.Context) error) { diff --git a/pkg/server/endpoints/endpoints.go b/pkg/server/endpoints/endpoints.go index 983f3e94ba..9ddad53723 100644 --- a/pkg/server/endpoints/endpoints.go +++ b/pkg/server/endpoints/endpoints.go @@ -32,6 +32,7 @@ import ( "github.com/spiffe/spire/pkg/common/auth" "github.com/spiffe/spire/pkg/common/peertracker" "github.com/spiffe/spire/pkg/common/telemetry" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/spiffe/spire/pkg/common/util" "github.com/spiffe/spire/pkg/server/api" "github.com/spiffe/spire/pkg/server/api/middleware" @@ -84,6 +85,7 @@ type Endpoints struct { AuditLogEnabled bool AuthPolicyEngine *authpolicy.Engine AdminIDs []spiffeid.ID + TLSPolicy tlspolicy.Policy } type APIServers struct { @@ -176,6 +178,7 @@ func New(ctx context.Context, c Config) (*Endpoints, error) { AuditLogEnabled: c.AuditLogEnabled, AuthPolicyEngine: c.AuthPolicyEngine, AdminIDs: c.AdminIDs, + TLSPolicy: c.TLSPolicy, }, nil } @@ -359,6 +362,11 @@ func (e *Endpoints) getTLSConfig(ctx context.Context) func(*tls.ClientHelloInfo) spiffeTLSConfig.NextProtos = []string{http2.NextProtoTLS} spiffeTLSConfig.VerifyPeerCertificate = e.serverSpiffeVerificationFunc(bundleSrc) + err := tlspolicy.ApplyPolicy(spiffeTLSConfig, e.TLSPolicy) + if err != nil { + return nil, err + } + return spiffeTLSConfig, nil } } diff --git a/pkg/server/endpoints/endpoints_test.go b/pkg/server/endpoints/endpoints_test.go index 18b220c2b4..aee6ee95fd 100644 --- a/pkg/server/endpoints/endpoints_test.go +++ b/pkg/server/endpoints/endpoints_test.go @@ -24,6 +24,7 @@ import ( svidv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/svid/v1" trustdomainv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/trustdomain/v1" "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/spiffe/spire/pkg/common/util" "github.com/spiffe/spire/pkg/server/authpolicy" "github.com/spiffe/spire/pkg/server/ca/manager" @@ -102,6 +103,9 @@ func TestNew(t *testing.T) { RateLimit: rateLimit, Clock: clk, AuthPolicyEngine: pe, + TLSPolicy: tlspolicy.Policy{ + RequirePQKEM: true, + }, }) require.NoError(t, err) assert.Equal(t, tcpAddr, endpoints.TCPAddr) @@ -118,6 +122,7 @@ func TestNew(t *testing.T) { assert.NotNil(t, endpoints.BundleEndpointServer) assert.NotNil(t, endpoints.APIServers.LocalAUthorityServer) assert.NotNil(t, endpoints.EntryFetcherPruneEventsTask) + assert.True(t, endpoints.TLSPolicy.RequirePQKEM) assert.Equal(t, cat.GetDataStore(), endpoints.DataStore) assert.Equal(t, log, endpoints.Log) assert.Equal(t, metrics, endpoints.Metrics) @@ -259,19 +264,27 @@ func TestListenAndServe(t *testing.T) { require.NoError(t, err) defer localConn.Close() - noauthConn := dialTCP(tlsconfig.TLSClientConfig(ca.X509Bundle(), tlsconfig.AuthorizeID(serverID))) + noauthConfig := tlsconfig.TLSClientConfig(ca.X509Bundle(), tlsconfig.AuthorizeID(serverID)) + require.NoError(t, tlspolicy.ApplyPolicy(noauthConfig, endpoints.TLSPolicy)) + noauthConn := dialTCP(noauthConfig) defer noauthConn.Close() - agentConn := dialTCP(tlsconfig.MTLSClientConfig(agentSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID))) + agentConfig := tlsconfig.MTLSClientConfig(agentSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID)) + require.NoError(t, tlspolicy.ApplyPolicy(agentConfig, endpoints.TLSPolicy)) + agentConn := dialTCP(agentConfig) defer agentConn.Close() - adminConn := dialTCP(tlsconfig.MTLSClientConfig(adminSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID))) + adminConfig := tlsconfig.MTLSClientConfig(adminSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID)) + require.NoError(t, tlspolicy.ApplyPolicy(adminConfig, endpoints.TLSPolicy)) + adminConn := dialTCP(adminConfig) defer adminConn.Close() downstreamConn := dialTCP(tlsconfig.MTLSClientConfig(downstreamSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID))) defer downstreamConn.Close() - federatedAdminConn := dialTCP(tlsconfig.MTLSClientConfig(foreignAdminSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID))) + federatedAdminConfig := tlsconfig.MTLSClientConfig(foreignAdminSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID)) + require.NoError(t, tlspolicy.ApplyPolicy(federatedAdminConfig, endpoints.TLSPolicy)) + federatedAdminConn := dialTCP(federatedAdminConfig) defer federatedAdminConn.Close() t.Run("Bad Client SVID", func(t *testing.T) { @@ -280,8 +293,12 @@ func TestListenAndServe(t *testing.T) { badSVID := testca.New(t, testTD).CreateX509SVID(agentID) ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() + + tlsConfig := tlsconfig.MTLSClientConfig(badSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID)) + require.NoError(t, tlspolicy.ApplyPolicy(tlsConfig, endpoints.TLSPolicy)) + badConn, err := grpc.DialContext(ctx, endpoints.TCPAddr.String(), grpc.WithBlock(), grpc.FailOnNonTempDialError(true), //nolint: staticcheck // It is going to be resolved on #5152 - grpc.WithTransportCredentials(credentials.NewTLS(tlsconfig.MTLSClientConfig(badSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID)))), + grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), ) if !assert.Error(t, err, "dialing should have failed") { // close the conn if the dialing unexpectedly succeeded @@ -337,6 +354,8 @@ func TestListenAndServe(t *testing.T) { unfederatedConfig := tlsconfig.MTLSClientConfig(unfederatedForeignAdminSVID, ca.X509Bundle(), tlsconfig.AuthorizeID(serverID)) for _, config := range []*tls.Config{unauthenticatedConfig, unauthorizedConfig, unfederatedConfig} { + require.NoError(t, tlspolicy.ApplyPolicy(config, endpoints.TLSPolicy)) + conn, err := grpc.NewClient(endpoints.TCPAddr.String(), grpc.WithTransportCredentials(credentials.NewTLS(config)), ) diff --git a/pkg/server/plugin/upstreamauthority/spire/spire.go b/pkg/server/plugin/upstreamauthority/spire/spire.go index c955c30fd7..55b7fbc65c 100644 --- a/pkg/server/plugin/upstreamauthority/spire/spire.go +++ b/pkg/server/plugin/upstreamauthority/spire/spire.go @@ -20,6 +20,7 @@ import ( "github.com/spiffe/spire/pkg/common/coretypes/x509certificate" "github.com/spiffe/spire/pkg/common/idutil" "github.com/spiffe/spire/pkg/common/pluginconf" + "github.com/spiffe/spire/pkg/common/tlspolicy" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" @@ -51,6 +52,7 @@ func buildConfig(coreConfig catalog.CoreConfig, hclText string, status *pluginco type experimentalConfig struct { WorkloadAPINamedPipeName string `hcl:"workload_api_named_pipe_name" json:"workload_api_named_pipe_name"` + RequirePQKEM bool `hcl:"require_pq_kem" json:"require_pq_kem"` } func BuiltIn() catalog.BuiltIn { @@ -119,7 +121,13 @@ func (p *Plugin) Configure(_ context.Context, req *configv1.ConfigureRequest) (* return nil, status.Errorf(codes.Internal, "unable to build server ID: %v", err) } - p.serverClient = newServerClient(serverID, serverAddr, workloadAPIAddr, p.log) + tlsPolicy := tlspolicy.Policy{ + RequirePQKEM: p.config.Experimental.RequirePQKEM, + } + + tlspolicy.LogPolicy(tlsPolicy, p.log) + + p.serverClient = newServerClient(serverID, serverAddr, workloadAPIAddr, p.log, tlsPolicy) return &configv1.ConfigureResponse{}, nil } diff --git a/pkg/server/plugin/upstreamauthority/spire/spire_server_client.go b/pkg/server/plugin/upstreamauthority/spire/spire_server_client.go index 0ef93c43a9..e827ef9504 100644 --- a/pkg/server/plugin/upstreamauthority/spire/spire_server_client.go +++ b/pkg/server/plugin/upstreamauthority/spire/spire_server_client.go @@ -15,6 +15,7 @@ import ( bundlev1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/bundle/v1" svidv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/svid/v1" "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/pkg/common/tlspolicy" "github.com/spiffe/spire/pkg/common/util" "github.com/spiffe/spire/pkg/common/x509util" "google.golang.org/grpc" @@ -24,12 +25,13 @@ import ( ) // newServerClient creates a new spire-server client -func newServerClient(serverID spiffeid.ID, serverAddr string, workloadAPIAddr net.Addr, log hclog.Logger) *serverClient { +func newServerClient(serverID spiffeid.ID, serverAddr string, workloadAPIAddr net.Addr, log hclog.Logger, tlsPolicy tlspolicy.Policy) *serverClient { return &serverClient{ serverID: serverID, serverAddr: serverAddr, workloadAPIAddr: workloadAPIAddr, log: &logAdapter{log: log}, + tlsPolicy: tlsPolicy, } } @@ -39,6 +41,7 @@ type serverClient struct { serverAddr string workloadAPIAddr net.Addr log logger.Logger + tlsPolicy tlspolicy.Policy mtx sync.RWMutex source *workloadapi.X509Source @@ -60,6 +63,12 @@ func (c *serverClient) start(ctx context.Context) error { } tlsConfig := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeID(c.serverID)) + err = tlspolicy.ApplyPolicy(tlsConfig, c.tlsPolicy) + if err != nil { + source.Close() + return status.Errorf(codes.Internal, "error applying TLS policy: %v", err) + } + conn, err := grpc.NewClient(c.serverAddr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) if err != nil { diff --git a/pkg/server/server.go b/pkg/server/server.go index 27db8ca41b..6693e2fd2e 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -306,6 +306,7 @@ func (s *Server) newCredBuilder(cat catalog.Catalog) (*credtemplate.Builder, err JWTIssuer: s.config.JWTIssuer, CredentialComposers: cat.GetCredentialComposers(), UseLegacyDownstreamX509CATTL: s.config.UseLegacyDownstreamX509CATTL, + TLSPolicy: s.config.TLSPolicy, }) } diff --git a/test/fixture/config/server_good_posix.conf b/test/fixture/config/server_good_posix.conf index ae273f4c95..3474bf1eab 100644 --- a/test/fixture/config/server_good_posix.conf +++ b/test/fixture/config/server_good_posix.conf @@ -36,6 +36,9 @@ server { bundle_endpoint_profile "https_web" {} } } + experimental { + require_pq_kem = true + } } plugins { diff --git a/test/fixture/config/server_good_windows.conf b/test/fixture/config/server_good_windows.conf index 3accbbc1ef..54527582b0 100644 --- a/test/fixture/config/server_good_windows.conf +++ b/test/fixture/config/server_good_windows.conf @@ -37,6 +37,7 @@ server { } experimental { named_pipe_name = "\\spire-server\\private\\api-test" + require_pq_kem = true } }