Skip to content
Open
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
6 changes: 5 additions & 1 deletion exp/svid/witsvid/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package witsvid

import "github.com/spiffe/go-spiffe/v2/spiffeid"

// Source is a source of WIT-SVIDs keyed by SPIFFE ID.
// Source is a source of WIT-SVIDs.
type Source interface {
// GetWITSVID returns the default WIT-SVID from the source. It is used by
// code that presents a credential and therefore has no ID to look up.
GetWITSVID() (*SVID, error)

// GetWITSVIDForID returns the WIT-SVID for the given SPIFFE ID.
GetWITSVIDForID(id spiffeid.ID) (*SVID, error)
}
4 changes: 4 additions & 0 deletions exp/svid/witsvid/svid.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ type SVID struct {
// identity should be used by a workload when more than one SVID is returned.
Hint string

// Claims is the parsed claims from the WIT-SVID token.
Claims map[string]interface{}

// token is the serialized JWS compact serialization.
token string
}
Expand Down Expand Up @@ -167,6 +170,7 @@ func parse(token string, verify verifyFn) (*SVID, error) {
Expiry: stdClaims.Expiry.Time().UTC(),
PublicKey: publicKey,
KeyID: keyID,
Claims: rawClaims,
token: token,
}, nil
}
Expand Down
14 changes: 14 additions & 0 deletions exp/svid/witsvid/svid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ func TestParseInsecure(t *testing.T) {
c["nbf"] = jwt.NewNumericDate(time.Now().Add(-time.Minute))
}),
},
{
name: "issuer-supplied claims are exposed",
token: withClaims(func(c map[string]any) {
c["groups"] = []string{"admin", "ops"}
}),
check: func(t *testing.T, svid *witsvid.SVID) {
// Issuer-supplied claims are reachable by a verifier.
assert.Equal(t, []any{"admin", "ops"}, svid.Claims["groups"])
// Standard and confirmation claims come through untouched.
assert.Equal(t, workload.String(), svid.Claims["sub"])
assert.Contains(t, svid.Claims, "exp")
assert.Contains(t, svid.Claims, "cnf")
},
},
{
name: "malformed",
token: func(*testing.T) string { return "not.a.valid.jwt" },
Expand Down
19 changes: 19 additions & 0 deletions workloadapi/witsource.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,25 @@ func (s *WITSource) Close() error {
return s.closeBase(closer)
}

// GetWITSVID returns the default WIT-SVID, which is the first one in the list
// returned by the Workload API (see the SPIFFE Workload API specification §8).
// It implements the witsvid.Source interface.
//
// Experimental: subject to change.
func (s *WITSource) GetWITSVID() (*witsvid.SVID, error) {
if err := s.checkClosed(); err != nil {
return nil, err
}

s.mtx.RLock()
defer s.mtx.RUnlock()

if len(s.svids) == 0 {
return nil, errors.New("witsource: no WIT-SVID available")
}
return s.svids[0], nil
}

// GetWITSVIDForID returns the WIT-SVID for the given SPIFFE ID.
// It implements the witsvid.Source interface.
//
Expand Down
38 changes: 38 additions & 0 deletions workloadapi/witsource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,41 @@ func TestWITSourceLookup(t *testing.T) {
})
}

func TestWITSourceGetWITSVID(t *testing.T) {
t.Run("returns the first SVID as the default identity", func(t *testing.T) {
api := fakeworkloadapi.New(t)
t.Cleanup(api.Stop)

key := test.NewEC256Key(t)
kid := "key-1"
api.SetWITSVIDResponse(&workload.WITSVIDResponse{
Svids: []*workload.WITSVID{
makeWITSVIDProto(t, witFooID, key, test.NewEC256Key(t), kid, ""),
makeWITSVIDProto(t, witBarID, key, test.NewEC256Key(t), kid, ""),
},
})
api.SetWITBundles(makeWITBundle(t, witTD, key, kid))

src, err := workloadapi.NewWITSource(t.Context(), withAddr(api))
require.NoError(t, err)
t.Cleanup(func() { src.Close() })

svid, err := src.GetWITSVID()
require.NoError(t, err)
assert.Equal(t, witFooID, svid.ID)
})

t.Run("errors when the source holds no SVIDs", func(t *testing.T) {
// Not reachable through NewWITSource: parseWITSVIDs rejects an empty
// response and watchWITSVIDs routes that to OnWITSVIDsWatchError, so
// OnWITSVIDsUpdate never delivers an empty slice. The branch is
// defensive; this pins its error rather than a nil SVID.
var src workloadapi.WITSource
_, err := src.GetWITSVID()
require.EqualError(t, err, "witsource: no WIT-SVID available")
})
}

func TestWITSourceClose(t *testing.T) {
api := fakeworkloadapi.New(t)
t.Cleanup(api.Stop)
Expand All @@ -82,6 +117,9 @@ func TestWITSourceClose(t *testing.T) {
_, err := src.GetWITSVIDForID(witFooID)
require.EqualError(t, err, "witsource: source is closed")

_, err = src.GetWITSVID()
require.EqualError(t, err, "witsource: source is closed")

_, err = src.GetWITBundleForTrustDomain(witTD)
require.EqualError(t, err, "witsource: source is closed")
})
Expand Down
Loading