Skip to content

Commit

Permalink
Allow requiring use of a post-quantum-safe KEM (spiffe#5601)
Browse files Browse the repository at this point in the history
* 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 <hl@messier42.com>
Signed-off-by: Marcos Yacob <marcosyacob@gmail.com>
Co-authored-by: Marcos Yacob <marcosyacob@gmail.com>
  • Loading branch information
hlandau and MarcosDY authored Nov 27, 2024
1 parent ae25343 commit ef5a697
Show file tree
Hide file tree
Showing 28 changed files with 267 additions and 8 deletions.
8 changes: 8 additions & 0 deletions cmd/spire-agent/cli/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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"`
}
Expand Down Expand Up @@ -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.")
}
Expand Down
27 changes: 27 additions & 0 deletions cmd/spire-agent/cli/run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()...)

Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions cmd/spire-server/cli/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"`

Expand Down Expand Up @@ -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 {
Expand Down
27 changes: 27 additions & 0 deletions cmd/spire-server/cli/run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)...)

Expand Down Expand Up @@ -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)...)

Expand Down
3 changes: 2 additions & 1 deletion doc/plugin_server_upstreamauthority_spire.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down
1 change: 1 addition & 0 deletions doc/spire_agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions doc/spire_server.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|:--------------|----------------------------------------------------------------------------------------------------------------------------------------------------|---------|
Expand Down
2 changes: 2 additions & 0 deletions pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions pkg/agent/attestor/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -58,6 +59,7 @@ type Config struct {
Log logrus.FieldLogger
ServerAddress string
NodeAttestor nodeattestor.NodeAttestor
TLSPolicy tlspolicy.Policy
}

type attestor struct {
Expand Down Expand Up @@ -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,
})
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/agent/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -371,6 +375,7 @@ func (c *client) dial(ctx context.Context) (*grpc.ClientConn, error) {
}
return agentCert
},
TLSPolicy: c.c.TLSPolicy,
dialContext: c.dialContext,
})
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/agent/client/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
}
Expand All @@ -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()

Expand Down
4 changes: 4 additions & 0 deletions pkg/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions pkg/agent/manager/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions pkg/agent/svid/rotator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/agent/svid/rotator_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -85,6 +89,7 @@ func newRotator(c *RotatorConfig) (*rotator, client.Client) {
}
return s.SVID, s.Key, rootCAs
},
TLSPolicy: c.TLSPolicy,
}
client := client.New(cfg)

Expand Down
49 changes: 49 additions & 0 deletions pkg/common/tlspolicy/tlspolicy.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit ef5a697

Please sign in to comment.