Skip to content

Commit

Permalink
[v15] Workload ID: Expose local SPIFFE CA's JWT keypairs in SPIFFE tr…
Browse files Browse the repository at this point in the history
…ust bundles (#46883)

* Expose local SPIFFE CA's JWT keypairs in SPIFFE trust bundles

* Switch to old KeyID invocation

* Fix parsing of RSA public key
  • Loading branch information
strideynet committed Sep 30, 2024
1 parent f45f813 commit 17aedc0
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 0 deletions.
21 changes: 21 additions & 0 deletions lib/tbot/spiffe/trust_bundle_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package spiffe

import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"log/slog"
Expand All @@ -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")
Expand Down Expand Up @@ -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)
Expand All @@ -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
}

Expand Down
21 changes: 21 additions & 0 deletions lib/tbot/spiffe/trust_bundle_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package spiffe

import (
"context"
"crypto"
"crypto/rsa"
"crypto/x509/pkix"
"testing"
"time"
Expand All @@ -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"
)
Expand Down Expand Up @@ -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",
Expand All @@ -186,6 +197,12 @@ func TestTrustBundleCache_Run(t *testing.T) {
Key: caKey,
},
},
JWT: []*types.JWTKeyPair{
{
PublicKey: jwtCAPublic,
PrivateKey: jwtCAPrivate,
},
},
},
})
require.NoError(t, err)
Expand Down Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions lib/web/spiffe.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package web

import (
"crypto/rsa"
"net/http"
"time"

Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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")
Expand Down
17 changes: 17 additions & 0 deletions lib/web/spiffe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package web

import (
"context"
"crypto"
"crypto/rsa"
"crypto/x509"
"testing"

Expand All @@ -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) {
Expand Down Expand Up @@ -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")
}
}

0 comments on commit 17aedc0

Please sign in to comment.