config: add TTL to agent identity config (#18457)

Add support for identity token TTL in agent configuration fields such as
Consul `service_identity` and `template_identity`.

Co-authored-by: Michael Schurter <mschurter@hashicorp.com>
This commit is contained in:
Luiz Aoqui
2023-09-12 11:13:09 -03:00
committed by GitHub
parent 2e1974a574
commit 82372fecb8
8 changed files with 115 additions and 24 deletions

View File

@@ -72,6 +72,29 @@ func ParseConfigFile(path string) (*Config, error) {
return nil, fmt.Errorf("failed to decode HCL file %s: %w", path, err)
}
// Re-parse the file to extract the multiple Vault configurations, which we
// need to parse by hand because we don't have a label on the block
root, err := hcl.Parse(buf.String())
if err != nil {
return nil, fmt.Errorf("failed to parse HCL file %s: %w", path, err)
}
list, ok := root.Node.(*ast.ObjectList)
if !ok {
return nil, fmt.Errorf("error parsing: root should be an object")
}
matches := list.Filter("vault")
if len(matches.Items) > 0 {
if err := parseVaults(c, matches); err != nil {
return nil, fmt.Errorf("error parsing 'vault': %w", err)
}
}
matches = list.Filter("consul")
if len(matches.Items) > 0 {
if err := parseConsuls(c, matches); err != nil {
return nil, fmt.Errorf("error parsing 'consul': %w", err)
}
}
// convert strings to time.Durations
tds := []durationConversionMap{
{"gc_interval", &c.Client.GCInterval, &c.Client.GCIntervalHCL, nil},
@@ -152,6 +175,30 @@ func ParseConfigFile(path string) (*Config, error) {
},
}
// Parse durations for Consul and Vault config blocks if provided.
//
// Since the map of multiple cluster configuration contains a pointer to
// the default block we don't need to parse it directly.
for name, consulConfig := range c.Consuls {
if consulConfig.ServiceIdentity != nil {
tds = append(tds, durationConversionMap{
fmt.Sprintf("consuls.%s.service_identity.ttl", name), nil, &consulConfig.ServiceIdentity.TTLHCL,
func(d *time.Duration) {
consulConfig.ServiceIdentity.TTL = d
},
})
}
if consulConfig.TemplateIdentity != nil {
tds = append(tds, durationConversionMap{
fmt.Sprintf("consuls.%s.template_identity.ttl", name), nil, &consulConfig.TemplateIdentity.TTLHCL,
func(d *time.Duration) {
consulConfig.TemplateIdentity.TTL = d
},
})
}
}
// Add enterprise audit sinks for time.Duration parsing
for i, sink := range c.Audit.Sinks {
tds = append(tds, durationConversionMap{
@@ -164,28 +211,6 @@ func ParseConfigFile(path string) (*Config, error) {
return nil, err
}
// Re-parse the file to extract the multiple Vault configurations, which we
// need to parse by hand because we don't have a label on the block
root, err := hcl.Parse(buf.String())
if err != nil {
return nil, fmt.Errorf("failed to parse HCL file %s: %w", path, err)
}
list, ok := root.Node.(*ast.ObjectList)
if !ok {
return nil, fmt.Errorf("error parsing: root should be an object")
}
matches := list.Filter("vault")
if len(matches.Items) > 0 {
if err := parseVaults(c, matches); err != nil {
return nil, fmt.Errorf("error parsing 'vault': %w", err)
}
}
matches = list.Filter("consul")
if len(matches.Items) > 0 {
if err := parseConsuls(c, matches); err != nil {
return nil, fmt.Errorf("error parsing 'consul': %w", err)
}
}
// report unexpected keys
err = extraKeys(c)
if err != nil {

View File

@@ -240,11 +240,15 @@ var basicConfig = &Config{
Audience: []string{"consul.io", "nomad.dev"},
Env: pointer.Of(false),
File: pointer.Of(true),
TTL: pointer.Of(1 * time.Hour),
TTLHCL: "1h",
},
TemplateIdentity: &config.WorkloadIdentityConfig{
Audience: []string{"consul.io"},
Env: pointer.Of(true),
File: pointer.Of(false),
TTL: pointer.Of(2 * time.Hour),
TTLHCL: "2h",
},
},
Consuls: map[string]*config.ConsulConfig{
@@ -276,11 +280,15 @@ var basicConfig = &Config{
Audience: []string{"consul.io", "nomad.dev"},
Env: pointer.Of(false),
File: pointer.Of(true),
TTL: pointer.Of(1 * time.Hour),
TTLHCL: "1h",
},
TemplateIdentity: &config.WorkloadIdentityConfig{
Audience: []string{"consul.io"},
Env: pointer.Of(true),
File: pointer.Of(false),
TTL: pointer.Of(2 * time.Hour),
TTLHCL: "2h",
},
},
},

View File

@@ -248,11 +248,13 @@ consul {
aud = ["consul.io", "nomad.dev"]
env = false
file = true
ttl = "1h"
}
template_identity {
aud = ["consul.io"]
env = true
file = false
ttl = "2h"
}
}

View File

@@ -173,7 +173,8 @@
"nomad.dev"
],
"env": false,
"file": true
"file": true,
"ttl": "1h"
},
"ssl": true,
"template_identity": {
@@ -181,7 +182,8 @@
"consul.io"
],
"env": true,
"file": false
"file": false,
"ttl": "2h"
},
"timeout": "5s",
"token": "token1",

View File

@@ -508,6 +508,9 @@ func workloadIdentityFromConfig(widConfig *config.WorkloadIdentityConfig) *struc
if widConfig.File != nil {
wid.File = *widConfig.File
}
if widConfig.TTL != nil {
wid.TTL = *widConfig.TTL
}
return wid
}

View File

@@ -99,6 +99,7 @@ func TestConsulConfig_Merge(t *testing.T) {
Audience: []string{"consul.io", "nomad.dev"},
Env: pointer.Of(false),
File: pointer.Of(true),
TTL: pointer.Of(2 * time.Hour),
},
ExtraKeysHCL: []string{"b", "2"},
}
@@ -134,6 +135,7 @@ func TestConsulConfig_Merge(t *testing.T) {
Audience: []string{"consul.io", "nomad.dev"},
Env: pointer.Of(false),
File: pointer.Of(true),
TTL: pointer.Of(2 * time.Hour),
},
ExtraKeysHCL: []string{"a", "1"}, // not merged
}

View File

@@ -5,6 +5,7 @@ package config
import (
"slices"
"time"
"github.com/hashicorp/go-set"
"github.com/hashicorp/nomad/helper/pointer"
@@ -31,6 +32,11 @@ type WorkloadIdentityConfig struct {
// File writes the Workload Identity into the Task's secrets directory
// if set.
File *bool `mapstructure:"file"`
// TTL is used to determine the expiration of the credentials created for
// this identity (eg the JWT "exp" claim).
TTL *time.Duration `mapstructure:"-"`
TTLHCL string `mapstructure:"ttl" json:"-"`
}
func (wi *WorkloadIdentityConfig) Copy() *WorkloadIdentityConfig {
@@ -68,6 +74,12 @@ func (wi *WorkloadIdentityConfig) Equal(other *WorkloadIdentityConfig) bool {
if !pointer.Eq(wi.File, other.File) {
return false
}
if !pointer.Eq(wi.TTL, other.TTL) {
return false
}
if wi.TTLHCL != other.TTLHCL {
return false
}
return true
}
@@ -94,6 +106,10 @@ func (wi *WorkloadIdentityConfig) Merge(other *WorkloadIdentityConfig) *Workload
result.Env = pointer.Merge(result.Env, other.Env)
result.File = pointer.Merge(result.File, other.File)
result.TTL = pointer.Merge(result.TTL, other.TTL)
if other.TTLHCL != "" {
result.TTLHCL = other.TTLHCL
}
return result
}

View File

@@ -5,6 +5,7 @@ package config
import (
"testing"
"time"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/pointer"
@@ -19,6 +20,7 @@ func TestWorkloadIdentityConfig_Copy(t *testing.T) {
Audience: []string{"aud"},
Env: pointer.Of(true),
File: pointer.Of(false),
TTL: pointer.Of(time.Hour),
}
// Verify Copy() returns the same values but different pointer.
@@ -31,6 +33,7 @@ func TestWorkloadIdentityConfig_Copy(t *testing.T) {
clone.Audience = []string{"aud", "clone"}
clone.Env = pointer.Of(false)
clone.File = pointer.Of(true)
clone.TTL = pointer.Of(time.Second)
must.NotEq(t, original, clone)
must.NotEqOp(t, original, clone)
@@ -52,12 +55,14 @@ func TestWorkloadIdentityConfig_Equal(t *testing.T) {
Audience: []string{"aud"},
Env: pointer.Of(true),
File: pointer.Of(false),
TTL: pointer.Of(time.Hour),
},
b: &WorkloadIdentityConfig{
Name: "test",
Audience: []string{"aud"},
Env: pointer.Of(true),
File: pointer.Of(false),
TTL: pointer.Of(time.Hour),
},
expectEq: true,
},
@@ -121,6 +126,16 @@ func TestWorkloadIdentityConfig_Equal(t *testing.T) {
},
expectEq: false,
},
{
name: "different ttl",
a: &WorkloadIdentityConfig{
TTL: pointer.Of(time.Hour),
},
b: &WorkloadIdentityConfig{
TTL: pointer.Of(time.Minute),
},
expectEq: false,
},
}
for _, tc := range testCases {
@@ -152,6 +167,7 @@ func TestWorkloadIdentityConfig_Merge(t *testing.T) {
Audience: []string{"aud"},
Env: pointer.Of(true),
File: pointer.Of(false),
TTL: pointer.Of(time.Hour),
},
},
{
@@ -164,6 +180,7 @@ func TestWorkloadIdentityConfig_Merge(t *testing.T) {
Audience: []string{"aud", "other"},
Env: pointer.Of(true),
File: pointer.Of(false),
TTL: pointer.Of(time.Hour),
},
},
{
@@ -176,6 +193,7 @@ func TestWorkloadIdentityConfig_Merge(t *testing.T) {
Audience: []string{"aud"},
Env: pointer.Of(false),
File: pointer.Of(false),
TTL: pointer.Of(time.Hour),
},
},
{
@@ -188,6 +206,20 @@ func TestWorkloadIdentityConfig_Merge(t *testing.T) {
Audience: []string{"aud"},
Env: pointer.Of(true),
File: pointer.Of(true),
TTL: pointer.Of(time.Hour),
},
},
{
name: "merge ttl",
other: &WorkloadIdentityConfig{
TTL: pointer.Of(time.Second),
},
expected: &WorkloadIdentityConfig{
Name: "test",
Audience: []string{"aud"},
Env: pointer.Of(true),
File: pointer.Of(false),
TTL: pointer.Of(time.Second),
},
},
}
@@ -199,6 +231,7 @@ func TestWorkloadIdentityConfig_Merge(t *testing.T) {
Audience: []string{"aud"},
Env: pointer.Of(true),
File: pointer.Of(false),
TTL: pointer.Of(time.Hour),
}
got := original.Merge(tc.other)
must.Eq(t, tc.expected, got)