Skip to content

Commit

Permalink
Allow configuration of TLS policy: implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Hugo Landau <[email protected]>
  • Loading branch information
anvega authored and hlandau committed Aug 9, 2024
1 parent a593fc4 commit fc0836d
Show file tree
Hide file tree
Showing 21 changed files with 286 additions and 2 deletions.
7 changes: 7 additions & 0 deletions cmd/spire-agent/cli/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,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 @@ -118,6 +119,7 @@ type experimentalConfig struct {
NamedPipeName string `hcl:"named_pipe_name"`
AdminNamedPipeName string `hcl:"admin_named_pipe_name"`
UseSyncAuthorizedEntries bool `hcl:"use_sync_authorized_entries"`
PQKEMMode string `hcl:"pq_kem_mode"`

Flags fflag.RawConfig `hcl:"feature_flags"`

Expand Down Expand Up @@ -595,6 +597,11 @@ func NewAgentConfig(c *Config, logOptions []log.Option, allowUnknownConfig bool)
ac.AvailabilityTarget = t
}

ac.TLSPolicy.PQKEMMode, err = tlspolicy.ParsePQKEMMode(log.NewHCLogAdapter(logger, "tlspolicy"), c.Agent.Experimental.PQKEMMode)
if err != nil {
return nil, fmt.Errorf("pq_kem_mode config option %q is invalid: %w", c.Agent.Experimental.PQKEMMode, err)
}

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
7 changes: 7 additions & 0 deletions cmd/spire-server/cli/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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 @@ -108,6 +109,7 @@ type experimentalConfig struct {
EventsBasedCache bool `hcl:"events_based_cache"`
PruneEventsOlderThan string `hcl:"prune_events_older_than"`
SQLTransactionTimeout string `hcl:"sql_transaction_timeout"`
PQKEMMode string `hcl:"pq_kem_mode"`

Flags fflag.RawConfig `hcl:"feature_flags"`

Expand Down Expand Up @@ -508,6 +510,11 @@ func NewServerConfig(c *Config, logOptions []log.Option, allowUnknownConfig bool
sc.ProfilingFreq = c.Server.ProfilingFreq
sc.ProfilingNames = c.Server.ProfilingNames

sc.TLSPolicy.PQKEMMode, err = tlspolicy.ParsePQKEMMode(log.NewHCLogAdapter(logger, "tlspolicy"), c.Server.Experimental.PQKEMMode)
if err != nil {
return nil, fmt.Errorf("invalid pq_kem_mode: %q: %w", c.Server.Experimental.PQKEMMode, err)
}

for _, adminID := range c.Server.AdminIDs {
id, err := spiffeid.FromString(adminID)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,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 @@ -282,6 +283,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 @@ -42,6 +43,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 @@ -89,6 +91,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 @@ -313,6 +313,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/agent/plugin/nodeattestor"
"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
145 changes: 145 additions & 0 deletions pkg/common/tlspolicy/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Package tlspolicy provides for configuration and enforcement of policies
// relating to TLS.
package tlspolicy

import (
"crypto/tls"
"errors"
"fmt"

"github.com/hashicorp/go-hclog"
)

// SupportsPQKEM is a constant indicating whether the version of Go used to
// build, and the build configuration, supports a post-quantum safe TLS key
// exchange method.
const SupportsPQKEM = supportsPQKEM

// Post-quantum TLS KEM mode. Determines whether a post-quantum safe KEM should
// be used when establishing a TLS connection.
type PQKEMMode int

const (
// Do not require use of a post-quantum KEM when establishing a TLS
// connection. Whether a post-quantum KEM is attempted depends on
// environmental configuration (e.g. GODEBUG setting tlskyber) and the target
// Go version at build time.
PQKEMModeDefault PQKEMMode = iota

// Attempt use of a post-quantum KEM as the most preferred key exchange
// method when establishing a TLS connection.
// Support for this requires Go 1.23 or later.
// Configuring this will cause connections to fail if support is not available.
PQKEMModeAttempt

// Require use of a post-quantum KEM when establishing a TLS connection.
// Attempts to initiate a connection with a key exchange method which is not
// post-quantum safe will fail. Support for this requires Go 1.23 or later.
// Configuring this will cause connections to fail if support is not available.
PQKEMModeRequire
)

// ParsePQKEMMode parses a string into a PQKEMMode value or returns
// an error.
func ParsePQKEMMode(logger hclog.Logger, value string) (mode PQKEMMode, err error) {
if value != "" {
logger.Warn("pq_kem_mode is experimental and may be changed or removed in a future release")
}

switch value {
case "":
if SupportsPQKEM {
logger.Debug("pq_kem_mode supported in this build; post-quantum-safe TLS key exchange may or may not be used depending on system configuration")
} else {
logger.Debug("pq_kem_mode not supported in this build")
}
return

case "default":
if SupportsPQKEM {
logger.Debug("pq_kem_mode supported and explicitly set to 'default'; post-quantum-safe TLS key exchange may or may not be used depending on system configuration")
} else {
logger.Debug("pq_kem_mode explicitly set to 'default'; post-quantum-safe TLS key exchange not supported in this build")
}
return

case "attempt":
if !SupportsPQKEM {
logger.Warn("pq_kem_mode set to 'attempt' but no post-quantum-safe key exchange methods are supported in this build (requires Go 1.23); ignoring")
return
}

logger.Debug("pq_kem_mode supported and configured in 'attempt' mode")
mode = PQKEMModeAttempt
return

case "require":
if !SupportsPQKEM {
err = errors.New("pq_kem_mode set to 'require' but not supported in this build; requires Go 1.23")
logger.Error(err.Error())
return
}

logger.Debug("pq_kem_mode supported and configured in 'require' mode - will require post-quantum security for all TLS connections")
mode = PQKEMModeRequire
return

default:
mode = PQKEMModeDefault
err = fmt.Errorf("pq_kem_mode of %q is invalid; must be one of ['', 'default', 'attempt', 'require']", value)
return
}
}

// Policy describes policy options to be applied to a TLS configuration.
//
// A zero-initialised Policy provides reasonable defaults.
type Policy struct {
// PQKEMMode specifies the post-quantum KEM policy to use.
PQKEMMode PQKEMMode
}

// Not exported by crypto/tls, so we define it here from the I-D.
const x25519Kyber768Draft00 tls.CurveID = 0x6399

// 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 {
// Apply post-quantum KEM mode option.
switch policy.PQKEMMode {
case PQKEMModeDefault:
// Nothing to do - allow default curve preferences.

case PQKEMModeAttempt:
if len(config.CurvePreferences) == 0 {
// This is copied from the crypto/tls default curve list.
config.CurvePreferences = []tls.CurveID{
x25519Kyber768Draft00,
tls.X25519,
tls.CurveP256,
tls.CurveP384,
tls.CurveP521,
}
} else if config.CurvePreferences[0] != x25519Kyber768Draft00 {
// Prepend X25519Kyber768Draft00 to the list, making it most preferred.
curves := make([]tls.CurveID, 0, len(config.CurvePreferences)+1)
curves = append(curves, x25519Kyber768Draft00)
curves = append(curves, config.CurvePreferences...)
config.CurvePreferences = curves
}

case PQKEMModeRequire:
// 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 fc0836d

Please sign in to comment.