Skip to content

Commit

Permalink
[v16] Workload Identity: Federation bundle endpoint (#44998)
Browse files Browse the repository at this point in the history
* 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
strideynet and espadolini authored Aug 8, 2024
1 parent 84f8ccc commit 945ade6
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 0 deletions.
6 changes: 6 additions & 0 deletions integrations/terraform/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,8 @@ github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
Expand Down Expand Up @@ -1827,6 +1829,8 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.2.0 h1:9Vf06UsvsDbLYK/zJ4sYsIsHmMFknUD+feA7IYoWMQY=
github.com/spiffe/go-spiffe/v2 v2.2.0/go.mod h1:Urzb779b3+IwDJD2ZbN8fVl3Aa8G4N/PiUe6iXC0XxU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
Expand Down Expand Up @@ -1918,6 +1922,8 @@ github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
Expand Down
3 changes: 3 additions & 0 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,9 @@ func (h *Handler) bindDefaultEndpoints() {
h.GET(OIDCJWKWURI, h.WithLimiter(h.jwksOIDC))
h.GET("/webapi/thumbprint", h.WithLimiter(h.thumbprint))

// SPIFFE Federation Trust Bundle
h.GET("/webapi/spiffe/bundle.json", h.WithLimiter(h.getSPIFFEBundle))

// DiscoveryConfig CRUD
h.GET("/webapi/sites/:site/discoveryconfig", h.WithClusterAuth(h.discoveryconfigList))
h.POST("/webapi/sites/:site/discoveryconfig", h.WithClusterAuth(h.discoveryconfigCreate))
Expand Down
93 changes: 93 additions & 0 deletions lib/web/spiffe.go
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
}
71 changes: 71 additions & 0 deletions lib/web/spiffe_test.go
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")
}
}

0 comments on commit 945ade6

Please sign in to comment.