Skip to content

Commit

Permalink
Issue spiffe#2700: Adds support for X509 and JWT specific SVID TTLs (s…
Browse files Browse the repository at this point in the history
…piffe#3445)

* Adds support for X509 and JWT specific SVID TTLs

Fixes spiffe#2700

This change adds support for X509 and JWT specific SVID TTLs in each of the following places
 * Default values in spire-server configuration. Similar to the existing TTL value, if provided then it must be >= 0. A value of 0 is considered 'unset', meaning there is no default.
 * Entry records in the database and API

During Entry creation and update
 * If the API call contains a non-zero X509SvidTtl value then that will be stored, else the config default x509SvidTtl value is used
 * If the API call contains a non-zero JWTSvidTtl value then that will stored, else the config default jwtSvidTtl value is used

During X509-SVID creation
 * If the API call contains a non-zero TTL value then that is used, else
 * If the stored record contains a non-zero X509SvidTtl value then that will be used, else
 * If the stored record contains a non-zero TTL value then that will be used,
 * The hard-coded default X509SvidTTL value will be used

During JWT-SVID creation
 * If the API call contains a non-zero TTL value then that is used, else
 * If the stored record contains a non-zero JWTSvidTtl value then that will be used, else
 * If the stored record contains a non-zero TTL value then that will be used,
 * The hard-coded default JWTSvidTTL value will be used

X509SvidTtl and JwtSvidTtl will be considered during the following cases
 * All must be valid with-respect-to the configured CA TTL - they are all part of the min/max validation checks
 * Entry sorting now includes each of X509SvidTtl and JwtSvidTtl

Signed-off-by: Dennis Gove <dgove1@bloomberg.net>
  • Loading branch information
dennisgove authored and stevend-uber committed Oct 13, 2023
1 parent b5240ec commit 50ba284
Show file tree
Hide file tree
Showing 39 changed files with 1,606 additions and 745 deletions.
53 changes: 44 additions & 9 deletions cmd/spire-server/cli/entry/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,17 @@ type createCommand struct {
// Workload spiffeID
spiffeID string

// TTL for certificates issued to this workload
// TTL for x509 and JWT SVIDs issued to this workload, unless type specific TTLs are set.
// This field is deprecated in favor of the x509SVIDTTL and jwtSVIDTTL fields and will be
// removed in a future release.
ttl int

// TTL for x509 SVIDs issued to this workload
x509SVIDTTL int

// TTL for JWT SVIDs issued to this workload
jwtSVIDTTL int

// List of SPIFFE IDs of trust domains the registration entry is federated with
federatesWith StringsFlag

Expand Down Expand Up @@ -75,7 +83,9 @@ func (*createCommand) Synopsis() string {
func (c *createCommand) AppendFlags(f *flag.FlagSet) {
f.StringVar(&c.parentID, "parentID", "", "The SPIFFE ID of this record's parent")
f.StringVar(&c.spiffeID, "spiffeID", "", "The SPIFFE ID that this record represents")
f.IntVar(&c.ttl, "ttl", 0, "The lifetime, in seconds, for SVIDs issued based on this registration entry")
f.IntVar(&c.ttl, "ttl", 0, "The lifetime, in seconds, for SVIDs issued based on this registration entry. This flag is deprecated in favor of x509SVIDTTL and jwtSVIDTTL and will be removed in a future version")
f.IntVar(&c.x509SVIDTTL, "x509SVIDTTL", 0, "The lifetime, in seconds, for x509-SVIDs issued based on this registration entry. Overrides ttl flag")
f.IntVar(&c.jwtSVIDTTL, "jwtSVIDTTL", 0, "The lifetime, in seconds, for JWT-SVIDs issued based on this registration entry. Overrides ttl flag")
f.StringVar(&c.path, "data", "", "Path to a file containing registration JSON (optional). If set to '-', read the JSON from stdin.")
f.Var(&c.selectors, "selector", "A colon-delimited type:value selector. Can be used more than once")
f.Var(&c.federatesWith, "federatesWith", "SPIFFE ID of a trust domain to federate with. Can be used more than once")
Expand Down Expand Up @@ -156,6 +166,18 @@ func (c *createCommand) validate() (err error) {
return errors.New("a positive TTL is required")
}

if c.x509SVIDTTL < 0 {
return errors.New("a positive x509-SVID TTL is required")
}

if c.jwtSVIDTTL < 0 {
return errors.New("a positive JWT-SVID TTL is required")
}

if c.ttl > 0 && (c.x509SVIDTTL > 0 || c.jwtSVIDTTL > 0) {
return errors.New("use x509SVIDTTL and jwtSVIDTTL flags or the deprecated ttl flag")
}

return nil
}

Expand All @@ -172,13 +194,26 @@ func (c *createCommand) parseConfig() ([]*types.Entry, error) {
}

e := &types.Entry{
ParentId: parentID,
SpiffeId: spiffeID,
Ttl: int32(c.ttl),
Downstream: c.downstream,
ExpiresAt: c.entryExpiry,
DnsNames: c.dnsNames,
StoreSvid: c.storeSVID,
ParentId: parentID,
SpiffeId: spiffeID,
Downstream: c.downstream,
ExpiresAt: c.entryExpiry,
DnsNames: c.dnsNames,
StoreSvid: c.storeSVID,
X509SvidTtl: int32(c.x509SVIDTTL),
JwtSvidTtl: int32(c.jwtSVIDTTL),
}

// c.ttl is deprecated but usable if the new c.x509Svid field is not used.
// c.ttl should not be used to set the jwtSVIDTTL value because the previous
// behavior was to have a hard-coded 5 minute JWT TTL no matter what the value
// of ttl was set to.
// validate(...) ensures that either the new fields or the deprecated field is
// used, but never a mixture.
//
// https://github.com/spiffe/spire/issues/2700
if e.X509SvidTtl == 0 {
e.X509SvidTtl = int32(c.ttl)
}

selectors := []*types.Selector{}
Expand Down
176 changes: 144 additions & 32 deletions cmd/spire-server/cli/entry/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,35 @@ func TestCreate(t *testing.T) {
{Type: "zebra", Value: "zebra:2000"},
{Type: "alpha", Value: "alpha:2000"},
},
Ttl: 60,
X509SvidTtl: 60,
JwtSvidTtl: 30,
FederatesWith: []string{"spiffe://domaina.test", "spiffe://domainb.test"},
Admin: true,
ExpiresAt: 1552410266,
DnsNames: []string{"unu1000", "ung1000"},
Downstream: true,
StoreSvid: true,
},
Status: &types.Status{
Code: int32(codes.OK),
Message: "OK",
},
},
},
}

fakeRespOKFromCmd2 := &entryv1.BatchCreateEntryResponse{
Results: []*entryv1.BatchCreateEntryResponse_Result{
{
Entry: &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: "zebra", Value: "zebra:2000"},
{Type: "alpha", Value: "alpha:2000"},
},
X509SvidTtl: 60,
FederatesWith: []string{"spiffe://domaina.test", "spiffe://domainb.test"},
Admin: true,
ExpiresAt: 1552410266,
Expand All @@ -56,12 +84,13 @@ func TestCreate(t *testing.T) {
Results: []*entryv1.BatchCreateEntryResponse_Result{
{
Entry: &types.Entry{
Id: "entry-id-1",
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/Blog"},
ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenBlog"},
Selectors: []*types.Selector{{Type: "unix", Value: "uid:1111"}},
Ttl: 200,
Admin: true,
Id: "entry-id-1",
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/Blog"},
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,
},
Status: &types.Status{
Code: int32(codes.OK),
Expand All @@ -70,11 +99,12 @@ func TestCreate(t *testing.T) {
},
{
Entry: &types.Entry{
Id: "entry-id-2",
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/Database"},
ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenDatabase"},
Selectors: []*types.Selector{{Type: "unix", Value: "uid:1111"}},
Ttl: 200,
Id: "entry-id-2",
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/Database"},
ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenDatabase"},
Selectors: []*types.Selector{{Type: "unix", Value: "uid:1111"}},
X509SvidTtl: 200,
JwtSvidTtl: 30,
},
Status: &types.Status{
Code: int32(codes.OK),
Expand All @@ -90,8 +120,9 @@ func TestCreate(t *testing.T) {
{Type: "type", Value: "key1:value"},
{Type: "type", Value: "key2:value"},
},
StoreSvid: true,
Ttl: 200,
StoreSvid: true,
X509SvidTtl: 200,
JwtSvidTtl: 30,
},
Status: &types.Status{
Code: int32(codes.OK),
Expand Down Expand Up @@ -147,6 +178,21 @@ func TestCreate(t *testing.T) {
args: []string{"-selector", "unix", "-parentID", "spiffe://example.org/parent", "-spiffeID", "spiffe://example.org/workload", "-ttl", "-10"},
expErr: "Error: a positive TTL is required\n",
},
{
name: "Invalid TTL and X509SvidTtl",
args: []string{"-selector", "unix", "-parentID", "spiffe://example.org/parent", "-spiffeID", "spiffe://example.org/workload", "-ttl", "10", "-x509SVIDTTL", "20"},
expErr: "Error: use x509SVIDTTL and jwtSVIDTTL flags or the deprecated ttl flag\n",
},
{
name: "Invalid TTL and JwtSvidTtl",
args: []string{"-selector", "unix", "-parentID", "spiffe://example.org/parent", "-spiffeID", "spiffe://example.org/workload", "-ttl", "10", "-jwtSVIDTTL", "20"},
expErr: "Error: use x509SVIDTTL and jwtSVIDTTL flags or the deprecated ttl flag\n",
},
{
name: "Invalid TTL and both X509SvidTtl and JwtSvidTtl",
args: []string{"-selector", "unix", "-parentID", "spiffe://example.org/parent", "-spiffeID", "spiffe://example.org/workload", "-ttl", "10", "-x509SVIDTTL", "20", "-jwtSVIDTTL", "30"},
expErr: "Error: use x509SVIDTTL and jwtSVIDTTL flags or the deprecated ttl flag\n",
},
{
name: "Federated node entries",
args: []string{"-selector", "unix", "-spiffeID", "spiffe://example.org/workload", "-node", "-federatesWith", "spiffe://another.org"},
Expand All @@ -172,7 +218,8 @@ func TestCreate(t *testing.T) {
"-parentID", "spiffe://example.org/parent",
"-selector", "zebra:zebra:2000",
"-selector", "alpha:alpha:2000",
"-ttl", "60",
"-x509SVIDTTL", "60",
"-jwtSVIDTTL", "30",
"-federatesWith", "spiffe://domaina.test",
"-federatesWith", "spiffe://domainb.test",
"-admin",
Expand All @@ -191,7 +238,8 @@ func TestCreate(t *testing.T) {
{Type: "zebra", Value: "zebra:2000"},
{Type: "alpha", Value: "alpha:2000"},
},
Ttl: 60,
X509SvidTtl: 60,
JwtSvidTtl: 30,
FederatesWith: []string{"spiffe://domaina.test", "spiffe://domainb.test"},
Admin: true,
ExpiresAt: 1552410266,
Expand All @@ -207,7 +255,64 @@ SPIFFE ID : spiffe://example.org/workload
Parent ID : spiffe://example.org/parent
Revision : 0
Downstream : true
TTL : 60
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
`, time.Unix(1552410266, 0).UTC()),
},
{
name: "Create succeeds using deprecated command line arguments",
args: []string{
"-spiffeID", "spiffe://example.org/workload",
"-parentID", "spiffe://example.org/parent",
"-selector", "zebra:zebra:2000",
"-selector", "alpha:alpha:2000",
"-ttl", "60",
"-federatesWith", "spiffe://domaina.test",
"-federatesWith", "spiffe://domainb.test",
"-admin",
"-entryExpiry", "1552410266",
"-dns", "unu1000",
"-dns", "ung1000",
"-downstream",
"-storeSVID",
},
expReq: &entryv1.BatchCreateEntryRequest{
Entries: []*types.Entry{
{
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/workload"},
ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/parent"},
Selectors: []*types.Selector{
{Type: "zebra", Value: "zebra:2000"},
{Type: "alpha", Value: "alpha:2000"},
},
X509SvidTtl: 60,
FederatesWith: []string{"spiffe://domaina.test", "spiffe://domainb.test"},
Admin: true,
ExpiresAt: 1552410266,
DnsNames: []string{"unu1000", "ung1000"},
Downstream: true,
StoreSvid: true,
},
},
},
fakeResp: fakeRespOKFromCmd2,
expOut: 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
Expand All @@ -228,17 +333,19 @@ StoreSvid : true
expReq: &entryv1.BatchCreateEntryRequest{
Entries: []*types.Entry{
{
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/Blog"},
ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenBlog"},
Selectors: []*types.Selector{{Type: "unix", Value: "uid:1111"}},
Ttl: 200,
Admin: true,
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/Blog"},
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,
},
{
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/Database"},
ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenDatabase"},
Selectors: []*types.Selector{{Type: "unix", Value: "uid:1111"}},
Ttl: 200,
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/Database"},
ParentId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/join_token/TokenDatabase"},
Selectors: []*types.Selector{{Type: "unix", Value: "uid:1111"}},
X509SvidTtl: 200,
JwtSvidTtl: 30,
},
{
SpiffeId: &types.SPIFFEID{TrustDomain: "example.org", Path: "/storesvid"},
Expand All @@ -247,8 +354,9 @@ StoreSvid : true
{Type: "type", Value: "key1:value"},
{Type: "type", Value: "key2:value"},
},
Ttl: 200,
StoreSvid: true,
X509SvidTtl: 200,
JwtSvidTtl: 30,
StoreSvid: true,
},
},
},
Expand All @@ -257,22 +365,25 @@ StoreSvid : true
SPIFFE ID : spiffe://example.org/Blog
Parent ID : spiffe://example.org/spire/agent/join_token/TokenBlog
Revision : 0
TTL : 200
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
TTL : 200
X509-SVID TTL : 200
JWT-SVID TTL : 30
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
TTL : 200
X509-SVID TTL : 200
JWT-SVID TTL : 30
Selector : type:key1:value
Selector : type:key2:value
StoreSvid : true
Expand All @@ -295,7 +406,8 @@ Entry ID : (none)
SPIFFE ID : spiffe://example.org/already-exist
Parent ID : spiffe://example.org/spire/server
Revision : 0
TTL : default
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : unix:uid:1
Error: failed to create one or more entries
Expand Down
12 changes: 8 additions & 4 deletions cmd/spire-server/cli/entry/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,8 @@ func getPrintedEntry(idx int) string {
SPIFFE ID : spiffe://example.org/son
Parent ID : spiffe://example.org/father
Revision : 0
TTL : default
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : foo:bar
`
Expand All @@ -424,7 +425,8 @@ Selector : foo:bar
SPIFFE ID : spiffe://example.org/daughter
Parent ID : spiffe://example.org/father
Revision : 0
TTL : default
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : bar:baz
Selector : foo:bar
Expand All @@ -434,7 +436,8 @@ Selector : foo:bar
SPIFFE ID : spiffe://example.org/daughter
Parent ID : spiffe://example.org/mother
Revision : 0
TTL : default
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : bar:baz
Selector : baz:bat
FederatesWith : spiffe://domain.test
Expand All @@ -445,7 +448,8 @@ FederatesWith : spiffe://domain.test
SPIFFE ID : spiffe://example.org/son
Parent ID : spiffe://example.org/mother
Revision : 0
TTL : default
X509-SVID TTL : default
JWT-SVID TTL : default
Expiration time : %s
Selector : baz:bat
Expand Down
Loading

0 comments on commit 50ba284

Please sign in to comment.