diff --git a/cmd/spire-server/cli/entry/create.go b/cmd/spire-server/cli/entry/create.go index 349fa344b2..ffaea76b00 100644 --- a/cmd/spire-server/cli/entry/create.go +++ b/cmd/spire-server/cli/entry/create.go @@ -74,6 +74,10 @@ type createCommand struct { // storeSVID determines if the issued SVID must be stored through an SVIDStore plugin storeSVID bool + // disableX509SVIDPrefetch tells the agent not to prefetch and cache X509 SVID for + // the given entry + disableX509SVIDPrefetch bool + printer cliprinter.Printer env *commoncli.Env @@ -103,6 +107,7 @@ func (c *createCommand) AppendFlags(f *flag.FlagSet) { f.Int64Var(&c.entryExpiry, "entryExpiry", 0, "An expiry, from epoch in seconds, for the resulting registration entry to be pruned") f.Var(&c.dnsNames, "dns", "A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once") f.StringVar(&c.hint, "hint", "", "The entry hint, used to disambiguate entries with the same SPIFFE ID") + f.BoolVar(&c.disableX509SVIDPrefetch, "disableX509SVIDPrefetch", false, "A boolean value that, when set, disables prefetching X509 SVID for this entry") cliprinter.AppendFlagWithCustomPretty(&c.printer, f, c.env, prettyPrintCreate) } @@ -209,8 +214,14 @@ func (c *createCommand) parseConfig() ([]*types.Entry, error) { selectors = append(selectors, cs) } - e.Selectors = selectors + + if c.disableX509SVIDPrefetch { + e.AdditionalAttributes = &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: c.disableX509SVIDPrefetch, + } + } + e.FederatesWith = c.federatesWith e.Admin = c.admin return []*types.Entry{e}, nil diff --git a/cmd/spire-server/cli/entry/create_test.go b/cmd/spire-server/cli/entry/create_test.go index eef1f73f00..291edeed6c 100644 --- a/cmd/spire-server/cli/entry/create_test.go +++ b/cmd/spire-server/cli/entry/create_test.go @@ -44,7 +44,10 @@ func TestCreate(t *testing.T) { DnsNames: []string{"unu1000", "ung1000"}, Downstream: true, StoreSvid: true, - CreatedAt: 1547583197, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, + CreatedAt: 1547583197, }, Status: &types.Status{ Code: int32(codes.OK), @@ -93,7 +96,10 @@ func TestCreate(t *testing.T) { X509SvidTtl: 200, JwtSvidTtl: 30, Admin: true, - CreatedAt: 1547583197, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: false, + }, + CreatedAt: 1547583197, }, Status: &types.Status{ Code: int32(codes.OK), @@ -109,7 +115,10 @@ func TestCreate(t *testing.T) { X509SvidTtl: 200, JwtSvidTtl: 30, Hint: "internal", - CreatedAt: 1547583197, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: false, + }, + CreatedAt: 1547583197, }, Status: &types.Status{ Code: int32(codes.OK), @@ -128,7 +137,29 @@ func TestCreate(t *testing.T) { StoreSvid: true, X509SvidTtl: 200, JwtSvidTtl: 30, - CreatedAt: 1547583197, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: false, + }, + CreatedAt: 1547583197, + }, + Status: &types.Status{ + Code: int32(codes.OK), + Message: "OK", + }, + }, + { + Entry: &types.Entry{ + Id: "entry-id-4", + SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/additionalattr"}, + ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenBlog"}, + Selectors: []*types.Selector{{Type: "unix", Value: "uid:1111"}}, + X509SvidTtl: 200, + JwtSvidTtl: 30, + Admin: true, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, + CreatedAt: 1547583197, }, Status: &types.Status{ Code: int32(codes.OK), @@ -235,6 +266,7 @@ func TestCreate(t *testing.T) { "-downstream", "-storeSVID", "-hint", "internal", + "-disableX509SVIDPrefetch", }, expReq: &entryv1.BatchCreateEntryRequest{ Entries: []*types.Entry{ @@ -254,27 +286,30 @@ func TestCreate(t *testing.T) { Downstream: true, StoreSvid: true, Hint: "internal", + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, }, }, }, fakeResp: fakeRespOKFromCmd, - expOutPretty: fmt.Sprintf(`Entry ID : entry-id -SPIFFE ID : spiffe://example.org/workload -Parent ID : spiffe://example.org/parent -Revision : 0 -Downstream : true -X509-SVID TTL : 60 -JWT-SVID TTL : 30 -Expiration time : %s -Selector : zebra:zebra:2000 -Selector : alpha:alpha:2000 -FederatesWith : spiffe://domaina.test -FederatesWith : spiffe://domainb.test -DNS name : unu1000 -DNS name : ung1000 -Admin : true -StoreSvid : true - + expOutPretty: fmt.Sprintf(`Entry ID : entry-id +SPIFFE ID : spiffe://example.org/workload +Parent ID : spiffe://example.org/parent +Revision : 0 +Downstream : true +X509-SVID TTL : 60 +JWT-SVID TTL : 30 +Expiration time : %s +Selector : zebra:zebra:2000 +Selector : alpha:alpha:2000 +FederatesWith : spiffe://domaina.test +FederatesWith : spiffe://domainb.test +DNS name : unu1000 +DNS name : ung1000 +Admin : true +StoreSvid : true +DisableX509SvidPrefetch : true `, time.Unix(1552410266, 0).UTC()), expOutJSON: `{ "results": [ @@ -319,7 +354,10 @@ StoreSvid : true ], "revision_number": "0", "store_svid": true, - "jwt_svid_ttl": 30 + "jwt_svid_ttl": 30, + "additional_attributes": { + "disable_x509_svid_prefetch": true + } } } ] @@ -365,22 +403,22 @@ StoreSvid : true }, }, fakeResp: fakeRespOKFromCmdWithoutJwtTtl, - expOutPretty: fmt.Sprintf(`Entry ID : entry-id -SPIFFE ID : spiffe://example.org/workload -Parent ID : spiffe://example.org/parent -Revision : 0 -Downstream : true -X509-SVID TTL : 60 -JWT-SVID TTL : default -Expiration time : %s -Selector : zebra:zebra:2000 -Selector : alpha:alpha:2000 -FederatesWith : spiffe://domaina.test -FederatesWith : spiffe://domainb.test -DNS name : unu1000 -DNS name : ung1000 -Admin : true -StoreSvid : true + expOutPretty: fmt.Sprintf(`Entry ID : entry-id +SPIFFE ID : spiffe://example.org/workload +Parent ID : spiffe://example.org/parent +Revision : 0 +Downstream : true +X509-SVID TTL : 60 +JWT-SVID TTL : default +Expiration time : %s +Selector : zebra:zebra:2000 +Selector : alpha:alpha:2000 +FederatesWith : spiffe://domaina.test +FederatesWith : spiffe://domainb.test +DNS name : unu1000 +DNS name : ung1000 +Admin : true +StoreSvid : true `, time.Unix(1552410266, 0).UTC()), expOutJSON: `{ @@ -466,36 +504,57 @@ StoreSvid : true JwtSvidTtl: 30, StoreSvid: true, }, + { + SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/additionalattr"}, + ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenBlog"}, + Selectors: []*types.Selector{{Type: "unix", Value: "uid:1111"}}, + X509SvidTtl: 200, + JwtSvidTtl: 30, + Admin: true, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, + }, }, }, fakeResp: fakeRespOKFromFile, - expOutPretty: `Entry ID : entry-id-1 -SPIFFE ID : spiffe://example.org/Blog -Parent ID : spiffe://example.org/spire/agent/join_token/TokenBlog -Revision : 0 -X509-SVID TTL : 200 -JWT-SVID TTL : 30 -Selector : unix:uid:1111 -Admin : true + expOutPretty: `Entry ID : entry-id-1 +SPIFFE ID : spiffe://example.org/Blog +Parent ID : spiffe://example.org/spire/agent/join_token/TokenBlog +Revision : 0 +X509-SVID TTL : 200 +JWT-SVID TTL : 30 +Selector : unix:uid:1111 +Admin : true + +Entry ID : entry-id-2 +SPIFFE ID : spiffe://example.org/Database +Parent ID : spiffe://example.org/spire/agent/join_token/TokenDatabase +Revision : 0 +X509-SVID TTL : 200 +JWT-SVID TTL : 30 +Selector : unix:uid:1111 +Hint : internal -Entry ID : entry-id-2 -SPIFFE ID : spiffe://example.org/Database -Parent ID : spiffe://example.org/spire/agent/join_token/TokenDatabase -Revision : 0 -X509-SVID TTL : 200 -JWT-SVID TTL : 30 -Selector : unix:uid:1111 -Hint : internal +Entry ID : entry-id-3 +SPIFFE ID : spiffe://example.org/storesvid +Parent ID : spiffe://example.org/spire/agent/join_token/TokenDatabase +Revision : 0 +X509-SVID TTL : 200 +JWT-SVID TTL : 30 +Selector : type:key1:value +Selector : type:key2:value +StoreSvid : true -Entry ID : entry-id-3 -SPIFFE ID : spiffe://example.org/storesvid -Parent ID : spiffe://example.org/spire/agent/join_token/TokenDatabase -Revision : 0 -X509-SVID TTL : 200 -JWT-SVID TTL : 30 -Selector : type:key1:value -Selector : type:key2:value -StoreSvid : true +Entry ID : entry-id-4 +SPIFFE ID : spiffe://example.org/additionalattr +Parent ID : spiffe://example.org/spire/agent/join_token/TokenBlog +Revision : 0 +X509-SVID TTL : 200 +JWT-SVID TTL : 30 +Selector : unix:uid:1111 +Admin : true +DisableX509SvidPrefetch : true `, expOutJSON: `{ @@ -531,7 +590,10 @@ StoreSvid : true "dns_names": [], "revision_number": "0", "store_svid": false, - "jwt_svid_ttl": 30 + "jwt_svid_ttl": 30, + "additional_attributes": { + "disable_x509_svid_prefetch": false + } } }, { @@ -565,7 +627,10 @@ StoreSvid : true "dns_names": [], "revision_number": "0", "store_svid": false, - "jwt_svid_ttl": 30 + "jwt_svid_ttl": 30, + "additional_attributes": { + "disable_x509_svid_prefetch": false + } } }, { @@ -603,7 +668,47 @@ StoreSvid : true "dns_names": [], "revision_number": "0", "store_svid": true, - "jwt_svid_ttl": 30 + "jwt_svid_ttl": 30, + "additional_attributes": { + "disable_x509_svid_prefetch": false + } + } + }, + { + "status": { + "code": 0, + "message": "OK" + }, + "entry": { + "id": "entry-id-4", + "spiffe_id": { + "trust_domain": "example.org", + "path": "/additionalattr" + }, + "parent_id": { + "trust_domain": "example.org", + "path": "/spire/agent/join_token/TokenBlog" + }, + "selectors": [ + { + "type": "unix", + "value": "uid:1111" + } + ], + "x509_svid_ttl": 200, + "federates_with": [], + "hint": "", + "admin": true, + "created_at": "1547583197", + "downstream": false, + "expires_at": "0", + "dns_names": [], + "revision_number": "0", + "store_svid": false, + "jwt_svid_ttl": 30, + "additional_attributes": { + "disable_x509_svid_prefetch": true + } } } ] @@ -621,13 +726,13 @@ StoreSvid : true }}, fakeResp: fakeRespErr, expErrPretty: `Failed to create the following entry (code: AlreadyExists, msg: "similar entry already exists"): -Entry ID : (none) -SPIFFE ID : spiffe://example.org/already-exist -Parent ID : spiffe://example.org/spire/server -Revision : 0 -X509-SVID TTL : default -JWT-SVID TTL : default -Selector : unix:uid:1 +Entry ID : (none) +SPIFFE ID : spiffe://example.org/already-exist +Parent ID : spiffe://example.org/spire/server +Revision : 0 +X509-SVID TTL : default +JWT-SVID TTL : default +Selector : unix:uid:1 Error: failed to create one or more entries `, diff --git a/cmd/spire-server/cli/entry/show_test.go b/cmd/spire-server/cli/entry/show_test.go index 84cd90854d..4a1851c972 100644 --- a/cmd/spire-server/cli/entry/show_test.go +++ b/cmd/spire-server/cli/entry/show_test.go @@ -455,49 +455,49 @@ func getEntries(count int) []*types.Entry { func getPrettyPrintedEntry(idx int) string { switch idx { case 0: - return `Entry ID : 00000000-0000-0000-0000-000000000000 -SPIFFE ID : spiffe://example.org/son -Parent ID : spiffe://example.org/father -Revision : 0 -X509-SVID TTL : default -JWT-SVID TTL : default -Selector : foo:bar -Hint : internal + return `Entry ID : 00000000-0000-0000-0000-000000000000 +SPIFFE ID : spiffe://example.org/son +Parent ID : spiffe://example.org/father +Revision : 0 +X509-SVID TTL : default +JWT-SVID TTL : default +Selector : foo:bar +Hint : internal ` case 1: - return `Entry ID : 00000000-0000-0000-0000-000000000001 -SPIFFE ID : spiffe://example.org/daughter -Parent ID : spiffe://example.org/father -Revision : 0 -X509-SVID TTL : default -JWT-SVID TTL : default -Selector : bar:baz -Selector : foo:bar -Hint : external + return `Entry ID : 00000000-0000-0000-0000-000000000001 +SPIFFE ID : spiffe://example.org/daughter +Parent ID : spiffe://example.org/father +Revision : 0 +X509-SVID TTL : default +JWT-SVID TTL : default +Selector : bar:baz +Selector : foo:bar +Hint : external ` case 2: - return `Entry ID : 00000000-0000-0000-0000-000000000002 -SPIFFE ID : spiffe://example.org/daughter -Parent ID : spiffe://example.org/mother -Revision : 0 -X509-SVID TTL : default -JWT-SVID TTL : default -Selector : bar:baz -Selector : baz:bat -FederatesWith : spiffe://domain.test + return `Entry ID : 00000000-0000-0000-0000-000000000002 +SPIFFE ID : spiffe://example.org/daughter +Parent ID : spiffe://example.org/mother +Revision : 0 +X509-SVID TTL : default +JWT-SVID TTL : default +Selector : bar:baz +Selector : baz:bat +FederatesWith : spiffe://domain.test ` case 3: - return fmt.Sprintf(`Entry ID : 00000000-0000-0000-0000-000000000003 -SPIFFE ID : spiffe://example.org/son -Parent ID : spiffe://example.org/mother -Revision : 0 -X509-SVID TTL : default -JWT-SVID TTL : default -Expiration time : %s -Selector : baz:bat + return fmt.Sprintf(`Entry ID : 00000000-0000-0000-0000-000000000003 +SPIFFE ID : spiffe://example.org/son +Parent ID : spiffe://example.org/mother +Revision : 0 +X509-SVID TTL : default +JWT-SVID TTL : default +Expiration time : %s +Selector : baz:bat `, time.Unix(1552410266, 0).UTC()) default: diff --git a/cmd/spire-server/cli/entry/update.go b/cmd/spire-server/cli/entry/update.go index 369275d203..3aed41d4d9 100644 --- a/cmd/spire-server/cli/entry/update.go +++ b/cmd/spire-server/cli/entry/update.go @@ -67,6 +67,13 @@ type updateCommand struct { // storeSVID determines if the issued SVID must be stored through an SVIDStore plugin storeSVID bool + // additionalAttributesSet indicates whether any of the additional attributes if the requestration entry were set + additionalAttributesSet bool + + // disableX509SVIDPrefetch tells the agent not to prefetch and cache X509 SVID for + // the given entry + disableX509SVIDPrefetch bool + // Entry hint, used to disambiguate entries with the same SPIFFE ID hint string @@ -98,6 +105,12 @@ func (c *updateCommand) AppendFlags(f *flag.FlagSet) { f.Int64Var(&c.entryExpiry, "entryExpiry", 0, "An expiry, from epoch in seconds, for the resulting registration entry to be pruned") f.Var(&c.dnsNames, "dns", "A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once") f.StringVar(&c.hint, "hint", "", "The entry hint, used to disambiguate entries with the same SPIFFE ID") + f.BoolFunc("disableX509SVIDPrefetch", "A boolean value that, when set, disables prefetching X509 SVID for this entry", + func(_ string) error { + c.additionalAttributesSet = true + c.disableX509SVIDPrefetch = true + return nil + }) cliprinter.AppendFlagWithCustomPretty(&c.printer, f, c.env, prettyPrintUpdate) } @@ -204,6 +217,15 @@ func (c *updateCommand) parseConfig() ([]*types.Entry, error) { } e.Selectors = selectors + + if c.additionalAttributesSet { + e.AdditionalAttributes = &types.Entry_AdditionalAttributes{} + } + + if c.disableX509SVIDPrefetch { + e.AdditionalAttributes.DisableX509SvidPrefetch = true + } + e.FederatesWith = c.federatesWith e.Admin = c.admin e.StoreSvid = c.storeSVID diff --git a/cmd/spire-server/cli/entry/update_test.go b/cmd/spire-server/cli/entry/update_test.go index 0befd96851..f8f6a926d6 100644 --- a/cmd/spire-server/cli/entry/update_test.go +++ b/cmd/spire-server/cli/entry/update_test.go @@ -190,7 +190,44 @@ func TestUpdate(t *testing.T) { "revision_number": "0", "store_svid": true, "jwt_svid_ttl": 300 - }` + } + }` + entry4JSON := `{ + "id": "entry-id-4", + "spiffe_id": { + "trust_domain": "example.org", + "path": "/DisableX509SvidPrefetch" + }, + "parent_id": { + "trust_domain": "example.org", + "path": "/spire/agent/join_token/TokenDatabase" + }, + "selectors": [ + { + "type": "type", + "value": "key1:value" + }, + { + "type": "type", + "value": "key2:value" + } + ], + "x509_svid_ttl": 200, + "federates_with": [], + "hint": "", + "admin": false, + "created_at": "1547583197", + "downstream": false, + "expires_at": "0", + "dns_names": [], + "revision_number": "0", + "jwt_svid_ttl": 300, + "store_svid": false, + "additional_attributes": { + "disable_x509_svid_prefetch": true + } + } + }` nonExistentEntryJSON := `{ "id": "non-existent-id", "spiffe_id": { @@ -321,12 +358,29 @@ func TestUpdate(t *testing.T) { JwtSvidTtl: 300, } + entry5 := &types.Entry{ + Id: "entry-id-4", + SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/DisableX509SvidPrefetch"}, + ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenDatabase"}, + Selectors: []*types.Selector{ + {Type: "type", Value: "key1:value"}, + {Type: "type", Value: "key2:value"}, + }, + X509SvidTtl: 200, + JwtSvidTtl: 300, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, + } + entry2Resp := proto.Clone(entry2).(*types.Entry) entry2Resp.CreatedAt = 1547583197 entry3Resp := proto.Clone(entry3).(*types.Entry) entry3Resp.CreatedAt = 1547583197 entry4Resp := proto.Clone(entry4).(*types.Entry) entry4Resp.CreatedAt = 1547583197 + entry5Resp := proto.Clone(entry5).(*types.Entry) + entry5Resp.CreatedAt = 1547583197 fakeRespOKFromFile := &entryv1.BatchUpdateEntryResponse{ Results: []*entryv1.BatchUpdateEntryResponse_Result{ @@ -342,6 +396,10 @@ func TestUpdate(t *testing.T) { Entry: entry4Resp, Status: &types.Status{Code: int32(codes.OK), Message: "OK"}, }, + { + Entry: entry5Resp, + Status: &types.Status{Code: int32(codes.OK), Message: "OK"}, + }, }, } @@ -403,10 +461,11 @@ func TestUpdate(t *testing.T) { args: []string{"-entryID", "entry-id", "-spiffeID", "spiffe://example.org/workload", "-parentID", "spiffe://example.org/parent", "-selector", "unix:uid:1"}, expReq: &entryv1.BatchUpdateEntryRequest{Entries: []*types.Entry{ { - Id: "entry-id", - SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/workload"}, - ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/parent"}, - Selectors: []*types.Selector{{Type: "unix", Value: "uid:1"}}, + Id: "entry-id", + SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/workload"}, + ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/parent"}, + Selectors: []*types.Selector{{Type: "unix", Value: "uid:1"}}, + AdditionalAttributes: &types.Entry_AdditionalAttributes{}, }, }}, serverErr: errors.New("server-error"), @@ -436,22 +495,22 @@ func TestUpdate(t *testing.T) { Entries: []*types.Entry{entry1}, }, fakeResp: fakeRespOKFromCmd, - expOutPretty: fmt.Sprintf(`Entry ID : entry-id -SPIFFE ID : spiffe://example.org/workload -Parent ID : spiffe://example.org/parent -Revision : 0 -Downstream : true -X509-SVID TTL : 60 -JWT-SVID TTL : 30 -Expiration time : %s -Selector : zebra:zebra:2000 -Selector : alpha:alpha:2000 -FederatesWith : spiffe://domaina.test -FederatesWith : spiffe://domainb.test -DNS name : unu1000 -DNS name : ung1000 -Admin : true -Hint : external + expOutPretty: fmt.Sprintf(`Entry ID : entry-id +SPIFFE ID : spiffe://example.org/workload +Parent ID : spiffe://example.org/parent +Revision : 0 +Downstream : true +X509-SVID TTL : 60 +JWT-SVID TTL : 30 +Expiration time : %s +Selector : zebra:zebra:2000 +Selector : alpha:alpha:2000 +FederatesWith : spiffe://domaina.test +FederatesWith : spiffe://domainb.test +DNS name : unu1000 +DNS name : ung1000 +Admin : true +Hint : external `, time.Unix(1552410266, 0).UTC()), expOutJSON: fmt.Sprintf(`{ @@ -497,20 +556,20 @@ Hint : external }, }, }, - expOutPretty: fmt.Sprintf(`Entry ID : entry-id -SPIFFE ID : spiffe://example.org/workload -Parent ID : spiffe://example.org/parent -Revision : 0 -X509-SVID TTL : 60 -JWT-SVID TTL : 30 -Expiration time : %s -Selector : type:key1:value -Selector : type:key2:value -FederatesWith : spiffe://domaina.test -FederatesWith : spiffe://domainb.test -DNS name : unu1000 -DNS name : ung1000 -StoreSvid : true + expOutPretty: fmt.Sprintf(`Entry ID : entry-id +SPIFFE ID : spiffe://example.org/workload +Parent ID : spiffe://example.org/parent +Revision : 0 +X509-SVID TTL : 60 +JWT-SVID TTL : 30 +Expiration time : %s +Selector : type:key1:value +Selector : type:key2:value +FederatesWith : spiffe://domaina.test +FederatesWith : spiffe://domainb.test +DNS name : unu1000 +DNS name : ung1000 +StoreSvid : true `, time.Unix(1552410266, 0).UTC()), expOutJSON: fmt.Sprintf(`{ @@ -531,36 +590,46 @@ StoreSvid : true "-data", "../../../../test/fixture/registration/good-for-update.json", }, expReq: &entryv1.BatchUpdateEntryRequest{ - Entries: []*types.Entry{entry2, entry3, entry4}, + Entries: []*types.Entry{entry2, entry3, entry4, entry5}, }, fakeResp: fakeRespOKFromFile, - expOutPretty: `Entry ID : entry-id-1 -SPIFFE ID : spiffe://example.org/Blog -Parent ID : spiffe://example.org/spire/agent/join_token/TokenBlog -Revision : 0 -X509-SVID TTL : 200 -JWT-SVID TTL : 300 -Selector : unix:uid:1111 -Admin : true -Hint : external + expOutPretty: `Entry ID : entry-id-1 +SPIFFE ID : spiffe://example.org/Blog +Parent ID : spiffe://example.org/spire/agent/join_token/TokenBlog +Revision : 0 +X509-SVID TTL : 200 +JWT-SVID TTL : 300 +Selector : unix:uid:1111 +Admin : true +Hint : external + +Entry ID : entry-id-2 +SPIFFE ID : spiffe://example.org/Database +Parent ID : spiffe://example.org/spire/agent/join_token/TokenDatabase +Revision : 0 +X509-SVID TTL : 200 +JWT-SVID TTL : 300 +Selector : unix:uid:1111 -Entry ID : entry-id-2 -SPIFFE ID : spiffe://example.org/Database -Parent ID : spiffe://example.org/spire/agent/join_token/TokenDatabase -Revision : 0 -X509-SVID TTL : 200 -JWT-SVID TTL : 300 -Selector : unix:uid:1111 +Entry ID : entry-id-3 +SPIFFE ID : spiffe://example.org/Storesvid +Parent ID : spiffe://example.org/spire/agent/join_token/TokenDatabase +Revision : 0 +X509-SVID TTL : 200 +JWT-SVID TTL : 300 +Selector : type:key1:value +Selector : type:key2:value +StoreSvid : true -Entry ID : entry-id-3 -SPIFFE ID : spiffe://example.org/Storesvid -Parent ID : spiffe://example.org/spire/agent/join_token/TokenDatabase -Revision : 0 -X509-SVID TTL : 200 -JWT-SVID TTL : 300 -Selector : type:key1:value -Selector : type:key2:value -StoreSvid : true +Entry ID : entry-id-4 +SPIFFE ID : spiffe://example.org/DisableX509SvidPrefetch +Parent ID : spiffe://example.org/spire/agent/join_token/TokenDatabase +Revision : 0 +X509-SVID TTL : 200 +JWT-SVID TTL : 300 +Selector : type:key1:value +Selector : type:key2:value +DisableX509SvidPrefetch : true `, expOutJSON: fmt.Sprintf(` @@ -578,15 +647,20 @@ StoreSvid : true "message": "OK" }, "entry": %s, + { + "status": { + "code": 0, + "message": "OK" + }, + "entry": %s, { "status": { "code": 0, "message": "OK" }, "entry": %s - } ] -}`, entry1JSON, entry2JSON, entry3JSON), +}`, entry1JSON, entry2JSON, entry3JSON, entry4JSON), }, { name: "Entry not found", @@ -601,13 +675,13 @@ StoreSvid : true }}, fakeResp: fakeRespErr, expErrPretty: `Failed to update the following entry (code: NotFound, msg: "failed to update entry: datastore-sql: record not found"): -Entry ID : non-existent-id -SPIFFE ID : spiffe://example.org/workload -Parent ID : spiffe://example.org/parent -Revision : 0 -X509-SVID TTL : default -JWT-SVID TTL : default -Selector : unix:uid:1 +Entry ID : non-existent-id +SPIFFE ID : spiffe://example.org/workload +Parent ID : spiffe://example.org/parent +Revision : 0 +X509-SVID TTL : default +JWT-SVID TTL : default +Selector : unix:uid:1 Error: failed to update one or more entries `, diff --git a/cmd/spire-server/cli/entry/util.go b/cmd/spire-server/cli/entry/util.go index d51208c646..f224329ea3 100644 --- a/cmd/spire-server/cli/entry/util.go +++ b/cmd/spire-server/cli/entry/util.go @@ -14,53 +14,59 @@ import ( ) func printEntry(e *types.Entry, printf func(string, ...any) error) { - _ = printf("Entry ID : %s\n", printableEntryID(e.Id)) - _ = printf("SPIFFE ID : %s\n", protoToIDString(e.SpiffeId)) - _ = printf("Parent ID : %s\n", protoToIDString(e.ParentId)) - _ = printf("Revision : %d\n", e.RevisionNumber) + _ = printf("Entry ID : %s\n", printableEntryID(e.Id)) + _ = printf("SPIFFE ID : %s\n", protoToIDString(e.SpiffeId)) + _ = printf("Parent ID : %s\n", protoToIDString(e.ParentId)) + _ = printf("Revision : %d\n", e.RevisionNumber) if e.Downstream { - _ = printf("Downstream : %t\n", e.Downstream) + _ = printf("Downstream : %t\n", e.Downstream) } if e.X509SvidTtl == 0 { - _ = printf("X509-SVID TTL : default\n") + _ = printf("X509-SVID TTL : default\n") } else { - _ = printf("X509-SVID TTL : %d\n", e.X509SvidTtl) + _ = printf("X509-SVID TTL : %d\n", e.X509SvidTtl) } if e.JwtSvidTtl == 0 { - _ = printf("JWT-SVID TTL : default\n") + _ = printf("JWT-SVID TTL : default\n") } else { - _ = printf("JWT-SVID TTL : %d\n", e.JwtSvidTtl) + _ = printf("JWT-SVID TTL : %d\n", e.JwtSvidTtl) } if e.ExpiresAt != 0 { - _ = printf("Expiration time : %s\n", time.Unix(e.ExpiresAt, 0).UTC()) + _ = printf("Expiration time : %s\n", time.Unix(e.ExpiresAt, 0).UTC()) } for _, s := range e.Selectors { - _ = printf("Selector : %s:%s\n", s.Type, s.Value) + _ = printf("Selector : %s:%s\n", s.Type, s.Value) } for _, id := range e.FederatesWith { - _ = printf("FederatesWith : %s\n", id) + _ = printf("FederatesWith : %s\n", id) } for _, dnsName := range e.DnsNames { - _ = printf("DNS name : %s\n", dnsName) + _ = printf("DNS name : %s\n", dnsName) } // admin is rare, so only show admin if true to keep // from muddying the output. if e.Admin { - _ = printf("Admin : %t\n", e.Admin) + _ = printf("Admin : %t\n", e.Admin) } if e.StoreSvid { - _ = printf("StoreSvid : %t\n", e.StoreSvid) + _ = printf("StoreSvid : %t\n", e.StoreSvid) } if e.Hint != "" { - _ = printf("Hint : %s\n", e.Hint) + _ = printf("Hint : %s\n", e.Hint) + } + + if e.AdditionalAttributes != nil { + if e.AdditionalAttributes.DisableX509SvidPrefetch { + _ = printf("DisableX509SvidPrefetch : %t\n", e.AdditionalAttributes.DisableX509SvidPrefetch) + } } _ = printf("\n") diff --git a/cmd/spire-server/cli/entry/util_posix_test.go b/cmd/spire-server/cli/entry/util_posix_test.go index 2ac9598873..ce8db79653 100644 --- a/cmd/spire-server/cli/entry/util_posix_test.go +++ b/cmd/spire-server/cli/entry/util_posix_test.go @@ -8,6 +8,8 @@ const ( If set, the SPIFFE ID in this entry will be granted access to the SPIRE Server's management APIs -data string Path to a file containing registration JSON (optional). If set to '-', read the JSON from stdin. + -disableX509SVIDPrefetch + A boolean value that, when set, disables prefetching X509 SVID for this entry -dns value A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once -downstream @@ -68,6 +70,8 @@ const ( If set, the SPIFFE ID in this entry will be granted access to the SPIRE Server's management APIs -data string Path to a file containing registration JSON (optional). If set to '-', read the JSON from stdin. + -disableX509SVIDPrefetch + A boolean value that, when set, disables prefetching X509 SVID for this entry -dns value A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once -downstream diff --git a/cmd/spire-server/cli/entry/util_test.go b/cmd/spire-server/cli/entry/util_test.go index 782b95afa7..191889aa08 100644 --- a/cmd/spire-server/cli/entry/util_test.go +++ b/cmd/spire-server/cli/entry/util_test.go @@ -106,11 +106,28 @@ func TestParseEntryJSON(t *testing.T) { X509SvidTtl: 200, JwtSvidTtl: 30, } + entry4 := &types.Entry{ + Selectors: []*types.Selector{ + { + Type: "unix", + Value: "uid:1111", + }, + }, + SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/additionalattr"}, + ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenBlog"}, + X509SvidTtl: 200, + JwtSvidTtl: 30, + Admin: true, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, + } expectedEntries := []*types.Entry{ entry1, entry2, entry3, + entry4, } spiretest.RequireProtoListEqual(t, expectedEntries, entries) }) diff --git a/cmd/spire-server/cli/entry/util_windows_test.go b/cmd/spire-server/cli/entry/util_windows_test.go index 06fd7e4942..7c56388a04 100644 --- a/cmd/spire-server/cli/entry/util_windows_test.go +++ b/cmd/spire-server/cli/entry/util_windows_test.go @@ -8,6 +8,8 @@ const ( If set, the SPIFFE ID in this entry will be granted access to the SPIRE Server's management APIs -data string Path to a file containing registration JSON (optional). If set to '-', read the JSON from stdin. + -disableX509SVIDPrefetch + A boolean value that, when set, disables prefetching X509 SVID for this entry -dns value A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once -downstream @@ -68,6 +70,8 @@ const ( If set, the SPIFFE ID in this entry will be granted access to the SPIRE Server's management APIs -data string Path to a file containing registration JSON (optional). If set to '-', read the JSON from stdin. + -disableX509SVIDPrefetch + A boolean value that, when set, disables prefetching X509 SVID for this entry -dns value A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once -downstream diff --git a/doc/spire_server.md b/doc/spire_server.md index e35749a816..cb7f4f72a1 100644 --- a/doc/spire_server.md +++ b/doc/spire_server.md @@ -379,44 +379,46 @@ human-readable registration entry name in addition to the token-based ID. Creates registration entries. -| Command | Action | Default | -|:-----------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------| -| `-admin` | If set, the SPIFFE ID in this entry will be granted access to the Server APIs | | -| `-data` | Path to a file containing registration data in JSON format (optional, if specified, other flags related with entry information must be omitted). If set to '-', read the JSON from stdin. | | -| `-dns` | A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once | | -| `-downstream` | A boolean value that, when set, indicates that the entry describes a downstream SPIRE server | | -| `-entryExpiry` | An expiry, from epoch in seconds, for the resulting registration entry to be pruned from the datastore. Please note that this is a data management feature and not a security feature (optional). | | -| `-entryID` | A user-specified ID for the newly created registration entry (optional). If no entry ID is provided, one will be generated during creation | | -| `-federatesWith` | A list of trust domain SPIFFE IDs representing the trust domains this registration entry federates with. A bundle for that trust domain must already exist | | -| `-node` | If set, this entry will be applied to matching nodes rather than workloads | | -| `-parentID` | The SPIFFE ID of this record's parent. | | -| `-selector` | A colon-delimited type:value selector used for attestation. This parameter can be used more than once, to specify multiple selectors that must be satisfied. | | -| `-socketPath` | Path to the SPIRE Server API socket | /tmp/spire-server/private/api.sock | -| `-spiffeID` | The SPIFFE ID that this record represents and will be set to the SVID issued. | | -| `-x509SVIDTTL` | A TTL, in seconds, for any X509-SVID issued as a result of this record. | The TTL configured with `default_x509_svid_ttl` | -| `-jwtSVIDTTL` | A TTL, in seconds, for any JWT-SVID issued as a result of this record. | The TTL configured with `default_jwt_svid_ttl` | -| `-storeSVID` | A boolean value that, when set, indicates that the resulting issued SVID from this entry must be stored through an SVIDStore plugin | | +| Command | Action | Default | +|:---------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------| +| `-admin` | If set, the SPIFFE ID in this entry will be granted access to the Server APIs | | +| `-data` | Path to a file containing registration data in JSON format (optional, if specified, other flags related with entry information must be omitted). If set to '-', read the JSON from stdin. | | +| `-disableX509SVIDPrefetch` | A boolean value that, when set, disables prefetching X509 SVID for this entry | `false` | +| `-dns` | A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once | | +| `-downstream` | A boolean value that, when set, indicates that the entry describes a downstream SPIRE server | | +| `-entryExpiry` | An expiry, from epoch in seconds, for the resulting registration entry to be pruned from the datastore. Please note that this is a data management feature and not a security feature (optional). | | +| `-entryID` | A user-specified ID for the newly created registration entry (optional). If no entry ID is provided, one will be generated during creation | | +| `-federatesWith` | A list of trust domain SPIFFE IDs representing the trust domains this registration entry federates with. A bundle for that trust domain must already exist | | +| `-node` | If set, this entry will be applied to matching nodes rather than workloads | | +| `-parentID` | The SPIFFE ID of this record's parent. | | +| `-selector` | A colon-delimited type:value selector used for attestation. This parameter can be used more than once, to specify multiple selectors that must be satisfied. | | +| `-socketPath` | Path to the SPIRE Server API socket | /tmp/spire-server/private/api.sock | +| `-spiffeID` | The SPIFFE ID that this record represents and will be set to the SVID issued. | | +| `-x509SVIDTTL` | A TTL, in seconds, for any X509-SVID issued as a result of this record. | The TTL configured with `default_x509_svid_ttl` | +| `-jwtSVIDTTL` | A TTL, in seconds, for any JWT-SVID issued as a result of this record. | The TTL configured with `default_jwt_svid_ttl` | +| `-storeSVID` | A boolean value that, when set, indicates that the resulting issued SVID from this entry must be stored through an SVIDStore plugin | | ### `spire-server entry update` Updates registration entries. -| Command | Action | Default | -|:-----------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------| -| `-admin` | If true, the SPIFFE ID in this entry will be granted access to the Server APIs | | -| `-data` | Path to a file containing registration data in JSON format (optional, if specified, other flags related with entry information must be omitted). If set to '-', read the JSON from stdin. | | -| `-dns` | A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once | | -| `-downstream` | A boolean value that, when set, indicates that the entry describes a downstream SPIRE server | | -| `-entryExpiry` | An expiry, from epoch in seconds, for the resulting registration entry to be pruned | | -| `-entryID` | The Registration Entry ID of the record to update | | -| `-federatesWith` | A list of trust domain SPIFFE IDs representing the trust domains this registration entry federates with. A bundle for that trust domain must already exist | | -| `-parentID` | The SPIFFE ID of this record's parent. | | -| `-selector` | A colon-delimited type:value selector used for attestation. This parameter can be used more than once, to specify multiple selectors that must be satisfied. | | -| `-socketPath` | Path to the SPIRE Server API socket | /tmp/spire-server/private/api.sock | -| `-spiffeID` | The SPIFFE ID that this record represents and will be set to the SVID issued. | | -| `-x509SVIDTTL` | A TTL, in seconds, for any X509-SVID issued as a result of this record. | The TTL configured with `default_x509_svid_ttl` | -| `-jwtSVIDTTL` | A TTL, in seconds, for any JWT-SVID issued as a result of this record. | The TTL configured with `default_jwt_svid_ttl` | -| `storeSVID` | A boolean value that, when set, indicates that the resulting issued SVID from this entry must be stored through an SVIDStore plugin | | +| Command | Action | Default | +|:---------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------| +| `-admin` | If set, the SPIFFE ID in this entry will be granted access to the Server APIs | | +| `-data` | Path to a file containing registration data in JSON format (optional, if specified, other flags related with entry information must be omitted). If set to '-', read the JSON from stdin. | | +| `-disableX509SVIDPrefetch` | A boolean value that, when set, disables prefetching X509 SVID for this entry | `false` | +| `-dns` | A DNS name that will be included in SVIDs issued based on this entry, where appropriate. Can be used more than once | | +| `-downstream` | A boolean value that, when set, indicates that the entry describes a downstream SPIRE server | | +| `-entryExpiry` | An expiry, from epoch in seconds, for the resulting registration entry to be pruned from the datastore. Please note that this is a data management feature and not a security feature (optional). | | +| `-entryID` | A user-specified ID for the newly created registration entry (optional). If no entry ID is provided, one will be generated during creation | | +| `-federatesWith` | A list of trust domain SPIFFE IDs representing the trust domains this registration entry federates with. A bundle for that trust domain must already exist | | +| `-parentID` | The SPIFFE ID of this record's parent. | | +| `-selector` | A colon-delimited type:value selector used for attestation. This parameter can be used more than once, to specify multiple selectors that must be satisfied. | | +| `-socketPath` | Path to the SPIRE Server API socket | /tmp/spire-server/private/api.sock | +| `-spiffeID` | The SPIFFE ID that this record represents and will be set to the SVID issued. | | +| `-x509SVIDTTL` | A TTL, in seconds, for any X509-SVID issued as a result of this record. | The TTL configured with `default_x509_svid_ttl` | +| `-jwtSVIDTTL` | A TTL, in seconds, for any JWT-SVID issued as a result of this record. | The TTL configured with `default_jwt_svid_ttl` | +| `-storeSVID` | A boolean value that, when set, indicates that the resulting issued SVID from this entry must be stored through an SVIDStore plugin | | ### `spire-server entry count` diff --git a/go.mod b/go.mod index 6b3154931b..a626e5a110 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,7 @@ require ( github.com/sirupsen/logrus v1.9.4 github.com/smallstep/pkcs7 v0.2.1 github.com/spiffe/go-spiffe/v2 v2.6.0 - github.com/spiffe/spire-api-sdk v1.2.5-0.20260115194754-bcd1999bdd05 + github.com/spiffe/spire-api-sdk v1.2.5-0.20260320104153-99d433165a01 github.com/spiffe/spire-plugin-sdk v1.4.4-0.20251120194148-791bbd274dc7 github.com/stretchr/testify v1.11.1 github.com/uber-go/tally/v4 v4.1.17 diff --git a/go.sum b/go.sum index 31f6d0ee84..81e5344109 100644 --- a/go.sum +++ b/go.sum @@ -811,8 +811,8 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= -github.com/spiffe/spire-api-sdk v1.2.5-0.20260115194754-bcd1999bdd05 h1:wJPyPSXHtKEc9SuK83sr40OoRSaYUTf1+wncvWvogHE= -github.com/spiffe/spire-api-sdk v1.2.5-0.20260115194754-bcd1999bdd05/go.mod h1:9hXJcMzatM1KwAtBDO3s6HccDCic++/5c2yOc5Iln8Y= +github.com/spiffe/spire-api-sdk v1.2.5-0.20260320104153-99d433165a01 h1:FA0UhZ5fOca/d/ogmVHif1eDARpMo4FLWUnav8JzEts= +github.com/spiffe/spire-api-sdk v1.2.5-0.20260320104153-99d433165a01/go.mod h1:9hXJcMzatM1KwAtBDO3s6HccDCic++/5c2yOc5Iln8Y= github.com/spiffe/spire-plugin-sdk v1.4.4-0.20251120194148-791bbd274dc7 h1:OAvr7TNirmBpXnAp82cTosuB+JAus5cyFCRqXHE0WHs= github.com/spiffe/spire-plugin-sdk v1.4.4-0.20251120194148-791bbd274dc7/go.mod h1:QvrRDiBlXiJ7kNd176ZHsF5eklxxeTRgJSu2CXe0MKw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/pkg/agent/client/client.go b/pkg/agent/client/client.go index 5d3610c1cd..14af4fd7d2 100644 --- a/pkg/agent/client/client.go +++ b/pkg/agent/client/client.go @@ -39,15 +39,16 @@ var ( ErrUnableToGetStream = errors.New("unable to get a stream") entryOutputMask = &types.EntryMask{ - SpiffeId: true, - Selectors: true, - FederatesWith: true, - Admin: true, - Downstream: true, - RevisionNumber: true, - StoreSvid: true, - Hint: true, - CreatedAt: true, + SpiffeId: true, + Selectors: true, + FederatesWith: true, + Admin: true, + Downstream: true, + RevisionNumber: true, + StoreSvid: true, + Hint: true, + AdditionalAttributes: true, + CreatedAt: true, } // RPCTimeoutWithCacheHit can be more aggressive with timeouts in cases where a valid SVID diff --git a/pkg/agent/client/client_test.go b/pkg/agent/client/client_test.go index bf4e3d477e..b53da479b3 100644 --- a/pkg/agent/client/client_test.go +++ b/pkg/agent/client/client_test.go @@ -1195,15 +1195,16 @@ type testServer struct { func checkAuthorizedEntryOutputMask(outputMask *types.EntryMask) error { if diff := cmp.Diff(outputMask, &types.EntryMask{ - SpiffeId: true, - Selectors: true, - FederatesWith: true, - Admin: true, - Downstream: true, - RevisionNumber: true, - StoreSvid: true, - Hint: true, - CreatedAt: true, + SpiffeId: true, + Selectors: true, + FederatesWith: true, + Admin: true, + Downstream: true, + RevisionNumber: true, + StoreSvid: true, + Hint: true, + AdditionalAttributes: true, + CreatedAt: true, }, protocmp.Transform()); diff != "" { return status.Errorf(codes.InvalidArgument, "invalid output mask requested: %s", diff) } diff --git a/pkg/agent/client/util.go b/pkg/agent/client/util.go index 398a8807ea..ee5ad119cb 100644 --- a/pkg/agent/client/util.go +++ b/pkg/agent/client/util.go @@ -28,6 +28,15 @@ func spiffeIDFromProto(protoID *types.SPIFFEID) (string, error) { return id.String(), nil } +func additionalAttributesFromProto(in *types.Entry_AdditionalAttributes) *common.RegistrationEntry_AdditionalAttributes { + if in != nil { + return &common.RegistrationEntry_AdditionalAttributes{ + DisableX509SvidPrefetch: in.DisableX509SvidPrefetch, + } + } + return nil +} + func slicedEntryFromProto(e *types.Entry) (*common.RegistrationEntry, error) { if e == nil { return nil, errors.New("missing entry") @@ -72,15 +81,16 @@ func slicedEntryFromProto(e *types.Entry) (*common.RegistrationEntry, error) { } return &common.RegistrationEntry{ - EntryId: e.Id, - SpiffeId: spiffeID, - FederatesWith: federatesWith, - RevisionNumber: e.RevisionNumber, - Selectors: selectors, - StoreSvid: e.StoreSvid, - Admin: e.Admin, - Downstream: e.Downstream, - Hint: e.Hint, - CreatedAt: e.CreatedAt, + EntryId: e.Id, + SpiffeId: spiffeID, + FederatesWith: federatesWith, + RevisionNumber: e.RevisionNumber, + Selectors: selectors, + StoreSvid: e.StoreSvid, + Admin: e.Admin, + Downstream: e.Downstream, + Hint: e.Hint, + CreatedAt: e.CreatedAt, + AdditionalAttributes: additionalAttributesFromProto(e.AdditionalAttributes), }, nil } diff --git a/pkg/agent/manager/cache/lru_cache.go b/pkg/agent/manager/cache/lru_cache.go index e66e475db6..b2f879922f 100644 --- a/pkg/agent/manager/cache/lru_cache.go +++ b/pkg/agent/manager/cache/lru_cache.go @@ -779,10 +779,13 @@ func (c *LRUCache) syncSVIDsWithSubscribers() (map[string]struct{}, []recordAcce remainderSize := c.x509SvidCacheMaxSize - len(c.svids) // add records which are not cached for remainder of cache size - for id := range c.records { + for id, record := range c.records { if len(c.staleEntries) >= remainderSize { break } + if record.entry.GetAdditionalAttributes() != nil && record.entry.AdditionalAttributes.DisableX509SvidPrefetch { + continue + } if _, svidCached := c.svids[id]; !svidCached { if _, ok := c.staleEntries[id]; !ok { c.staleEntries[id] = true diff --git a/pkg/agent/manager/cache/lru_cache_test.go b/pkg/agent/manager/cache/lru_cache_test.go index c251dd415c..a76728be58 100644 --- a/pkg/agent/manager/cache/lru_cache_test.go +++ b/pkg/agent/manager/cache/lru_cache_test.go @@ -554,11 +554,42 @@ func TestLRUCacheCheckSVIDCallback(t *testing.T) { assert.Equal(t, map[string]bool{foo.EntryId: true}, cache.staleEntries) } -func TestLRUCacheGetStaleEntries(t *testing.T) { +func TestLRUCacheGetStaleEntriesDisablePrefetch(t *testing.T) { cache := newTestLRUCache(t) + foo := makeRegistrationEntryWithTTL("FOO", 130, 140, "F") + fooAdditionalAttributes := &common.RegistrationEntry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + } + foo.AdditionalAttributes = fooAdditionalAttributes + bar := makeRegistrationEntryWithTTL("BAR", 130, 140, "B") + barAdditionalAttributes := &common.RegistrationEntry_AdditionalAttributes{ + DisableX509SvidPrefetch: false, + } + bar.AdditionalAttributes = barAdditionalAttributes + qux := makeRegistrationEntryWithTTL("QUX", 130, 140, "Q") + // Create entry but don't mark it stale from checkSVID method; + // it will be marked stale because it does not have SVID cached + cache.UpdateEntries(&UpdateEntries{ + Bundles: makeBundles(bundleV2), + RegistrationEntries: makeRegistrationEntries(foo, bar, qux), + }, func(existingEntry, newEntry *common.RegistrationEntry, svid *X509SVID) bool { + return false + }) + + // Assert that the entry is returned as stale. The `ExpiresAt` field should be unset since there is no SVID. + expectedEntries := []*StaleEntry{ + {Entry: cache.records[bar.EntryId].entry}, + {Entry: cache.records[qux.EntryId].entry}, + } + assert.ElementsMatch(t, expectedEntries, cache.GetStaleEntries()) +} + +func TestLRUCacheGetStaleEntries(t *testing.T) { + cache := newTestLRUCache(t) + bar := makeRegistrationEntryWithTTL("BAR", 130, 140, "B") // Create entry but don't mark it stale from checkSVID method; // it will be marked stale because it does not have SVID cached cache.UpdateEntries(&UpdateEntries{ diff --git a/pkg/agent/manager/manager_test.go b/pkg/agent/manager/manager_test.go index 86f297eefa..228c4b8b7a 100644 --- a/pkg/agent/manager/manager_test.go +++ b/pkg/agent/manager/manager_test.go @@ -297,6 +297,51 @@ func TestHappyPathWithoutSyncNorRotation(t *testing.T) { }) } +func TestX509PrefetchDisabled(t *testing.T) { + dir := spiretest.TempDir(t) + km := fakeagentkeymanager.New(t, dir) + + clk := clock.NewMock(t) + api := newMockAPI(t, &mockAPIConfig{ + km: km, + getAuthorizedEntries: func(*mockAPI, int32, *entryv1.GetAuthorizedEntriesRequest) (*entryv1.GetAuthorizedEntriesResponse, error) { + return makeGetAuthorizedEntriesResponse(t, "resp6"), nil + }, + batchNewX509SVIDEntries: func(*mockAPI, int32) []*common.RegistrationEntry { + return makeBatchNewX509SVIDEntries("resp6") + }, + svidTTL: 200, + clk: clk, + }) + + baseSVID, baseSVIDKey := api.newSVID(joinTokenID, 1*time.Hour) + + cat := fakeagentcatalog.New() + cat.SetKeyManager(km) + + c := &Config{ + ServerAddr: api.addr, + SVID: baseSVID, + SVIDKey: baseSVIDKey, + Log: testLogger, + TrustDomain: trustDomain, + Storage: openStorage(t, dir), + WorkloadKeyType: workloadkey.ECP256, + Bundle: api.bundle, + Metrics: &telemetry.Blackhole{}, + Clk: clk, + Catalog: cat, + SVIDStoreCache: storecache.New(&storecache.Config{TrustDomain: trustDomain, Log: testLogger}), + RotationStrategy: rotationutil.NewRotationStrategy(0), + } + + m, closer := initializeAndRunNewManager(t, c) + defer closer() + + // Expect SVID not to be cached + require.Equal(t, 0, m.CountX509SVIDs()) +} + func TestRotationWithRSAKey(t *testing.T) { dir := spiretest.TempDir(t) km := fakeagentkeymanager.New(t, dir) @@ -1750,12 +1795,13 @@ func makeGetAuthorizedEntriesResponse(t *testing.T, respKeys ...string) *entryv1 spiffeID, err := spiffeid.FromString(regEntry.SpiffeId) require.NoError(t, err) entries = append(entries, &types.Entry{ - Id: regEntry.EntryId, - SpiffeId: api.ProtoFromID(spiffeID), - FederatesWith: regEntry.FederatesWith, - RevisionNumber: regEntry.RevisionNumber, - Selectors: api.ProtoFromSelectors(regEntry.Selectors), - StoreSvid: regEntry.StoreSvid, + Id: regEntry.EntryId, + SpiffeId: api.ProtoFromID(spiffeID), + FederatesWith: regEntry.FederatesWith, + RevisionNumber: regEntry.RevisionNumber, + Selectors: api.ProtoFromSelectors(regEntry.Selectors), + StoreSvid: regEntry.StoreSvid, + AdditionalAttributes: api.ProtoFromAdditionalAttributes(regEntry.AdditionalAttributes), }) } } diff --git a/pkg/common/protoutil/masks_test.go b/pkg/common/protoutil/masks_test.go index 6615839acb..ca0483d565 100644 --- a/pkg/common/protoutil/masks_test.go +++ b/pkg/common/protoutil/masks_test.go @@ -29,20 +29,21 @@ func TestAllTrueMasks(t *testing.T) { }, protoutil.AllTrueBundleMask) spiretest.AssertProtoEqual(t, &types.EntryMask{ - SpiffeId: true, - ParentId: true, - Selectors: true, - X509SvidTtl: true, - JwtSvidTtl: true, - FederatesWith: true, - Admin: true, - CreatedAt: true, - Downstream: true, - ExpiresAt: true, - DnsNames: true, - RevisionNumber: true, - StoreSvid: true, - Hint: true, + SpiffeId: true, + ParentId: true, + Selectors: true, + X509SvidTtl: true, + JwtSvidTtl: true, + FederatesWith: true, + Admin: true, + CreatedAt: true, + Downstream: true, + ExpiresAt: true, + DnsNames: true, + RevisionNumber: true, + StoreSvid: true, + Hint: true, + AdditionalAttributes: true, }, protoutil.AllTrueEntryMask) spiretest.AssertProtoEqual(t, &common.BundleMask{ diff --git a/pkg/server/api/entry.go b/pkg/server/api/entry.go index 33594c2a6f..34467ca39f 100644 --- a/pkg/server/api/entry.go +++ b/pkg/server/api/entry.go @@ -59,6 +59,10 @@ func (e *ReadOnlyEntry) GetCreatedAt() int64 { return e.entry.CreatedAt } +func (e *ReadOnlyEntry) GetAdditionalAttributes() *types.Entry_AdditionalAttributes { + return e.entry.AdditionalAttributes +} + // Manually clone the entry instead of using the protobuf helpers // since those are two times slower. func (e *ReadOnlyEntry) Clone(mask *types.EntryMask) *types.Entry { @@ -132,6 +136,10 @@ func (e *ReadOnlyEntry) Clone(mask *types.EntryMask) *types.Entry { clone.CreatedAt = e.entry.CreatedAt } + if mask.AdditionalAttributes { + clone.AdditionalAttributes = e.entry.AdditionalAttributes + } + return clone } @@ -179,7 +187,7 @@ func RegistrationEntryToProto(e *common.RegistrationEntry) (*types.Entry, error) } } - return &types.Entry{ + entry := &types.Entry{ Id: e.EntryId, SpiffeId: ProtoFromID(spiffeID), ParentId: ProtoFromID(parentID), @@ -195,7 +203,32 @@ func RegistrationEntryToProto(e *common.RegistrationEntry) (*types.Entry, error) JwtSvidTtl: e.JwtSvidTtl, Hint: e.Hint, CreatedAt: e.CreatedAt, - }, nil + } + + additionalAttributes := ProtoFromAdditionalAttributes(e.AdditionalAttributes) + if additionalAttributes != nil { + entry.AdditionalAttributes = additionalAttributes + } + + return entry, nil +} + +func ProtoFromAdditionalAttributes(in *common.RegistrationEntry_AdditionalAttributes) *types.Entry_AdditionalAttributes { + if in != nil { + return &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: in.DisableX509SvidPrefetch, + } + } + return nil +} + +func AdditionalAttributesFromProto(in *types.Entry_AdditionalAttributes) *common.RegistrationEntry_AdditionalAttributes { + if in != nil { + return &common.RegistrationEntry_AdditionalAttributes{ + DisableX509SvidPrefetch: in.DisableX509SvidPrefetch, + } + } + return nil } // ProtoToRegistrationEntry converts and validate entry into common registration entry @@ -309,20 +342,27 @@ func ProtoToRegistrationEntryWithMask(ctx context.Context, td spiffeid.TrustDoma } hint = e.Hint } + + var additionalAttributes *common.RegistrationEntry_AdditionalAttributes + if mask.AdditionalAttributes { + additionalAttributes = AdditionalAttributesFromProto(e.AdditionalAttributes) + } + return &common.RegistrationEntry{ - EntryId: e.Id, - ParentId: parentID.String(), - SpiffeId: spiffeID.String(), - Admin: admin, - DnsNames: dnsNames, - Downstream: downstream, - EntryExpiry: expiresAt, - FederatesWith: federatesWith, - Selectors: selectors, - RevisionNumber: revisionNumber, - StoreSvid: storeSVID, - X509SvidTtl: x509SvidTTL, - JwtSvidTtl: jwtSvidTTL, - Hint: hint, + EntryId: e.Id, + ParentId: parentID.String(), + SpiffeId: spiffeID.String(), + Admin: admin, + DnsNames: dnsNames, + Downstream: downstream, + EntryExpiry: expiresAt, + FederatesWith: federatesWith, + Selectors: selectors, + RevisionNumber: revisionNumber, + StoreSvid: storeSVID, + X509SvidTtl: x509SvidTTL, + JwtSvidTtl: jwtSvidTTL, + Hint: hint, + AdditionalAttributes: additionalAttributes, }, nil } diff --git a/pkg/server/api/entry/v1/service.go b/pkg/server/api/entry/v1/service.go index a49966fcb3..bcc8070477 100644 --- a/pkg/server/api/entry/v1/service.go +++ b/pkg/server/api/entry/v1/service.go @@ -628,6 +628,10 @@ func applyMask(e *types.Entry, mask *types.EntryMask) { e.Hint = "" } + if !mask.AdditionalAttributes { + e.AdditionalAttributes = nil + } + if !mask.CreatedAt { e.CreatedAt = 0 } diff --git a/pkg/server/api/entry/v1/service_test.go b/pkg/server/api/entry/v1/service_test.go index a623dcd546..fa2c87c6ea 100644 --- a/pkg/server/api/entry/v1/service_test.go +++ b/pkg/server/api/entry/v1/service_test.go @@ -1244,6 +1244,9 @@ func TestGetEntry(t *testing.T) { DnsNames: []string{"dns1", "dns2"}, Downstream: true, Hint: "internal", + AdditionalAttributes: &common.RegistrationEntry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, }) require.NoError(t, err) @@ -1309,6 +1312,9 @@ func TestGetEntry(t *testing.T) { Downstream: true, ExpiresAt: expiresAt, Hint: "internal", + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, }, expectLogs: []spiretest.LogEntry{ { @@ -1508,6 +1514,9 @@ func TestBatchCreateEntry(t *testing.T) { X509SvidTtl: 45, JwtSvidTtl: 30, Hint: "external", + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, } // Registration entry for test entry testDSEntry := &common.RegistrationEntry{ @@ -1526,7 +1535,10 @@ func TestBatchCreateEntry(t *testing.T) { X509SvidTtl: 45, JwtSvidTtl: 30, Hint: "external", - CreatedAt: 1678731397, + AdditionalAttributes: &common.RegistrationEntry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, + CreatedAt: 1678731397, } for _, tt := range []struct { @@ -1846,7 +1858,10 @@ func TestBatchCreateEntry(t *testing.T) { JwtSvidTtl: 30, StoreSvid: false, Hint: "external", - CreatedAt: 1678731397, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: true, + }, + CreatedAt: 1678731397, }, }, }, diff --git a/pkg/server/api/entry_test.go b/pkg/server/api/entry_test.go index 8254e20d45..a410f025e2 100644 --- a/pkg/server/api/entry_test.go +++ b/pkg/server/api/entry_test.go @@ -722,6 +722,7 @@ func TestReadOnlyEntry(t *testing.T) { require.Equal(t, readOnlyEntry.GetDnsNames(), entry.DnsNames) require.Equal(t, readOnlyEntry.GetRevisionNumber(), entry.RevisionNumber) require.Equal(t, readOnlyEntry.GetCreatedAt(), entry.CreatedAt) + require.Equal(t, readOnlyEntry.GetAdditionalAttributes(), entry.AdditionalAttributes) } func TestReadOnlyEntryClone(t *testing.T) { @@ -748,6 +749,9 @@ func TestReadOnlyEntryClone(t *testing.T) { Hint: "external", CreatedAt: 1678731397, StoreSvid: true, + AdditionalAttributes: &types.Entry_AdditionalAttributes{ + DisableX509SvidPrefetch: false, + }, } // Verify that we our test entry has all fields set to make sure diff --git a/pkg/server/datastore/sqlstore/migration.go b/pkg/server/datastore/sqlstore/migration.go index 60d1e5d149..635b9caf01 100644 --- a/pkg/server/datastore/sqlstore/migration.go +++ b/pkg/server/datastore/sqlstore/migration.go @@ -276,7 +276,7 @@ import ( const ( // the latest schema version of the database in the code - latestSchemaVersion = 24 + latestSchemaVersion = 25 // lastMinorReleaseSchemaVersion is the schema version supported by the // last minor release. When the migrations are opportunistically pruned @@ -509,6 +509,8 @@ func migrateVersion(tx *gorm.DB, currVersion int, log logrus.FieldLogger) (versi switch currVersion { case 23: err = migrateToV24(tx) + case 24: + err = migrateToV25(tx) default: err = newSQLError("no migration support for unknown schema version %d", currVersion) } @@ -527,6 +529,14 @@ func migrateToV24(tx *gorm.DB) error { return nil } +func migrateToV25(tx *gorm.DB) error { + // Add additional_attributes column to registered_entries table + if err := tx.AutoMigrate(&RegisteredEntry{}).Error; err != nil { + return newWrappedSQLError(err) + } + return nil +} + func addFederatedRegistrationEntriesRegisteredEntryIDIndex(tx *gorm.DB) error { // GORM creates the federated_registration_entries implicitly with a primary // key tuple (bundle_id, registered_entry_id). Unfortunately, MySQL5 does diff --git a/pkg/server/datastore/sqlstore/migration_test.go b/pkg/server/datastore/sqlstore/migration_test.go index 9c0b11f64d..0b3d2a7138 100644 --- a/pkg/server/datastore/sqlstore/migration_test.go +++ b/pkg/server/datastore/sqlstore/migration_test.go @@ -57,6 +57,47 @@ var ( CREATE INDEX idx_federated_registration_entries_registered_entry_id ON "federated_registration_entries"(registered_entry_id) ; COMMIT; `, + 24: ` + PRAGMA foreign_keys=OFF; + BEGIN TRANSACTION; + CREATE TABLE IF NOT EXISTS "federated_registration_entries" ("bundle_id" integer,"registered_entry_id" integer, PRIMARY KEY ("bundle_id","registered_entry_id")); + CREATE TABLE IF NOT EXISTS "bundles" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"trust_domain" varchar(255) NOT NULL,"data" blob ); + INSERT INTO bundles VALUES(1,'2026-02-18 10:42:12.139101+00:00','2026-02-18 10:42:12.140014+00:00','spiffe://example.org',X'0a147370696666653a2f2f6578616d706c652e6f726712df030adc03308201d83082015ea0030201020214449db4c88cda977653f4d5e4770aec9b4b1e970c300a06082a8648ce3d040304301e310b3009060355040613025553310f300d060355040a0c06535049464645301e170d3233303531353032303530365a170d3238303531333032303530365a301e310b3009060355040613025553310f300d060355040a0c065350494646453076301006072a8648ce3d020106052b8104002203620004f57073b72f16fdec785ebd117735018227bfa2475a51385e485d0f42f540693b1768fd49ef2bf40e195ac38e48ec2bfd1cfdb51ce98cc48959d177aab0e97db0ce47e7b1c1416bb46c83577f0e2375e1dd079be4d57c8dc81410c5e5294b1867a35d305b301d0603551d0e04160414928ae360c6aaa7cf6aff8d1716b0046aa61c10ff300f0603551d130101ff040530030101ff300e0603551d0f0101ff04040302010630190603551d1104123010860e7370696666653a2f2f6c6f63616c300a06082a8648ce3d0403040368003065023100e7843c85f844778a95c9cc1b2cdcce9bf1d0ae9d67d7e6b6c5cf3c894d37e8530f6a7711d4f2ea82c3833df5b2b6d75102300a2287548b879888c6bdf88dab55b8fc80ec490059f484b2c4177403997b463e9011b3da82f8a6e29254eee45a6293641a85010a5b3059301306072a8648ce3d020106082a8648ce3d03010703420004c5f71bf758cacd8d14b8cf7feac452344ef4e6179e90a7c9827119ec56812d37cf5da87c41c6e2aade917438e6e1e85511d70785d9eacceb5c97d49b9d876a4f122069484c38587a6433556936664b42576f686252454e6a626b62715945557665771884d2dbcc062801'); + CREATE TABLE IF NOT EXISTS "attested_node_entries" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"spiffe_id" varchar(255),"data_type" varchar(255),"serial_number" varchar(255),"expires_at" datetime,"new_serial_number" varchar(255),"new_expires_at" datetime,"can_reattest" bool,"agent_version" varchar(255) ); + CREATE TABLE IF NOT EXISTS "attested_node_entries_events" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"spiffe_id" varchar(255) ); + CREATE TABLE IF NOT EXISTS "node_resolver_map_entries" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"spiffe_id" varchar(255),"type" varchar(255),"value" varchar(255) ); + CREATE TABLE IF NOT EXISTS "registered_entries" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"entry_id" varchar(255),"spiffe_id" varchar(255),"parent_id" varchar(255),"ttl" integer,"admin" bool,"downstream" bool,"expiry" bigint,"revision_number" bigint,"store_svid" bool,"hint" varchar(255),"jwt_svid_ttl" integer ); + CREATE TABLE IF NOT EXISTS "registered_entries_events" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"entry_id" varchar(255) ); + CREATE TABLE IF NOT EXISTS "join_tokens" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"token" varchar(255),"expiry" bigint ); + CREATE TABLE IF NOT EXISTS "selectors" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"registered_entry_id" integer,"type" varchar(255),"value" varchar(255) ); + CREATE TABLE IF NOT EXISTS "migrations" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"version" integer,"code_version" varchar(255) ); + INSERT INTO migrations VALUES(1,'2026-02-18 10:42:12.131652+00:00','2026-02-18 10:42:12.131652+00:00',24,'1.14.2-dev-27d2083'); + CREATE TABLE IF NOT EXISTS "dns_names" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"registered_entry_id" integer,"value" varchar(255) ); + CREATE TABLE IF NOT EXISTS "federated_trust_domains" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"trust_domain" varchar(255) NOT NULL,"bundle_endpoint_url" varchar(255),"bundle_endpoint_profile" varchar(255),"endpoint_spiffe_id" varchar(255),"implicit" bool ); + CREATE TABLE IF NOT EXISTS "ca_journals" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"data" blob,"active_x509_authority_id" varchar(255),"active_jwt_authority_id" varchar(255) ); + INSERT INTO ca_journals VALUES(1,'2026-02-18 10:42:12.139336+00:00','2026-02-18 10:42:12.140316+00:00',X'0a99090a01411084afd6cc061a97043082021330820199a003020102021100edc054dc96e559d730b03d4ed8325d8d300a06082a8648ce3d040303301e310b3009060355040613025553310f300d060355040a0c06535049464645301e170d3236303231383130343230325a170d3236303231393130343231325a3050310b3009060355040613025553310f300d060355040a13065350494646453130302e060355040513273235363631393333353330383839393338353431373438373938393233313537363633333937363059301306072a8648ce3d020106082a8648ce3d03010703420004259745e04850a4c2f6d6f3dd8414ae098d08c523f6de5d04ca2332aa12ae5bcf64c7f16fb4423c923eba1b7e88dfa21dee985e6643a8c45c2284b166919403f4a38185308182300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e04160414d15f6be56f1b49cdbbeb148b14dd9663caab881d301f0603551d23041830168014928ae360c6aaa7cf6aff8d1716b0046aa61c10ff301f0603551d110418301686147370696666653a2f2f6578616d706c652e6f7267300a06082a8648ce3d0403030368003065023100e72005fbbd726232c3fde94c7da6fe7614377890e87a8391b6e499461be26825c1636d0c1a279be863d68e63b53b8d1f0230209e99243c0f6ed7db2c9e0b190e5abf1a86990761fa8459cee89f1c3ca79ce23c641fe066898b7818fdc1bd8abd027e2297043082021330820199a003020102021100edc054dc96e559d730b03d4ed8325d8d300a06082a8648ce3d040303301e310b3009060355040613025553310f300d060355040a0c06535049464645301e170d3236303231383130343230325a170d3236303231393130343231325a3050310b3009060355040613025553310f300d060355040a13065350494646453130302e060355040513273235363631393333353330383839393338353431373438373938393233313537363633333937363059301306072a8648ce3d020106082a8648ce3d03010703420004259745e04850a4c2f6d6f3dd8414ae098d08c523f6de5d04ca2332aa12ae5bcf64c7f16fb4423c923eba1b7e88dfa21dee985e6643a8c45c2284b166919403f4a38185308182300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e04160414d15f6be56f1b49cdbbeb148b14dd9663caab881d301f0603551d23041830168014928ae360c6aaa7cf6aff8d1716b0046aa61c10ff301f0603551d110418301686147370696666653a2f2f6578616d706c652e6f7267300a06082a8648ce3d0403030368003065023100e72005fbbd726232c3fde94c7da6fe7614377890e87a8391b6e499461be26825c1636d0c1a279be863d68e63b53b8d1f0230209e99243c0f6ed7db2c9e0b190e5abf1a86990761fa8459cee89f1c3ca79ce23c641fe066898b7818fdc1bd8abd027e28033228643135663662653536663162343963646262656231343862313464643936363363616162383831643884d2dbcc0642283932386165333630633661616137636636616666386431373136623030343661613631633130666612b2010a01411084afd6cc061884d2dbcc06222069484c38587a6433556936664b42576f686252454e6a626b62715945557665772a5b3059301306072a8648ce3d020106082a8648ce3d03010703420004c5f71bf758cacd8d14b8cf7feac452344ef4e6179e90a7c9827119ec56812d37cf5da87c41c6e2aade917438e6e1e85511d70785d9eacceb5c97d49b9d876a4f30033a2069484c38587a6433556936664b42576f686252454e6a626b6271594555766577','d15f6be56f1b49cdbbeb148b14dd9663caab881d',''); + INSERT INTO sqlite_sequence VALUES('migrations',1); + INSERT INTO sqlite_sequence VALUES('bundles',1); + INSERT INTO sqlite_sequence VALUES('ca_journals',1); + CREATE UNIQUE INDEX uix_bundles_trust_domain ON "bundles"(trust_domain) ; + CREATE INDEX idx_attested_node_entries_expires_at ON "attested_node_entries"(expires_at) ; + CREATE UNIQUE INDEX uix_attested_node_entries_spiffe_id ON "attested_node_entries"(spiffe_id) ; + CREATE UNIQUE INDEX idx_node_resolver_map ON "node_resolver_map_entries"(spiffe_id, "type", "value") ; + CREATE INDEX idx_registered_entries_hint ON "registered_entries"("hint") ; + CREATE INDEX idx_registered_entries_spiffe_id ON "registered_entries"(spiffe_id) ; + CREATE INDEX idx_registered_entries_parent_id ON "registered_entries"(parent_id) ; + CREATE INDEX idx_registered_entries_expiry ON "registered_entries"("expiry") ; + CREATE UNIQUE INDEX uix_registered_entries_entry_id ON "registered_entries"(entry_id) ; + CREATE UNIQUE INDEX uix_join_tokens_token ON "join_tokens"("token") ; + CREATE INDEX idx_selectors_type_value ON "selectors"("type", "value") ; + CREATE UNIQUE INDEX idx_selector_entry ON "selectors"(registered_entry_id, "type", "value") ; + CREATE UNIQUE INDEX idx_dns_entry ON "dns_names"(registered_entry_id, "value") ; + CREATE UNIQUE INDEX uix_federated_trust_domains_trust_domain ON "federated_trust_domains"(trust_domain) ; + CREATE INDEX idx_ca_journals_active_x509_authority_id ON "ca_journals"(active_x509_authority_id) ; + CREATE INDEX idx_ca_journals_active_jwt_authority_id ON "ca_journals"(active_jwt_authority_id) ; + CREATE INDEX idx_federated_registration_entries_registered_entry_id ON "federated_registration_entries"(registered_entry_id) ; + COMMIT; + `, } ) diff --git a/pkg/server/datastore/sqlstore/models.go b/pkg/server/datastore/sqlstore/models.go index 69c73165e7..cb38637bea 100644 --- a/pkg/server/datastore/sqlstore/models.go +++ b/pkg/server/datastore/sqlstore/models.go @@ -100,6 +100,11 @@ type RegisteredEntry struct { // TTL of JWT identities derived from this entry JWTSvidTTL int32 `gorm:"column:jwt_svid_ttl"` + + // AdditionalAttributes may contain a number of optional fields controlling + // the various aspects of the agent's behaviour with respect to a given + // registration entry + AdditionalAttributes []byte `gorm:"size:65535,column:additional_attributes"` } // RegisteredEntryEvent holds the entry id of a registered entry that had an event diff --git a/pkg/server/datastore/sqlstore/sqlstore.go b/pkg/server/datastore/sqlstore/sqlstore.go index b12d9f1bf1..f3c9d8abb7 100644 --- a/pkg/server/datastore/sqlstore/sqlstore.go +++ b/pkg/server/datastore/sqlstore/sqlstore.go @@ -67,6 +67,9 @@ const ( // Maximum size for preallocation in a paginated request maxResultPreallocation = 1000 + + // Maximum size for additional attributes message in a registration entry + maxAdditionalAttributesSize = 65535 ) // Configuration for the sql datastore implementation. @@ -2550,17 +2553,23 @@ func createRegistrationEntry(tx *gorm.DB, entry *common.RegistrationEntry) (*com return nil, err } + AdditionalAttributes, err := marshalAndValidateAdditionalAttributes(entry.AdditionalAttributes) + if err != nil { + return nil, err + } + newRegisteredEntry := RegisteredEntry{ - EntryID: entryID, - SpiffeID: entry.SpiffeId, - ParentID: entry.ParentId, - TTL: entry.X509SvidTtl, - Admin: entry.Admin, - Downstream: entry.Downstream, - Expiry: entry.EntryExpiry, - StoreSvid: entry.StoreSvid, - JWTSvidTTL: entry.JwtSvidTtl, - Hint: entry.Hint, + EntryID: entryID, + SpiffeID: entry.SpiffeId, + ParentID: entry.ParentId, + TTL: entry.X509SvidTtl, + Admin: entry.Admin, + Downstream: entry.Downstream, + Expiry: entry.EntryExpiry, + StoreSvid: entry.StoreSvid, + JWTSvidTTL: entry.JwtSvidTtl, + Hint: entry.Hint, + AdditionalAttributes: AdditionalAttributes, } if err := tx.Create(&newRegisteredEntry).Error; err != nil { @@ -2675,7 +2684,8 @@ SELECT NULL AS dns_name_id, NULL AS dns_name, revision_number, - jwt_svid_ttl AS reg_jwt_svid_ttl + jwt_svid_ttl AS reg_jwt_svid_ttl, + additional_attributes FROM registered_entries WHERE id IN (SELECT id FROM listing) @@ -2683,7 +2693,7 @@ WHERE id IN (SELECT id FROM listing) UNION SELECT - F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL + F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL, NULL FROM bundles B INNER JOIN @@ -2696,7 +2706,7 @@ WHERE UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL, NULL FROM dns_names WHERE registered_entry_id IN (SELECT id FROM listing) @@ -2704,7 +2714,7 @@ WHERE registered_entry_id IN (SELECT id FROM listing) UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL, NULL FROM selectors WHERE registered_entry_id IN (SELECT id FROM listing) @@ -2739,7 +2749,8 @@ SELECT NULL ::integer AS dns_name_id, NULL AS dns_name, revision_number, - jwt_svid_ttl AS reg_jwt_svid_ttl + jwt_svid_ttl AS reg_jwt_svid_ttl, + additional_attributes FROM registered_entries WHERE id IN (SELECT id FROM listing) @@ -2747,7 +2758,7 @@ WHERE id IN (SELECT id FROM listing) UNION SELECT - F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL + F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL, NULL FROM bundles B INNER JOIN @@ -2760,7 +2771,7 @@ WHERE UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL, NULL FROM dns_names WHERE registered_entry_id IN (SELECT id FROM listing) @@ -2768,7 +2779,7 @@ WHERE registered_entry_id IN (SELECT id FROM listing) UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL, NULL FROM selectors WHERE registered_entry_id IN (SELECT id FROM listing) @@ -2799,7 +2810,8 @@ SELECT D.id AS dns_name_id, D.value AS dns_name, E.revision_number, - E.jwt_svid_ttl AS reg_jwt_svid_ttl + E.jwt_svid_ttl AS reg_jwt_svid_ttl, + E.additional_attributes AS additional_attributes FROM registered_entries E LEFT JOIN @@ -2841,7 +2853,8 @@ SELECT NULL AS dns_name_id, NULL AS dns_name, revision_number, - jwt_svid_ttl AS reg_jwt_svid_ttl + jwt_svid_ttl AS reg_jwt_svid_ttl, + additional_attributes FROM registered_entries WHERE id IN (SELECT id FROM listing) @@ -2849,7 +2862,7 @@ WHERE id IN (SELECT id FROM listing) UNION SELECT - F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL + F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL, NULL FROM bundles B INNER JOIN @@ -2862,7 +2875,7 @@ WHERE UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL, NULL FROM dns_names WHERE registered_entry_id IN (SELECT id FROM listing) @@ -2870,7 +2883,7 @@ WHERE registered_entry_id IN (SELECT id FROM listing) UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL, NULL FROM selectors WHERE registered_entry_id IN (SELECT id FROM listing) @@ -3052,7 +3065,8 @@ SELECT NULL AS dns_name_id, NULL AS dns_name, revision_number, - jwt_svid_ttl AS reg_jwt_svid_ttl + jwt_svid_ttl AS reg_jwt_svid_ttl, + additional_attributes FROM registered_entries `) @@ -3071,7 +3085,7 @@ FROM UNION SELECT - F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL + F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL, NULL FROM bundles B INNER JOIN @@ -3086,7 +3100,7 @@ ON UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL, NULL FROM dns_names `) @@ -3097,7 +3111,7 @@ FROM UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL, NULL FROM selectors `) @@ -3147,7 +3161,8 @@ SELECT NULL ::integer AS dns_name_id, NULL AS dns_name, revision_number, - jwt_svid_ttl AS reg_jwt_svid_ttl + jwt_svid_ttl AS reg_jwt_svid_ttl, + additional_attributes FROM registered_entries `) @@ -3165,7 +3180,7 @@ FROM UNION ALL SELECT - F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL + F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL, NULL FROM bundles B INNER JOIN @@ -3180,7 +3195,7 @@ ON UNION ALL SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL, NULL FROM dns_names `) @@ -3191,7 +3206,7 @@ FROM UNION ALL SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL, NULL FROM selectors `) @@ -3240,7 +3255,8 @@ SELECT D.id AS dns_name_id, D.value AS dns_name, E.revision_number, - E.jwt_svid_ttl AS reg_jwt_svid_ttl + E.jwt_svid_ttl AS reg_jwt_svid_ttl, + E.additional_attributes AS additional_attributes FROM registered_entries E LEFT JOIN @@ -3314,7 +3330,8 @@ SELECT NULL AS dns_name_id, NULL AS dns_name, revision_number, - jwt_svid_ttl AS reg_jwt_svid_ttl + jwt_svid_ttl AS reg_jwt_svid_ttl, + additional_attributes FROM registered_entries `) @@ -3332,7 +3349,7 @@ FROM UNION SELECT - F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL + F.registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, B.trust_domain, NULL, NULL, NULL, NULL, NULL FROM bundles B INNER JOIN @@ -3347,7 +3364,7 @@ ON UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, value, NULL, NULL, NULL FROM dns_names `) @@ -3358,7 +3375,7 @@ FROM UNION SELECT - registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL + registered_entry_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, id, type, value, NULL, NULL, NULL, NULL, NULL, NULL FROM selectors `) @@ -3856,25 +3873,26 @@ func fillNodeSelectorFromRow(nodeSelector *common.Selector, r *nodeSelectorRow) } type entryRow struct { - EId uint64 - EntryID sql.NullString - SpiffeID sql.NullString - ParentID sql.NullString - RegTTL sql.NullInt64 - Admin sql.NullBool - Downstream sql.NullBool - Expiry sql.NullInt64 - SelectorID sql.NullInt64 - SelectorType sql.NullString - SelectorValue sql.NullString - StoreSvid sql.NullBool - Hint sql.NullString - CreatedAt sql.NullTime - TrustDomain sql.NullString - DNSNameID sql.NullInt64 - DNSName sql.NullString - RevisionNumber sql.NullInt64 - RegJwtSvidTTL sql.NullInt64 + EId uint64 + EntryID sql.NullString + SpiffeID sql.NullString + ParentID sql.NullString + RegTTL sql.NullInt64 + Admin sql.NullBool + Downstream sql.NullBool + Expiry sql.NullInt64 + SelectorID sql.NullInt64 + SelectorType sql.NullString + SelectorValue sql.NullString + StoreSvid sql.NullBool + Hint sql.NullString + CreatedAt sql.NullTime + TrustDomain sql.NullString + DNSNameID sql.NullInt64 + DNSName sql.NullString + RevisionNumber sql.NullInt64 + RegJwtSvidTTL sql.NullInt64 + AdditionalAttributes sql.Null[[]byte] } func scanEntryRow(rs *sql.Rows, r *entryRow) error { @@ -3898,6 +3916,7 @@ func scanEntryRow(rs *sql.Rows, r *entryRow) error { &r.DNSName, &r.RevisionNumber, &r.RegJwtSvidTTL, + &r.AdditionalAttributes, )) } @@ -3960,6 +3979,15 @@ func fillEntryFromRow(entry *common.RegistrationEntry, r *entryRow) error { entry.CreatedAt = roundedInSecondsUnix(r.CreatedAt.Time) } + if r.AdditionalAttributes.Valid { + if len(r.AdditionalAttributes.V) > 0 { + entry.AdditionalAttributes = &common.RegistrationEntry_AdditionalAttributes{} + if err := proto.Unmarshal(r.AdditionalAttributes.V, entry.AdditionalAttributes); err != nil { + return newSQLError("could not parse cache hint flags: %s", err) + } + } + } + return nil } @@ -4057,6 +4085,13 @@ func updateRegistrationEntry(tx *gorm.DB, e *common.RegistrationEntry, mask *com if mask == nil || mask.Hint { entry.Hint = e.Hint } + if mask == nil || mask.AdditionalAttributes { + AdditionalAttributes, err := marshalAndValidateAdditionalAttributes(e.AdditionalAttributes) + if err != nil { + return nil, err + } + entry.AdditionalAttributes = AdditionalAttributes + } // Revision number is increased by 1 on every update call entry.RevisionNumber++ @@ -4497,6 +4532,22 @@ func modelToBundle(model *Bundle) (*common.Bundle, error) { return bundle, nil } +func marshalAndValidateAdditionalAttributes(additionalAttributes *common.RegistrationEntry_AdditionalAttributes) ([]byte, error) { + if additionalAttributes == nil { + return nil, nil + } + + marshaledAdditionalAttributes, err := proto.Marshal(additionalAttributes) + if err != nil { + return nil, newValidationError("invalid additional attributes: %s", err) + } + if len(marshaledAdditionalAttributes) > maxAdditionalAttributesSize { + return nil, newValidationError("invalid registration entry: additional attributes size exceeds the maximum allowed size of %d bytes", maxAdditionalAttributesSize) + } + + return marshaledAdditionalAttributes, nil +} + func validateRegistrationEntry(entry *common.RegistrationEntry) error { if entry == nil { return newValidationError("invalid request: missing registered entry") @@ -4510,6 +4561,9 @@ func validateRegistrationEntry(entry *common.RegistrationEntry) error { // it is done to avoid users to mix selectors from different platforms in // entries with storable SVIDs if entry.StoreSvid { + if entry.AdditionalAttributes.GetDisableX509SvidPrefetch() { + return newValidationError("specifying cache behaviour is incompatible with storable SVIDs") + } // Selectors must never be empty tpe := entry.Selectors[0].Type for _, t := range entry.Selectors { @@ -4639,22 +4693,32 @@ func modelToEntry(tx *gorm.DB, model RegisteredEntry) (*common.RegistrationEntry federatesWith = append(federatesWith, bundle.TrustDomain) } + AdditionalAttributes := &common.RegistrationEntry_AdditionalAttributes{} + if len(model.AdditionalAttributes) != 0 { + if err := proto.Unmarshal(model.AdditionalAttributes, AdditionalAttributes); err != nil { + return nil, err + } + } else { + AdditionalAttributes = nil + } + return &common.RegistrationEntry{ - EntryId: model.EntryID, - Selectors: selectors, - SpiffeId: model.SpiffeID, - ParentId: model.ParentID, - X509SvidTtl: model.TTL, - FederatesWith: federatesWith, - Admin: model.Admin, - Downstream: model.Downstream, - EntryExpiry: model.Expiry, - DnsNames: dnsList, - RevisionNumber: model.RevisionNumber, - StoreSvid: model.StoreSvid, - JwtSvidTtl: model.JWTSvidTTL, - Hint: model.Hint, - CreatedAt: roundedInSecondsUnix(model.CreatedAt), + EntryId: model.EntryID, + Selectors: selectors, + SpiffeId: model.SpiffeID, + ParentId: model.ParentID, + X509SvidTtl: model.TTL, + FederatesWith: federatesWith, + Admin: model.Admin, + Downstream: model.Downstream, + EntryExpiry: model.Expiry, + DnsNames: dnsList, + RevisionNumber: model.RevisionNumber, + StoreSvid: model.StoreSvid, + JwtSvidTtl: model.JWTSvidTTL, + Hint: model.Hint, + AdditionalAttributes: AdditionalAttributes, + CreatedAt: roundedInSecondsUnix(model.CreatedAt), }, nil } diff --git a/pkg/server/datastore/sqlstore/sqlstore_test.go b/pkg/server/datastore/sqlstore/sqlstore_test.go index cdbf3022d7..f36c923f96 100644 --- a/pkg/server/datastore/sqlstore/sqlstore_test.go +++ b/pkg/server/datastore/sqlstore/sqlstore_test.go @@ -5241,6 +5241,9 @@ func (s *PluginSuite) TestMigration() { case 23: // Migration from v23 to v24 adds agent_version column prepareDB(true) + case 24: + // Migration from v24 to v25 adds additional_attributes column + prepareDB(true) default: t.Fatalf("no migration test added for schema version %d", schemaVersion) } diff --git a/pkg/server/datastore/sqlstore/testdata/invalid_registration_entries.json b/pkg/server/datastore/sqlstore/testdata/invalid_registration_entries.json index dc357fde3f..f8b4820adc 100644 --- a/pkg/server/datastore/sqlstore/testdata/invalid_registration_entries.json +++ b/pkg/server/datastore/sqlstore/testdata/invalid_registration_entries.json @@ -63,5 +63,18 @@ ], "spiffe_id": "SpiffeId5" }, + { + "selectors": [ + { + "type": "Type", + "value": "Value" + } + ], + "spiffe_id": "SpiffeId1", + "store_svid": true, + "additional_attributes": { + "disable_x509_svid_prefetch": true + } + }, null ] diff --git a/pkg/server/datastore/sqlstore/testdata/valid_registration_entries.json b/pkg/server/datastore/sqlstore/testdata/valid_registration_entries.json index da10f93a0c..cdfc1dd5f6 100644 --- a/pkg/server/datastore/sqlstore/testdata/valid_registration_entries.json +++ b/pkg/server/datastore/sqlstore/testdata/valid_registration_entries.json @@ -53,5 +53,17 @@ } ], "spiffe_id": "SpiffeId4" + }, + { + "selectors": [ + { + "type": "Type1", + "value": "Value3" + } + ], + "spiffe_id": "SpiffeId1", + "additional_attributes": { + "disable_x509_svid_prefetch": true + } } ] diff --git a/proto/spire/common/common.pb.go b/proto/spire/common/common.pb.go index d116f6f849..6e67c9217b 100644 --- a/proto/spire/common/common.pb.go +++ b/proto/spire/common/common.pb.go @@ -373,9 +373,10 @@ type RegistrationEntry struct { // identity should be used by a workload when more than one SVID is returned. Hint string `protobuf:"bytes,14,opt,name=hint,proto3" json:"hint,omitempty"` // * Time of creation, in seconds from epoch - CreatedAt int64 `protobuf:"varint,15,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + CreatedAt int64 `protobuf:"varint,15,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + AdditionalAttributes *RegistrationEntry_AdditionalAttributes `protobuf:"bytes,16,opt,name=additional_attributes,json=additionalAttributes,proto3,oneof" json:"additional_attributes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RegistrationEntry) Reset() { @@ -513,24 +514,32 @@ func (x *RegistrationEntry) GetCreatedAt() int64 { return 0 } +func (x *RegistrationEntry) GetAdditionalAttributes() *RegistrationEntry_AdditionalAttributes { + if x != nil { + return x.AdditionalAttributes + } + return nil +} + // * The RegistrationEntryMask is used to update only selected fields of the RegistrationEntry type RegistrationEntryMask struct { - state protoimpl.MessageState `protogen:"open.v1"` - Selectors bool `protobuf:"varint,1,opt,name=selectors,proto3" json:"selectors,omitempty"` - ParentId bool `protobuf:"varint,2,opt,name=parent_id,json=parentId,proto3" json:"parent_id,omitempty"` - SpiffeId bool `protobuf:"varint,3,opt,name=spiffe_id,json=spiffeId,proto3" json:"spiffe_id,omitempty"` - X509SvidTtl bool `protobuf:"varint,4,opt,name=x509_svid_ttl,json=x509SvidTtl,proto3" json:"x509_svid_ttl,omitempty"` - FederatesWith bool `protobuf:"varint,5,opt,name=federates_with,json=federatesWith,proto3" json:"federates_with,omitempty"` - EntryId bool `protobuf:"varint,6,opt,name=entry_id,json=entryId,proto3" json:"entry_id,omitempty"` - Admin bool `protobuf:"varint,7,opt,name=admin,proto3" json:"admin,omitempty"` - Downstream bool `protobuf:"varint,8,opt,name=downstream,proto3" json:"downstream,omitempty"` - EntryExpiry bool `protobuf:"varint,9,opt,name=entryExpiry,proto3" json:"entryExpiry,omitempty"` - DnsNames bool `protobuf:"varint,10,opt,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty"` - StoreSvid bool `protobuf:"varint,11,opt,name=store_svid,json=storeSvid,proto3" json:"store_svid,omitempty"` - JwtSvidTtl bool `protobuf:"varint,12,opt,name=jwt_svid_ttl,json=jwtSvidTtl,proto3" json:"jwt_svid_ttl,omitempty"` - Hint bool `protobuf:"varint,13,opt,name=hint,proto3" json:"hint,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Selectors bool `protobuf:"varint,1,opt,name=selectors,proto3" json:"selectors,omitempty"` + ParentId bool `protobuf:"varint,2,opt,name=parent_id,json=parentId,proto3" json:"parent_id,omitempty"` + SpiffeId bool `protobuf:"varint,3,opt,name=spiffe_id,json=spiffeId,proto3" json:"spiffe_id,omitempty"` + X509SvidTtl bool `protobuf:"varint,4,opt,name=x509_svid_ttl,json=x509SvidTtl,proto3" json:"x509_svid_ttl,omitempty"` + FederatesWith bool `protobuf:"varint,5,opt,name=federates_with,json=federatesWith,proto3" json:"federates_with,omitempty"` + EntryId bool `protobuf:"varint,6,opt,name=entry_id,json=entryId,proto3" json:"entry_id,omitempty"` + Admin bool `protobuf:"varint,7,opt,name=admin,proto3" json:"admin,omitempty"` + Downstream bool `protobuf:"varint,8,opt,name=downstream,proto3" json:"downstream,omitempty"` + EntryExpiry bool `protobuf:"varint,9,opt,name=entryExpiry,proto3" json:"entryExpiry,omitempty"` + DnsNames bool `protobuf:"varint,10,opt,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty"` + StoreSvid bool `protobuf:"varint,11,opt,name=store_svid,json=storeSvid,proto3" json:"store_svid,omitempty"` + JwtSvidTtl bool `protobuf:"varint,12,opt,name=jwt_svid_ttl,json=jwtSvidTtl,proto3" json:"jwt_svid_ttl,omitempty"` + Hint bool `protobuf:"varint,13,opt,name=hint,proto3" json:"hint,omitempty"` + AdditionalAttributes bool `protobuf:"varint,14,opt,name=additional_attributes,json=additionalAttributes,proto3" json:"additional_attributes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RegistrationEntryMask) Reset() { @@ -654,6 +663,13 @@ func (x *RegistrationEntryMask) GetHint() bool { return false } +func (x *RegistrationEntryMask) GetAdditionalAttributes() bool { + if x != nil { + return x.AdditionalAttributes + } + return false +} + // * A list of registration entries. type RegistrationEntries struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1094,6 +1110,60 @@ func (x *AttestedNodeMask) GetAgentVersion() bool { return false } +// * This nested message is reserved to contain a number of optional fields +// controlling the various aspects of the agent's behaviour with respect to a +// given registration entry. It serves to enable introducing and testing out new +// tunables, without having to modify the datastore schema. Over time, some of +// the fields contained therein may be considered eligible for their dedicated +// attributes in the datastore. +type RegistrationEntry_AdditionalAttributes struct { + state protoimpl.MessageState `protogen:"open.v1"` + // * Flag indicating whether the agent should prefetch and cache X509 SVID. + // Can be set to `true` if the workload is unlikely to request an X509 SVID. + // This is meant to prevent unnecessary effort spent on generating SVIDs of types, + // which are unlikely to be needed. + DisableX509SvidPrefetch bool `protobuf:"varint,1,opt,name=disable_x509_svid_prefetch,json=disableX509SvidPrefetch,proto3" json:"disable_x509_svid_prefetch,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegistrationEntry_AdditionalAttributes) Reset() { + *x = RegistrationEntry_AdditionalAttributes{} + mi := &file_spire_common_common_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegistrationEntry_AdditionalAttributes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegistrationEntry_AdditionalAttributes) ProtoMessage() {} + +func (x *RegistrationEntry_AdditionalAttributes) ProtoReflect() protoreflect.Message { + mi := &file_spire_common_common_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegistrationEntry_AdditionalAttributes.ProtoReflect.Descriptor instead. +func (*RegistrationEntry_AdditionalAttributes) Descriptor() ([]byte, []int) { + return file_spire_common_common_proto_rawDescGZIP(), []int{5, 0} +} + +func (x *RegistrationEntry_AdditionalAttributes) GetDisableX509SvidPrefetch() bool { + if x != nil { + return x.DisableX509SvidPrefetch + } + return false +} + var File_spire_common_common_proto protoreflect.FileDescriptor const file_spire_common_common_proto_rawDesc = "" + @@ -1117,7 +1187,7 @@ const file_spire_common_common_proto_rawDesc = "" + "\x12new_cert_not_after\x18\x06 \x01(\x03R\x0fnewCertNotAfter\x124\n" + "\tselectors\x18\a \x03(\v2\x16.spire.common.SelectorR\tselectors\x12!\n" + "\fcan_reattest\x18\b \x01(\bR\vcanReattest\x12#\n" + - "\ragent_version\x18\t \x01(\tR\fagentVersion\"\xfb\x03\n" + + "\ragent_version\x18\t \x01(\tR\fagentVersion\"\xda\x05\n" + "\x11RegistrationEntry\x124\n" + "\tselectors\x18\x01 \x03(\v2\x16.spire.common.SelectorR\tselectors\x12\x1b\n" + "\tparent_id\x18\x02 \x01(\tR\bparentId\x12\x1b\n" + @@ -1139,7 +1209,11 @@ const file_spire_common_common_proto_rawDesc = "" + "jwtSvidTtl\x12\x12\n" + "\x04hint\x18\x0e \x01(\tR\x04hint\x12\x1d\n" + "\n" + - "created_at\x18\x0f \x01(\x03R\tcreatedAt\"\x9f\x03\n" + + "created_at\x18\x0f \x01(\x03R\tcreatedAt\x12n\n" + + "\x15additional_attributes\x18\x10 \x01(\v24.spire.common.RegistrationEntry.AdditionalAttributesH\x00R\x14additionalAttributes\x88\x01\x01\x1aS\n" + + "\x14AdditionalAttributes\x12;\n" + + "\x1adisable_x509_svid_prefetch\x18\x01 \x01(\bR\x17disableX509SvidPrefetchB\x18\n" + + "\x16_additional_attributes\"\xd4\x03\n" + "\x15RegistrationEntryMask\x12\x1c\n" + "\tselectors\x18\x01 \x01(\bR\tselectors\x12\x1b\n" + "\tparent_id\x18\x02 \x01(\bR\bparentId\x12\x1b\n" + @@ -1158,7 +1232,8 @@ const file_spire_common_common_proto_rawDesc = "" + "store_svid\x18\v \x01(\bR\tstoreSvid\x12 \n" + "\fjwt_svid_ttl\x18\f \x01(\bR\n" + "jwtSvidTtl\x12\x12\n" + - "\x04hint\x18\r \x01(\bR\x04hint\"P\n" + + "\x04hint\x18\r \x01(\bR\x04hint\x123\n" + + "\x15additional_attributes\x18\x0e \x01(\bR\x14additionalAttributes\"P\n" + "\x13RegistrationEntries\x129\n" + "\aentries\x18\x01 \x03(\v2\x1f.spire.common.RegistrationEntryR\aentries\"K\n" + "\vCertificate\x12\x1b\n" + @@ -1208,35 +1283,37 @@ func file_spire_common_common_proto_rawDescGZIP() []byte { return file_spire_common_common_proto_rawDescData } -var file_spire_common_common_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_spire_common_common_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_spire_common_common_proto_goTypes = []any{ - (*Empty)(nil), // 0: spire.common.Empty - (*AttestationData)(nil), // 1: spire.common.AttestationData - (*Selector)(nil), // 2: spire.common.Selector - (*Selectors)(nil), // 3: spire.common.Selectors - (*AttestedNode)(nil), // 4: spire.common.AttestedNode - (*RegistrationEntry)(nil), // 5: spire.common.RegistrationEntry - (*RegistrationEntryMask)(nil), // 6: spire.common.RegistrationEntryMask - (*RegistrationEntries)(nil), // 7: spire.common.RegistrationEntries - (*Certificate)(nil), // 8: spire.common.Certificate - (*PublicKey)(nil), // 9: spire.common.PublicKey - (*Bundle)(nil), // 10: spire.common.Bundle - (*BundleMask)(nil), // 11: spire.common.BundleMask - (*AttestedNodeMask)(nil), // 12: spire.common.AttestedNodeMask + (*Empty)(nil), // 0: spire.common.Empty + (*AttestationData)(nil), // 1: spire.common.AttestationData + (*Selector)(nil), // 2: spire.common.Selector + (*Selectors)(nil), // 3: spire.common.Selectors + (*AttestedNode)(nil), // 4: spire.common.AttestedNode + (*RegistrationEntry)(nil), // 5: spire.common.RegistrationEntry + (*RegistrationEntryMask)(nil), // 6: spire.common.RegistrationEntryMask + (*RegistrationEntries)(nil), // 7: spire.common.RegistrationEntries + (*Certificate)(nil), // 8: spire.common.Certificate + (*PublicKey)(nil), // 9: spire.common.PublicKey + (*Bundle)(nil), // 10: spire.common.Bundle + (*BundleMask)(nil), // 11: spire.common.BundleMask + (*AttestedNodeMask)(nil), // 12: spire.common.AttestedNodeMask + (*RegistrationEntry_AdditionalAttributes)(nil), // 13: spire.common.RegistrationEntry.AdditionalAttributes } var file_spire_common_common_proto_depIdxs = []int32{ - 2, // 0: spire.common.Selectors.entries:type_name -> spire.common.Selector - 2, // 1: spire.common.AttestedNode.selectors:type_name -> spire.common.Selector - 2, // 2: spire.common.RegistrationEntry.selectors:type_name -> spire.common.Selector - 5, // 3: spire.common.RegistrationEntries.entries:type_name -> spire.common.RegistrationEntry - 8, // 4: spire.common.Bundle.root_cas:type_name -> spire.common.Certificate - 9, // 5: spire.common.Bundle.jwt_signing_keys:type_name -> spire.common.PublicKey - 9, // 6: spire.common.Bundle.wit_signing_keys:type_name -> spire.common.PublicKey - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 2, // 0: spire.common.Selectors.entries:type_name -> spire.common.Selector + 2, // 1: spire.common.AttestedNode.selectors:type_name -> spire.common.Selector + 2, // 2: spire.common.RegistrationEntry.selectors:type_name -> spire.common.Selector + 13, // 3: spire.common.RegistrationEntry.additional_attributes:type_name -> spire.common.RegistrationEntry.AdditionalAttributes + 5, // 4: spire.common.RegistrationEntries.entries:type_name -> spire.common.RegistrationEntry + 8, // 5: spire.common.Bundle.root_cas:type_name -> spire.common.Certificate + 9, // 6: spire.common.Bundle.jwt_signing_keys:type_name -> spire.common.PublicKey + 9, // 7: spire.common.Bundle.wit_signing_keys:type_name -> spire.common.PublicKey + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_spire_common_common_proto_init() } @@ -1244,13 +1321,14 @@ func file_spire_common_common_proto_init() { if File_spire_common_common_proto != nil { return } + file_spire_common_common_proto_msgTypes[5].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_spire_common_common_proto_rawDesc), len(file_spire_common_common_proto_rawDesc)), NumEnums: 0, - NumMessages: 13, + NumMessages: 14, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/spire/common/common.proto b/proto/spire/common/common.proto index f516a7453b..f41b6ed413 100644 --- a/proto/spire/common/common.proto +++ b/proto/spire/common/common.proto @@ -99,6 +99,20 @@ message RegistrationEntry { string hint = 14; /** Time of creation, in seconds from epoch */ int64 created_at = 15; + /** This nested message is reserved to contain a number of optional fields + controlling the various aspects of the agent's behaviour with respect to a + given registration entry. It serves to enable introducing and testing out new + tunables, without having to modify the datastore schema. Over time, some of + the fields contained therein may be considered eligible for their dedicated + attributes in the datastore. */ + message AdditionalAttributes { + /** Flag indicating whether the agent should prefetch and cache X509 SVID. + Can be set to `true` if the workload is unlikely to request an X509 SVID. + This is meant to prevent unnecessary effort spent on generating SVIDs of types, + which are unlikely to be needed.*/ + bool disable_x509_svid_prefetch = 1; + } + optional AdditionalAttributes additional_attributes = 16; } /** The RegistrationEntryMask is used to update only selected fields of the RegistrationEntry */ @@ -116,6 +130,7 @@ message RegistrationEntryMask { bool store_svid = 11; bool jwt_svid_ttl = 12; bool hint = 13; + bool additional_attributes = 14; } diff --git a/test/fixture/registration/good-for-update.json b/test/fixture/registration/good-for-update.json index 4df150afc8..934dd2df01 100644 --- a/test/fixture/registration/good-for-update.json +++ b/test/fixture/registration/good-for-update.json @@ -45,6 +45,26 @@ "store_svid": true, "x509_svid_ttl": 200, "jwt_svid_ttl": 300 + }, + { + "entry_id": "entry-id-4", + "selectors": [ + { + "type": "type", + "value": "key1:value" + }, + { + "type": "type", + "value": "key2:value" + } + ], + "spiffe_id": "spiffe://example.org/DisableX509SvidPrefetch", + "parent_id": "spiffe://example.org/spire/agent/join_token/TokenDatabase", + "x509_svid_ttl": 200, + "jwt_svid_ttl": 300, + "additional_attributes": { + "disable_x509_svid_prefetch": true + } } ] } diff --git a/test/fixture/registration/good.json b/test/fixture/registration/good.json index 42099149ff..2172365b82 100644 --- a/test/fixture/registration/good.json +++ b/test/fixture/registration/good.json @@ -41,7 +41,23 @@ "parent_id": "spiffe://example.org/spire/agent/join_token/TokenDatabase", "x509_svid_ttl": 200, "jwt_svid_ttl": 30, - "store_svid": true + "store_svid": true + }, + { + "selectors": [ + { + "type": "unix", + "value": "uid:1111" + } + ], + "spiffe_id": "spiffe://example.org/additionalattr", + "parent_id": "spiffe://example.org/spire/agent/join_token/TokenBlog", + "x509_svid_ttl": 200, + "jwt_svid_ttl": 30, + "admin": true, + "additional_attributes": { + "disable_x509_svid_prefetch": true + } } ] } diff --git a/test/fixture/registration/manager_test_entries.json b/test/fixture/registration/manager_test_entries.json index cff686b43d..fd3edb415f 100644 --- a/test/fixture/registration/manager_test_entries.json +++ b/test/fixture/registration/manager_test_entries.json @@ -95,7 +95,7 @@ "revision_number": 2 } ] - }, + }, "resp5" : { "entries": [ { @@ -124,5 +124,23 @@ "revision_number": 3 } ] + }, + "resp6" : { + "entries": [ + { + "selectors": [ + { + "type": "unix", + "value": "uid:1111" + } + ], + "entry_id": "0001", + "spiffe_id": "spiffe://example.org/spire/agent-1", + "revision_number": 1, + "additional_attributes": { + "disable_x509_svid_prefetch": true + } + } + ] } } diff --git a/test/integration/common b/test/integration/common index b628898af2..8bd06dcb37 100644 --- a/test/integration/common +++ b/test/integration/common @@ -169,6 +169,26 @@ check-svid-count() { fi } +check-debug-info() { + MAXCHECKS=50 + CHECKINTERVAL=1 + + for ((i=1;i<=MAXCHECKS;i++)); do + log-info "check $2 on $1 debug endpoint ($((i)) of $MAXCHECKS max)..." + COUNT=$(docker compose exec -T "$1" /opt/spire/conf/agent/debugclient -testCase "printDebugPage" | jq $(printf '.%s' "$2")) + log-info "$2: ${COUNT}" + if [ "$COUNT" -eq "$3" ]; then + log-info "SVID count of $COUNT matches the expected count of $3" + break + fi + sleep "${CHECKINTERVAL}" + done + + if (( i>MAXCHECKS )); then + fail-now "$2 validation failed" + fi +} + build-mashup-image() { ENVOY_VERSION=$1 ENVOY_IMAGE_TAG="${ENVOY_VERSION}-latest" @@ -242,7 +262,7 @@ download-kind() { elif [ "${ARCH}" = "aarch64" ]; then ARCH=arm64 fi - echo "Ensuring kind version $KINDVERSION is available..." + echo "Ensuring kind version $KINDVERSION is available..." KINDURL="https://github.com/kubernetes-sigs/kind/releases/download/$KINDVERSION/kind-$UNAME-$ARCH" local kind_link_or_path=$1 @@ -265,7 +285,7 @@ download-helm() { ARCH=arm64 fi - echo "Ensuring helm version $HELMVERSION is available..." + echo "Ensuring helm version $HELMVERSION is available..." HELMURL="https://get.helm.sh/helm-${HELMVERSION}-${UNAME}-${ARCH}.tar.gz" local helm_link_or_path=$1 diff --git a/test/integration/suites/entries/02-start-agents b/test/integration/suites/entries/02-start-agents index 3873648d7b..567984453a 100755 --- a/test/integration/suites/entries/02-start-agents +++ b/test/integration/suites/entries/02-start-agents @@ -54,10 +54,23 @@ docker compose exec -T spire-server \ -selector "unix:uid:1003" \ -dns "example.org" +docker compose exec -T spire-server \ + /opt/spire/bin/spire-server entry create \ + -entryID prefetch-disabled \ + -parentID "spiffe://domain.test/cluster/test" \ + -spiffeID "spiffe://domain.test/workload-with-prefetch-disabled" \ + -selector "unix:uid:1004" \ + -disableX509SVIDPrefetch + check-synced-entry "spire-agent-1" "spiffe://domain.test/workload-agent-1" check-synced-entry "spire-agent-2" "spiffe://domain.test/workload-agent-2" for agent in spire-agent-1 spire-agent-2 spire-agent-3; do check-synced-entry ${agent} "spiffe://domain.test/workload-shared" check-synced-entry ${agent} "spiffe://domain.test/workload-with-dns" + check-synced-entry ${agent} "spiffe://domain.test/workload-with-prefetch-disabled" +done + +for agent in spire-agent-1 spire-agent-2; do + check-debug-info ${agent} "cachedX509SvidsCount" 3 done