-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[v16] Workload Identity: Federation bundle endpoint (#44998)
* Add SPIFFE bundle endpoint * Add comment explaining mountpoint * Add test * Fix misspelling of equivalent * Update lib/web/spiffe.go Co-authored-by: Edoardo Spadolini <[email protected]> * Update lib/web/spiffe.go Co-authored-by: Edoardo Spadolini <[email protected]> * Update lib/web/spiffe.go Co-authored-by: Edoardo Spadolini <[email protected]> * Update lib/web/spiffe_test.go Co-authored-by: Edoardo Spadolini <[email protected]> * Fix test * Fix imports * Go mod tidy --------- Co-authored-by: Edoardo Spadolini <[email protected]>
- Loading branch information
1 parent
84f8ccc
commit 945ade6
Showing
4 changed files
with
173 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// Teleport | ||
// Copyright (C) 2024 Gravitational, Inc. | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package web | ||
|
||
import ( | ||
"net/http" | ||
"time" | ||
|
||
"github.com/gravitational/trace" | ||
"github.com/julienschmidt/httprouter" | ||
"github.com/spiffe/go-spiffe/v2/bundle/spiffebundle" | ||
"github.com/spiffe/go-spiffe/v2/spiffeid" | ||
|
||
"github.com/gravitational/teleport/api/types" | ||
"github.com/gravitational/teleport/lib/services" | ||
"github.com/gravitational/teleport/lib/tlsca" | ||
) | ||
|
||
// getSPIFFEBundle returns the SPIFFE-compatible trust bundle which allows other | ||
// trust domains to federate with this Teleport cluster. | ||
// | ||
// Mounted at /webapi/spiffe/bundle.json | ||
// | ||
// Must abide by the standard for a "https_web" profile as described in | ||
// https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Federation.md#5-serving-and-consuming-a-spiffe-bundle-endpoint | ||
func (h *Handler) getSPIFFEBundle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) (any, error) { | ||
cn, err := h.GetAccessPoint().GetClusterName() | ||
if err != nil { | ||
return nil, trace.Wrap(err, "fetching cluster name") | ||
} | ||
|
||
td, err := spiffeid.TrustDomainFromString(cn.GetClusterName()) | ||
if err != nil { | ||
return nil, trace.Wrap(err, "creating trust domain") | ||
} | ||
|
||
bundle := spiffebundle.New(td) | ||
// The refresh hint indicates how often a federated trust domain should | ||
// check for updates to the bundle. This should be a low value to ensure | ||
// that CA rotations are picked up quickly. Since we're leveraging | ||
// https_web, it's not critical for a federated trust domain to catch | ||
// all phases of the rotation - however, if we support https_spiffe in | ||
// future, we may need to consider a lower value or enforcing a wait | ||
// period during rotations equivalent to the refresh hint. | ||
bundle.SetRefreshHint(5 * time.Minute) | ||
// TODO(noah): | ||
// For now, we omit the SequenceNumber field. This is only a SHOULD not a | ||
// MUST per the spec. To add this, we will add a sequence number to the | ||
// cert authority and increment it on every update. | ||
|
||
const loadKeysFalse = false | ||
spiffeCA, err := h.GetAccessPoint().GetCertAuthority(r.Context(), types.CertAuthID{ | ||
Type: types.SPIFFECA, | ||
DomainName: cn.GetClusterName(), | ||
}, loadKeysFalse) | ||
if err != nil { | ||
return nil, trace.Wrap(err, "fetching SPIFFE CA") | ||
} | ||
|
||
for _, certPEM := range services.GetTLSCerts(spiffeCA) { | ||
cert, err := tlsca.ParseCertificatePEM(certPEM) | ||
if err != nil { | ||
return nil, trace.Wrap(err, "parsing certificate") | ||
} | ||
bundle.AddX509Authority(cert) | ||
} | ||
|
||
bundleBytes, err := bundle.Marshal() | ||
if err != nil { | ||
return nil, trace.Wrap(err, "marshaling bundle") | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(http.StatusOK) | ||
if _, err = w.Write(bundleBytes); err != nil { | ||
h.logger.DebugContext(h.cfg.Context, "Failed to write SPIFFE bundle response", "error", err) | ||
} | ||
return nil, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Teleport | ||
* Copyright (C) 2024 Gravitational, Inc. | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package web | ||
|
||
import ( | ||
"context" | ||
"crypto/x509" | ||
"testing" | ||
|
||
"github.com/gravitational/roundtrip" | ||
"github.com/spiffe/go-spiffe/v2/bundle/spiffebundle" | ||
"github.com/spiffe/go-spiffe/v2/spiffeid" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/gravitational/teleport/api/types" | ||
"github.com/gravitational/teleport/lib/client" | ||
"github.com/gravitational/teleport/lib/services" | ||
"github.com/gravitational/teleport/lib/tlsca" | ||
) | ||
|
||
func TestGetSPIFFEBundle(t *testing.T) { | ||
ctx := context.Background() | ||
env := newWebPack(t, 1) | ||
authServer := env.server.Auth() | ||
cn, err := authServer.GetClusterName() | ||
require.NoError(t, err) | ||
ca, err := authServer.GetCertAuthority(ctx, types.CertAuthID{ | ||
Type: types.SPIFFECA, | ||
DomainName: cn.GetClusterName(), | ||
}, false) | ||
require.NoError(t, err) | ||
|
||
var wantCACerts []*x509.Certificate | ||
for _, certPem := range services.GetTLSCerts(ca) { | ||
cert, err := tlsca.ParseCertificatePEM(certPem) | ||
require.NoError(t, err) | ||
wantCACerts = append(wantCACerts, cert) | ||
} | ||
|
||
clt, err := client.NewWebClient(env.proxies[0].webURL.String(), roundtrip.HTTPClient(client.NewInsecureWebClient())) | ||
require.NoError(t, err) | ||
|
||
res, err := clt.Get(ctx, clt.Endpoint("webapi", "spiffe", "bundle.json"), nil) | ||
require.NoError(t, err) | ||
|
||
td, err := spiffeid.TrustDomainFromString(cn.GetClusterName()) | ||
require.NoError(t, err) | ||
gotBundle, err := spiffebundle.Read(td, res.Reader()) | ||
require.NoError(t, err) | ||
|
||
require.Len(t, gotBundle.X509Authorities(), len(wantCACerts)) | ||
for _, caCert := range wantCACerts { | ||
require.True(t, gotBundle.HasX509Authority(caCert), "certificate not found in bundle") | ||
} | ||
} |