From 17aedc0387cab6f17729ed0544e45f2f8afb6b1c Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 30 Sep 2024 03:27:45 -0700 Subject: [PATCH] [v15] Workload ID: Expose local SPIFFE CA's JWT keypairs in SPIFFE trust bundles (#46883) * Expose local SPIFFE CA's JWT keypairs in SPIFFE trust bundles * Switch to old KeyID invocation * Fix parsing of RSA public key --- lib/tbot/spiffe/trust_bundle_cache.go | 21 +++++++++++++++++++++ lib/tbot/spiffe/trust_bundle_cache_test.go | 21 +++++++++++++++++++++ lib/web/spiffe.go | 20 ++++++++++++++++++++ lib/web/spiffe_test.go | 17 +++++++++++++++++ 4 files changed, 79 insertions(+) diff --git a/lib/tbot/spiffe/trust_bundle_cache.go b/lib/tbot/spiffe/trust_bundle_cache.go index b908f1134eb8a..2fc4b5ffdba8e 100644 --- a/lib/tbot/spiffe/trust_bundle_cache.go +++ b/lib/tbot/spiffe/trust_bundle_cache.go @@ -20,6 +20,7 @@ package spiffe import ( "context" + "crypto/rsa" "crypto/x509" "encoding/pem" "log/slog" @@ -37,7 +38,9 @@ import ( machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" trustv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/jwt" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/utils" ) var tracer = otel.Tracer("github.com/gravitational/teleport/lib/spiffe") @@ -610,6 +613,8 @@ func convertSPIFFECAToBundle(ca types.CertAuthority) (*spiffebundle.Bundle, erro } bundle := spiffebundle.New(td) + + // Add X509 authorities to the trust bundle. for _, certBytes := range services.GetTLSCerts(ca) { block, _ := pem.Decode(certBytes) cert, err := x509.ParseCertificate(block.Bytes) @@ -619,6 +624,22 @@ func convertSPIFFECAToBundle(ca types.CertAuthority) (*spiffebundle.Bundle, erro bundle.AddX509Authority(cert) } + // Add JWT authorities to the trust bundle. + for _, keyPair := range ca.GetTrustedJWTKeyPairs() { + pubKey, err := utils.ParsePublicKey(keyPair.PublicKey) + if err != nil { + return nil, trace.Wrap(err, "parsing public key") + } + rsaPubKey, ok := pubKey.(*rsa.PublicKey) + if !ok { + return nil, trace.BadParameter("unsupported key format %T", pubKey) + } + kid := jwt.KeyID(rsaPubKey) + if err := bundle.AddJWTAuthority(kid, pubKey); err != nil { + return nil, trace.Wrap(err, "adding JWT authority to bundle") + } + } + return bundle, nil } diff --git a/lib/tbot/spiffe/trust_bundle_cache_test.go b/lib/tbot/spiffe/trust_bundle_cache_test.go index bde5b9521699c..3512c52ebcb06 100644 --- a/lib/tbot/spiffe/trust_bundle_cache_test.go +++ b/lib/tbot/spiffe/trust_bundle_cache_test.go @@ -20,6 +20,8 @@ package spiffe import ( "context" + "crypto" + "crypto/rsa" "crypto/x509/pkix" "testing" "time" @@ -35,6 +37,8 @@ import ( machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" trustv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth/testauthority" + "github.com/gravitational/teleport/lib/jwt" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" ) @@ -176,6 +180,13 @@ func TestTrustBundleCache_Run(t *testing.T) { require.NoError(t, err) caCert, err := tlsca.ParseCertificatePEM(caCertPEM) require.NoError(t, err) + jwtCAPublic, jwtCAPrivate, err := testauthority.New().GenerateJWT() + require.NoError(t, err) + jwtCA, err := utils.ParsePublicKey(jwtCAPublic) + require.NoError(t, err) + rsaJWTCA, ok := jwtCA.(*rsa.PublicKey) + require.True(t, ok, "unsupported key format %T", jwtCA) + jwtCAKID := jwt.KeyID(rsaJWTCA) ca, err := types.NewCertAuthority(types.CertAuthoritySpecV2{ Type: types.SPIFFECA, ClusterName: "example.com", @@ -186,6 +197,12 @@ func TestTrustBundleCache_Run(t *testing.T) { Key: caKey, }, }, + JWT: []*types.JWTKeyPair{ + { + PublicKey: jwtCAPublic, + PrivateKey: jwtCAPrivate, + }, + }, }, }) require.NoError(t, err) @@ -233,6 +250,10 @@ func TestTrustBundleCache_Run(t *testing.T) { require.Equal(t, "example.com", gotBundleSet.Local.TrustDomain().Name()) require.Len(t, gotBundleSet.Local.X509Authorities(), 1) require.True(t, gotBundleSet.Local.X509Authorities()[0].Equal(caCert)) + require.Len(t, gotBundleSet.Local.JWTAuthorities(), 1) + gotBundleJWTKey, ok := gotBundleSet.Local.FindJWTAuthority(jwtCAKID) + require.True(t, ok, "public key not found in bundle") + require.True(t, gotBundleJWTKey.(interface{ Equal(x crypto.PublicKey) bool }).Equal(jwtCA), "public keys do not match") // Check the federated bundle gotFederatedBundle, ok := gotBundleSet.Federated["pre-init-federated.example.com"] require.True(t, ok) diff --git a/lib/web/spiffe.go b/lib/web/spiffe.go index a47e938c51387..304f65552e0d5 100644 --- a/lib/web/spiffe.go +++ b/lib/web/spiffe.go @@ -17,6 +17,7 @@ package web import ( + "crypto/rsa" "net/http" "time" @@ -26,8 +27,10 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/jwt" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils" ) // getSPIFFEBundle returns the SPIFFE-compatible trust bundle which allows other @@ -71,6 +74,7 @@ func (h *Handler) getSPIFFEBundle(w http.ResponseWriter, r *http.Request, _ http return nil, trace.Wrap(err, "fetching SPIFFE CA") } + // Add X509 authorities to the trust bundle. for _, certPEM := range services.GetTLSCerts(spiffeCA) { cert, err := tlsca.ParseCertificatePEM(certPEM) if err != nil { @@ -79,6 +83,22 @@ func (h *Handler) getSPIFFEBundle(w http.ResponseWriter, r *http.Request, _ http bundle.AddX509Authority(cert) } + // Add JWT authorities to the trust bundle. + for _, keyPair := range spiffeCA.GetTrustedJWTKeyPairs() { + pubKey, err := utils.ParsePublicKey(keyPair.PublicKey) + if err != nil { + return nil, trace.Wrap(err, "parsing public key") + } + rsaPubKey, ok := pubKey.(*rsa.PublicKey) + if !ok { + return nil, trace.BadParameter("unsupported key format %T", pubKey) + } + kid := jwt.KeyID(rsaPubKey) + if err := bundle.AddJWTAuthority(kid, pubKey); err != nil { + return nil, trace.Wrap(err, "adding JWT authority to bundle") + } + } + bundleBytes, err := bundle.Marshal() if err != nil { return nil, trace.Wrap(err, "marshaling bundle") diff --git a/lib/web/spiffe_test.go b/lib/web/spiffe_test.go index 7d04c80ad7465..fdb71479d1888 100644 --- a/lib/web/spiffe_test.go +++ b/lib/web/spiffe_test.go @@ -20,6 +20,8 @@ package web import ( "context" + "crypto" + "crypto/rsa" "crypto/x509" "testing" @@ -30,8 +32,10 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/jwt" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils" ) func TestGetSPIFFEBundle(t *testing.T) { @@ -68,4 +72,17 @@ func TestGetSPIFFEBundle(t *testing.T) { for _, caCert := range wantCACerts { require.True(t, gotBundle.HasX509Authority(caCert), "certificate not found in bundle") } + + require.Len(t, gotBundle.JWTAuthorities(), len(ca.GetTrustedJWTKeyPairs())) + for _, jwtKeyPair := range ca.GetTrustedJWTKeyPairs() { + wantKey, err := utils.ParsePublicKey(jwtKeyPair.PublicKey) + require.NoError(t, err) + rsaWantKey, ok := wantKey.(*rsa.PublicKey) + require.True(t, ok, "unsupported key type %T", wantKey) + wantKeyID := jwt.KeyID(rsaWantKey) + require.NoError(t, err) + gotPubKey, ok := gotBundle.JWTBundle().FindJWTAuthority(wantKeyID) + require.True(t, ok, "wanted public key not found in bundle") + require.True(t, gotPubKey.(interface{ Equal(x crypto.PublicKey) bool }).Equal(wantKey), "public keys do not match") + } }