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

Include hint field in agent's workload api #3993

Merged
merged 12 commits into from
Apr 12, 2023
17 changes: 10 additions & 7 deletions cmd/spire-agent/cli/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func TestFetchJWTCommand(t *testing.T) {
{
SpiffeId: "spiffe://domain1.test",
Svid: encodedSvid1,
Hint: "external",
},
{
SpiffeId: "spiffe://domain2.test",
Expand All @@ -92,6 +93,7 @@ func TestFetchJWTCommand(t *testing.T) {
},
expectedStdoutPretty: []string{
fmt.Sprintf("token(spiffe://domain1.test):\n\t%s", encodedSvid1),
fmt.Sprintf("hint(spiffe://domain1.test):\n\t%s", "external"),
fmt.Sprintf("token(spiffe://domain2.test):\n\t%s", encodedSvid2),
fmt.Sprintf("bundle(spiffe://domain1.test):\n\t%s", bundleJWKSBytes),
fmt.Sprintf("bundle(spiffe://domain2.test):\n\t%s", bundleJWKSBytes),
Expand All @@ -100,7 +102,7 @@ func TestFetchJWTCommand(t *testing.T) {
{
"svids": [
{
"hint": "",
"hint": "external",
"spiffe_id": "spiffe://domain1.test",
"svid": "%s"
},
Expand Down Expand Up @@ -221,6 +223,7 @@ func TestFetchX509Command(t *testing.T) {
X509Svid: x509util.DERFromCertificates(svid.Certificates),
X509SvidKey: pkcs8FromSigner(t, svid.PrivateKey),
Bundle: x509util.DERFromCertificates(ca.Bundle().X509Authorities()),
Hint: "external",
},
},
Crl: [][]byte{},
Expand All @@ -230,6 +233,7 @@ func TestFetchX509Command(t *testing.T) {
},
expectedStdoutPretty: fmt.Sprintf(`
SPIFFE ID: spiffe://example.org/foo
Hint: external
SVID Valid After: %v
SVID Valid Until: %v
CA #1 Valid After: %v
Expand All @@ -246,7 +250,7 @@ CA #1 Valid Until: %v
"svids": [
{
"bundle": "%s",
"hint": "",
"hint": "external",
"spiffe_id": "spiffe://example.org/foo",
"x509_svid": "%s",
"x509_svid_key": "%s"
Expand Down Expand Up @@ -302,8 +306,8 @@ Writing bundle #0 to file %s
"federated_bundles": {},
"svids": [
{
"hint": "",
"bundle": "%s",
"hint": "",
"spiffe_id": "spiffe://example.org/foo",
"x509_svid": "%s",
"x509_svid_key": "%s"
Expand Down Expand Up @@ -412,10 +416,9 @@ func TestValidateJWTCommand(t *testing.T) {
Claims: &structpb.Struct{
Fields: map[string]*structpb.Value{
"aud": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{{Kind: &structpb.Value_StringValue{StringValue: "foo"}}},
},
Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{
Values: []*structpb.Value{{Kind: &structpb.Value_StringValue{StringValue: "foo"}}},
},
},
},
},
Expand Down
3 changes: 3 additions & 0 deletions cmd/spire-agent/cli/api/fetch_jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ func printPrettyResult(env *commoncli.Env, results ...interface{}) error {

for _, svid := range svidResp.Svids {
env.Printf("token(%s):\n\t%s\n", svid.SpiffeId, svid.Svid)
if svid.Hint != "" {
env.Printf("hint(%s):\n\t%s\n", svid.SpiffeId, svid.Hint)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm looks like spiffeID here is redundant, since you already printed spiffeID with token

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also include the spiffe ID in the printed bundle (on line 101), I tried to maintain the same logic here for hint, what do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cant think in another good ouput... so keeping the duplicated SPIFFEID may be good

}
}

for trustDomainID, jwksJSON := range bundlesResp.Bundles {
Expand Down
2 changes: 2 additions & 0 deletions cmd/spire-agent/cli/api/fetch_x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func (c *fetchX509Command) prettyPrintFetchX509(env *commoncli.Env, results ...i

type X509SVID struct {
SPIFFEID string
Hint string
Certificates []*x509.Certificate
PrivateKey crypto.Signer
Bundle []*x509.Certificate
Expand Down Expand Up @@ -253,6 +254,7 @@ func parseX509SVID(svid *workload.X509SVID, federatedBundles map[string][]*x509.
Certificates: certificates,
Bundle: bundle,
FederatedBundles: federatedBundles,
Hint: svid.Hint,
}, nil
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/spire-agent/cli/api/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ func printX509SVID(env *commoncli.Env, svid *X509SVID) {
// Print SPIFFE ID first so if we run into a problem, we
// get to know which record it was
env.Printf("SPIFFE ID:\t\t%s\n", svid.SPIFFEID)
if svid.Hint != "" {
env.Printf("Hint:\t\t\t%s\n", svid.Hint)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like it has 3 tabs, but spiffeID has only 2, do you want to add some hierarchy level?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for maintaining the output aligned
Two tabs
image
Three tabs
image

}

env.Printf("SVID Valid After:\t%v\n", svid.Certificates[0].NotBefore)
env.Printf("SVID Valid Until:\t%v\n", svid.Certificates[0].NotAfter)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ require (
github.com/sigstore/rekor v1.0.1
github.com/sigstore/sigstore v1.5.2
github.com/sirupsen/logrus v1.9.0
github.com/spiffe/go-spiffe/v2 v2.1.3
github.com/spiffe/go-spiffe/v2 v2.1.4
github.com/spiffe/spire-api-sdk v1.2.5-0.20230315170933-494fe186be48
github.com/spiffe/spire-plugin-sdk v1.4.4-0.20230203133000-75d7213a0ba0
github.com/stretchr/testify v1.8.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2010,8 +2010,8 @@ github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
github.com/spiffe/go-spiffe/v2 v2.1.3 h1:P5L9Ixo5eqJiHnktAU0UD/6UfHsQs7yAtc8a/FFUi9M=
github.com/spiffe/go-spiffe/v2 v2.1.3/go.mod h1:eVDqm9xFvyqao6C+eQensb9ZPkyNEeaUbqbBpOhBnNk=
github.com/spiffe/go-spiffe/v2 v2.1.4 h1:Z31Ycaf2Z5DF38sQGmp+iGKjBhBlSzfAq68bfy67Mxw=
github.com/spiffe/go-spiffe/v2 v2.1.4/go.mod h1:eVDqm9xFvyqao6C+eQensb9ZPkyNEeaUbqbBpOhBnNk=
github.com/spiffe/spire-api-sdk v1.2.5-0.20230315170933-494fe186be48 h1:jRrlbqir48TQ4yMupNf9I1/OMrVTK0myhWxwOYqiS0g=
github.com/spiffe/spire-api-sdk v1.2.5-0.20230315170933-494fe186be48/go.mod h1:4uuhFlN6KBWjACRP3xXwrOTNnvaLp1zJs8Lribtr4fI=
github.com/spiffe/spire-plugin-sdk v1.4.4-0.20230203133000-75d7213a0ba0 h1:+ETVN721ZSZvi8CmR0oGf2KRSIkVMvWC8PqON9IknrM=
Expand Down
1 change: 1 addition & 0 deletions pkg/agent/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ func (c *client) fetchEntries(ctx context.Context) ([]*types.Entry, error) {
Downstream: true,
RevisionNumber: true,
StoreSvid: true,
Hint: true,
},
})
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions pkg/agent/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ var (
"spiffe://domain1.com",
},
RevisionNumber: 1234,
Hint: "external",
},
// This entry should be ignored since it is missing an entry ID
{
Expand Down Expand Up @@ -106,6 +107,7 @@ func TestFetchUpdates(t *testing.T) {
},
FederatesWith: []string{"domain1.com"},
RevisionNumber: 1234,
Hint: "external",
},
// This entry should be ignored since it is missing an entry ID
{
Expand Down Expand Up @@ -364,6 +366,7 @@ func TestFetchReleaseWaitsForFetchUpdatesToFinish(t *testing.T) {
},
FederatesWith: []string{"domain1.com"},
RevisionNumber: 1234,
Hint: "external",
},
// This entry should be ignored since it is missing an entry ID
{
Expand Down Expand Up @@ -781,6 +784,7 @@ func (c *fakeEntryClient) GetAuthorizedEntries(ctx context.Context, in *entryv1.
Downstream: true,
RevisionNumber: true,
StoreSvid: true,
Hint: true,
}, protocmp.Transform()); diff != "" {
return nil, status.Error(codes.InvalidArgument, "invalid output mask requested")
}
Expand Down
1 change: 1 addition & 0 deletions pkg/agent/client/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,6 @@ func slicedEntryFromProto(e *types.Entry) (*common.RegistrationEntry, error) {
StoreSvid: e.StoreSvid,
Admin: e.Admin,
Downstream: e.Downstream,
Hint: e.Hint,
}, nil
}
144 changes: 115 additions & 29 deletions pkg/agent/endpoints/workload/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ func New(c Config) *Handler {
}
}

// FetchJWTSVID processes request for a JWT-SVID
// FetchJWTSVID processes request for a JWT-SVID. In case of multiple fetched SVIDs with same hint, the SVID that has the oldest
// associated entry will be returned.
func (h *Handler) FetchJWTSVID(ctx context.Context, req *workload.JWTSVIDRequest) (resp *workload.JWTSVIDResponse, err error) {
log := rpccontext.Logger(ctx)
if len(req.Audience) == 0 {
Expand All @@ -82,49 +83,31 @@ func (h *Handler) FetchJWTSVID(ctx context.Context, req *workload.JWTSVIDRequest
return nil, err
}

var spiffeIDs []spiffeid.ID

log = log.WithField(telemetry.Registered, true)

entries := h.c.Manager.MatchingRegistrationEntries(selectors)
entries = filterRegistrations(entries, log)

resp = new(workload.JWTSVIDResponse)

for _, entry := range entries {
if req.SpiffeId != "" && entry.SpiffeId != req.SpiffeId {
continue
}

spiffeID, err := spiffeid.FromString(entry.SpiffeId)
loopLog := log.WithField(telemetry.SPIFFEID, entry.SpiffeId)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: since loopLog is used only on h.fetchJWTSVID may we remove the break line? and couple both lines?

svid, err := h.fetchJWTSVID(ctx, loopLog, entry, req.Audience)
if err != nil {
log.WithField(telemetry.SPIFFEID, entry.SpiffeId).WithError(err).Error("Invalid requested SPIFFE ID")
return nil, status.Errorf(codes.InvalidArgument, "invalid requested SPIFFE ID: %v", err)
return nil, err
}

spiffeIDs = append(spiffeIDs, spiffeID)
resp.Svids = append(resp.Svids, svid)
}

if len(spiffeIDs) == 0 {
if len(resp.Svids) == 0 {
log.WithField(telemetry.Registered, false).Error("No identity issued")
return nil, status.Error(codes.PermissionDenied, "no identity issued")
}

resp = new(workload.JWTSVIDResponse)
for _, id := range spiffeIDs {
loopLog := log.WithField(telemetry.SPIFFEID, id.String())

var svid *client.JWTSVID
svid, err = h.c.Manager.FetchJWTSVID(ctx, id, req.Audience)
if err != nil {
loopLog.WithError(err).Error("Could not fetch JWT-SVID")
return nil, status.Errorf(codes.Unavailable, "could not fetch JWT-SVID: %v", err)
}
resp.Svids = append(resp.Svids, &workload.JWTSVID{
SpiffeId: id.String(),
Svid: svid.Token,
})

ttl := time.Until(svid.ExpiresAt)
loopLog.WithField(telemetry.TTL, ttl.Seconds()).Debug("Fetched JWT SVID")
}

return resp, nil
}

Expand Down Expand Up @@ -214,7 +197,8 @@ func (h *Handler) ValidateJWTSVID(ctx context.Context, req *workload.ValidateJWT
}, nil
}

// FetchX509SVID processes request for an x509 SVID
// FetchX509SVID processes request for a x509 SVID. In case of multiple fetched SVIDs with same hint, the SVID that has the oldest
// associated entry will be returned.
func (h *Handler) FetchX509SVID(_ *workload.X509SVIDRequest, stream workload.SpiffeWorkloadAPI_FetchX509SVIDServer) error {
ctx := stream.Context()
log := rpccontext.Logger(ctx)
Expand All @@ -238,6 +222,7 @@ func (h *Handler) FetchX509SVID(_ *workload.X509SVIDRequest, stream workload.Spi
for {
select {
case update := <-subscriber.Updates():
update.Identities = filterIdentities(update.Identities, log)
if err := sendX509SVIDResponse(update, stream, log, quietLogging); err != nil {
return err
}
Expand Down Expand Up @@ -282,6 +267,29 @@ func (h *Handler) FetchX509Bundles(_ *workload.X509BundlesRequest, stream worklo
}
}

func (h *Handler) fetchJWTSVID(ctx context.Context, log logrus.FieldLogger, entry *common.RegistrationEntry, audience []string) (*workload.JWTSVID, error) {
spiffeID, err := spiffeid.FromString(entry.SpiffeId)
if err != nil {
log.WithError(err).Error("Invalid requested SPIFFE ID")
return nil, status.Errorf(codes.InvalidArgument, "invalid requested SPIFFE ID: %v", err)
}

svid, err := h.c.Manager.FetchJWTSVID(ctx, spiffeID, audience)
if err != nil {
log.WithError(err).Error("Could not fetch JWT-SVID")
return nil, status.Errorf(codes.Unavailable, "could not fetch JWT-SVID: %v", err)
}

ttl := time.Until(svid.ExpiresAt)
log.WithField(telemetry.TTL, ttl.Seconds()).Debug("Fetched JWT SVID")

return &workload.JWTSVID{
SpiffeId: spiffeID.String(),
Svid: svid.Token,
Hint: entry.Hint,
}, nil
}

func sendX509BundlesResponse(update *cache.WorkloadUpdate, stream workload.SpiffeWorkloadAPI_FetchX509BundlesServer, log logrus.FieldLogger, allowUnauthenticatedVerifiers bool, previousResponse *workload.X509BundlesResponse, quietLogging bool) (*workload.X509BundlesResponse, error) {
if !allowUnauthenticatedVerifiers && !update.HasIdentity() {
if !quietLogging {
Expand Down Expand Up @@ -391,6 +399,7 @@ func composeX509SVIDResponse(update *cache.WorkloadUpdate) (*workload.X509SVIDRe
X509Svid: x509util.DERFromCertificates(identity.SVID),
X509SvidKey: keyData,
Bundle: bundle,
Hint: identity.Entry.Hint,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a warranty that entry is not nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe so, as you can see on line 391, we are already taking the SPIFFE ID from the entry.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

resp.Svids = append(resp.Svids, svid)
Expand Down Expand Up @@ -513,3 +522,80 @@ func isClaimAllowed(claim string, allowedClaims map[string]struct{}) bool {
return ok
}
}

func filterIdentities(identities []cache.Identity, log logrus.FieldLogger) []cache.Identity {
var filteredIdentities []cache.Identity
var entries []*common.RegistrationEntry
for _, identity := range identities {
entries = append(entries, identity.Entry)
}

entriesToRemove := getEntriesToRemove(entries, log)

for _, identity := range identities {
if _, ok := entriesToRemove[identity.Entry.EntryId]; !ok {
filteredIdentities = append(filteredIdentities, identity)
}
}

return filteredIdentities
}

func filterRegistrations(entries []*common.RegistrationEntry, log logrus.FieldLogger) []*common.RegistrationEntry {
var filteredEntries []*common.RegistrationEntry
entriesToRemove := getEntriesToRemove(entries, log)

for _, entry := range entries {
if _, ok := entriesToRemove[entry.EntryId]; !ok {
filteredEntries = append(filteredEntries, entry)
}
}

return filteredEntries
}

func getEntriesToRemove(entries []*common.RegistrationEntry, log logrus.FieldLogger) map[string]bool {
entriesToRemove := make(map[string]bool)
rturner3 marked this conversation as resolved.
Show resolved Hide resolved
hintsMap := make(map[string]*common.RegistrationEntry)

for _, entry := range entries {
if entry.Hint == "" {
continue
}
if entryWithNonUniqueHint, ok := hintsMap[entry.Hint]; ok {
entryToMaintain, entryToRemove := hintTieBreaking(entry, entryWithNonUniqueHint)

hintsMap[entry.Hint] = entryToMaintain
entriesToRemove[entryToRemove.EntryId] = true

log.WithFields(logrus.Fields{
telemetry.Hint: entryToRemove.Hint,
telemetry.RegistrationID: entryToRemove.EntryId,
}).Warn("Ignoring entry with duplicate hint")
} else {
hintsMap[entry.Hint] = entry
}
}

return entriesToRemove
}

func hintTieBreaking(entryA *common.RegistrationEntry, entryB *common.RegistrationEntry) (maintain *common.RegistrationEntry, remove *common.RegistrationEntry) {
switch {
case entryA.CreatedAt < entryB.CreatedAt:
maintain = entryA
remove = entryB
case entryA.CreatedAt > entryB.CreatedAt:
maintain = entryB
remove = entryA
default:
if entryA.EntryId < entryB.EntryId {
maintain = entryA
remove = entryB
} else {
maintain = entryB
remove = entryA
}
}
return
}
Loading