Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v16] Workload ID: Expose local SPIFFE CA's JWT keypairs in SPIFFE trust bundles #46882

Merged
merged 3 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,6 +38,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/api/utils/keys"
"github.com/gravitational/teleport/lib/jwt"
"github.com/gravitational/teleport/lib/services"
)

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 := keys.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
22 changes: 22 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,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/api/utils/keys"
"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 +181,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 := keys.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 +198,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 +251,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,6 +27,8 @@ import (
"github.com/spiffe/go-spiffe/v2/spiffeid"

"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/lib/jwt"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
)
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 := keys.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 @@ -29,7 +31,9 @@ import (
"github.com/stretchr/testify/require"

"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/jwt"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
)
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 := keys.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")
}
}
Loading