From 868aba57bb33b3332b16ed618da7841d32ffb456 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Wed, 27 Sep 2023 15:53:28 -0300 Subject: [PATCH] vault: update identity name to start with `vault_` (#18591) * vault: update identity name to start with `vault_` In the original proposal, workload identities used to derive Vault tokens were expected to be called just `vault`. But in order to support multiple Vault clusters it is necessary to associate identities with specific Vault cluster configuration. This commit implements a new proposal to have Vault identities named as `vault_`. --- .../taskrunner/task_runner_linux_test.go | 2 +- .../taskrunner/task_runner_test.go | 16 ++++----- client/config/config.go | 2 +- client/config/config_ce.go | 3 +- client/config/testing.go | 3 +- client/fingerprint/vault.go | 3 +- client/fingerprint/vault_test.go | 2 +- command/agent/command.go | 2 +- command/agent/config.go | 4 +-- command/agent/config_parse.go | 5 +-- command/agent/config_parse_test.go | 35 ++++++++++--------- command/agent/config_test.go | 12 +++---- command/agent/job_endpoint_test.go | 2 +- nomad/config.go | 2 +- .../job_endpoint_hook_implicit_identities.go | 16 ++++----- ..._endpoint_hook_implicit_identities_test.go | 23 ++++++++---- nomad/job_endpoint_hook_vault_ce_test.go | 4 +-- nomad/job_endpoint_hooks.go | 22 ++++++++---- nomad/job_endpoint_hooks_test.go | 10 +++--- nomad/structs/config/vault.go | 3 +- nomad/structs/diff_test.go | 8 ++--- nomad/structs/structs.go | 9 +++++ nomad/structs/vault.go | 6 ++++ nomad/structs/workload_id.go | 9 +++-- testutil/vault.go | 3 +- website/content/docs/configuration/vault.mdx | 3 +- 26 files changed, 124 insertions(+), 85 deletions(-) diff --git a/client/allocrunner/taskrunner/task_runner_linux_test.go b/client/allocrunner/taskrunner/task_runner_linux_test.go index b425e426a..4f4ec8e29 100644 --- a/client/allocrunner/taskrunner/task_runner_linux_test.go +++ b/client/allocrunner/taskrunner/task_runner_linux_test.go @@ -37,7 +37,7 @@ func TestTaskRunner_DisableFileForVaultToken_UpgradePath(t *testing.T) { handler := func(*structs.Allocation, []string) (map[string]string, error) { return map[string]string{task.Name: token}, nil } - vc, err := vaultclient.NewMockVaultClient("default") + vc, err := vaultclient.NewMockVaultClient(structs.VaultDefaultCluster) must.NoError(t, err) vaultClient := vc.(*vaultclient.MockVaultClient) vaultClient.DeriveTokenFn = handler diff --git a/client/allocrunner/taskrunner/task_runner_test.go b/client/allocrunner/taskrunner/task_runner_test.go index d3a3aa679..8fc5e42ae 100644 --- a/client/allocrunner/taskrunner/task_runner_test.go +++ b/client/allocrunner/taskrunner/task_runner_test.go @@ -1606,7 +1606,7 @@ func TestTaskRunner_BlockForVaultToken(t *testing.T) { return map[string]string{task.Name: token}, nil } - vc, err := vaultclient.NewMockVaultClient("default") + vc, err := vaultclient.NewMockVaultClient(structs.VaultDefaultCluster) must.NoError(t, err) vaultClient := vc.(*vaultclient.MockVaultClient) vaultClient.DeriveTokenFn = handler @@ -1695,7 +1695,7 @@ func TestTaskRunner_DisableFileForVaultToken(t *testing.T) { handler := func(*structs.Allocation, []string) (map[string]string, error) { return map[string]string{task.Name: token}, nil } - vc, err := vaultclient.NewMockVaultClient("default") + vc, err := vaultclient.NewMockVaultClient(structs.VaultDefaultCluster) must.NoError(t, err) vaultClient := vc.(*vaultclient.MockVaultClient) vaultClient.DeriveTokenFn = handler @@ -1748,7 +1748,7 @@ func TestTaskRunner_DeriveToken_Retry(t *testing.T) { count++ return nil, structs.NewRecoverableError(fmt.Errorf("Want a retry"), true) } - vc, err := vaultclient.NewMockVaultClient("default") + vc, err := vaultclient.NewMockVaultClient(structs.VaultDefaultCluster) must.NoError(t, err) vaultClient := vc.(*vaultclient.MockVaultClient) vaultClient.DeriveTokenFn = handler @@ -1818,7 +1818,7 @@ func TestTaskRunner_DeriveToken_Unrecoverable(t *testing.T) { task.Vault = &structs.Vault{Policies: []string{"default"}} // Error the token derivation - vc, err := vaultclient.NewMockVaultClient("default") + vc, err := vaultclient.NewMockVaultClient(structs.VaultDefaultCluster) must.NoError(t, err) vaultClient := vc.(*vaultclient.MockVaultClient) vaultClient.SetDeriveTokenError( @@ -2138,7 +2138,7 @@ func TestTaskRunner_RestartSignalTask_NotRunning(t *testing.T) { <-waitCh return map[string]string{task.Name: "1234"}, nil } - vc, err := vaultclient.NewMockVaultClient("default") + vc, err := vaultclient.NewMockVaultClient(structs.VaultDefaultCluster) must.NoError(t, err) vaultClient := vc.(*vaultclient.MockVaultClient) vaultClient.DeriveTokenFn = handler @@ -2354,7 +2354,7 @@ func TestTaskRunner_Template_NewVaultToken(t *testing.T) { } task.Vault = &structs.Vault{Policies: []string{"default"}} - vc, err := vaultclient.NewMockVaultClient("default") + vc, err := vaultclient.NewMockVaultClient(structs.VaultDefaultCluster) must.NoError(t, err) vaultClient := vc.(*vaultclient.MockVaultClient) @@ -2435,7 +2435,7 @@ func TestTaskRunner_VaultManager_Restart(t *testing.T) { ChangeMode: structs.VaultChangeModeRestart, } - vc, err := vaultclient.NewMockVaultClient("default") + vc, err := vaultclient.NewMockVaultClient(structs.VaultDefaultCluster) vaultClient := vc.(*vaultclient.MockVaultClient) must.NoError(t, err) @@ -2511,7 +2511,7 @@ func TestTaskRunner_VaultManager_Signal(t *testing.T) { ChangeMode: structs.VaultChangeModeSignal, ChangeSignal: "SIGUSR1", } - vc, err := vaultclient.NewMockVaultClient("default") + vc, err := vaultclient.NewMockVaultClient(structs.VaultDefaultCluster) must.NoError(t, err) vaultClient := vc.(*vaultclient.MockVaultClient) diff --git a/client/config/config.go b/client/config/config.go index 2c3ed1ab6..9ed6d8815 100644 --- a/client/config/config.go +++ b/client/config/config.go @@ -816,7 +816,7 @@ func DefaultConfig() *Config { cfg.ConsulConfigs = map[string]*structsc.ConsulConfig{ "default": cfg.ConsulConfig} cfg.VaultConfigs = map[string]*structsc.VaultConfig{ - "default": cfg.VaultConfig} + structs.VaultDefaultCluster: cfg.VaultConfig} return cfg } diff --git a/client/config/config_ce.go b/client/config/config_ce.go index 10fd2b910..2df577051 100644 --- a/client/config/config_ce.go +++ b/client/config/config_ce.go @@ -7,6 +7,7 @@ package config import ( "github.com/hashicorp/go-hclog" + "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs/config" structsc "github.com/hashicorp/nomad/nomad/structs/config" ) @@ -22,7 +23,7 @@ func (c *Config) GetVaultConfigs(logger hclog.Logger) map[string]*structsc.Vault logger.Warn("multiple Vault configurations are only supported in Nomad Enterprise") } - return map[string]*structsc.VaultConfig{"default": c.VaultConfig} + return map[string]*structsc.VaultConfig{structs.VaultDefaultCluster: c.VaultConfig} } // GetConsulConfigs returns the set of Consul configurations the fingerprint needs diff --git a/client/config/testing.go b/client/config/testing.go index 8382549f4..156b5b54b 100644 --- a/client/config/testing.go +++ b/client/config/testing.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/helper/testlog" "github.com/hashicorp/nomad/nomad/mock" + "github.com/hashicorp/nomad/nomad/structs" testing "github.com/mitchellh/go-testing-interface" ) @@ -69,7 +70,7 @@ func TestClientConfig(t testing.T) (*Config, func()) { conf.CgroupParent = "testing.slice" conf.VaultConfig.Enabled = pointer.Of(false) - conf.VaultConfigs["default"].Enabled = pointer.Of(false) + conf.VaultConfigs[structs.VaultDefaultCluster].Enabled = pointer.Of(false) conf.DevMode = true // Loosen GC threshold diff --git a/client/fingerprint/vault.go b/client/fingerprint/vault.go index a46aea36f..5427ba370 100644 --- a/client/fingerprint/vault.go +++ b/client/fingerprint/vault.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/helper/useragent" + "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs/config" vapi "github.com/hashicorp/vault/api" ) @@ -92,7 +93,7 @@ func (f *VaultFingerprint) fingerprintImpl(cfg *config.VaultConfig, resp *Finger return nil } - if cfg.Name == "default" { + if cfg.Name == structs.VaultDefaultCluster { resp.AddAttribute("vault.accessible", strconv.FormatBool(true)) resp.AddAttribute("vault.version", strings.TrimPrefix(status.Version, "Vault ")) resp.AddAttribute("vault.cluster_id", status.ClusterID) diff --git a/client/fingerprint/vault_test.go b/client/fingerprint/vault_test.go index d5a82f272..8b033c015 100644 --- a/client/fingerprint/vault_test.go +++ b/client/fingerprint/vault_test.go @@ -67,7 +67,7 @@ func TestVaultFingerprint(t *testing.T) { // Reset the nextCheck time for testing purposes, or we won't pick up the // change until the next period, up to 2min from now vfp := fp.(*VaultFingerprint) - vfp.states["default"].nextCheck = time.Now() + vfp.states[structs.VaultDefaultCluster].nextCheck = time.Now() err = fp.Fingerprint(request, &response) if err != nil { diff --git a/command/agent/command.go b/command/agent/command.go index eec257cc0..59055b7ec 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -78,7 +78,7 @@ func (c *Command) readConfig() *Config { Audit: &config.AuditConfig{}, Reporting: &config.ReportingConfig{}, } - cmdConfig.Vaults = map[string]*config.VaultConfig{"default": cmdConfig.Vault} + cmdConfig.Vaults = map[string]*config.VaultConfig{structs.VaultDefaultCluster: cmdConfig.Vault} cmdConfig.Consuls = map[string]*config.ConsulConfig{"default": cmdConfig.Consul} flags := flag.NewFlagSet("agent", flag.ContinueOnError) diff --git a/command/agent/config.go b/command/agent/config.go index bebc0358c..150d762c2 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -1377,7 +1377,7 @@ func DefaultConfig() *Config { } cfg.Consuls = map[string]*config.ConsulConfig{"default": cfg.Consul} - cfg.Vaults = map[string]*config.VaultConfig{"default": cfg.Vault} + cfg.Vaults = map[string]*config.VaultConfig{structs.VaultDefaultCluster: cfg.Vault} return cfg } @@ -1552,7 +1552,7 @@ func (c *Config) Merge(b *Config) *Config { // Apply the Vault Configurations and overwrite the default Vault config result.Vaults = mergeVaultConfigs(result.Vaults, b.Vaults) - result.Vault = result.Vaults["default"] + result.Vault = result.Vaults[structs.VaultDefaultCluster] // Apply the UI Configuration if result.UI == nil && b.UI != nil { diff --git a/command/agent/config_parse.go b/command/agent/config_parse.go index 1105e74ef..9848ac8ee 100644 --- a/command/agent/config_parse.go +++ b/command/agent/config_parse.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/hcl/hcl/ast" client "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/helper" + "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs/config" "github.com/mitchellh/mapstructure" ) @@ -403,7 +404,7 @@ func parseVaults(c *Config, list *ast.ObjectList) error { return err } if v.Name == "" { - v.Name = "default" + v.Name = structs.VaultDefaultCluster } if exist, ok := c.Vaults[v.Name]; ok { c.Vaults[v.Name] = exist.Merge(v) @@ -434,7 +435,7 @@ func parseVaults(c *Config, list *ast.ObjectList) error { } } - c.Vault = c.Vaults["default"] + c.Vault = c.Vaults[structs.VaultDefaultCluster] return nil } diff --git a/command/agent/config_parse_test.go b/command/agent/config_parse_test.go index 9926fecd1..f649f37e4 100644 --- a/command/agent/config_parse_test.go +++ b/command/agent/config_parse_test.go @@ -292,7 +292,7 @@ var basicConfig = &Config{ }, }, Vault: &config.VaultConfig{ - Name: "default", + Name: structs.VaultDefaultCluster, Addr: "127.0.0.1:9500", AllowUnauthenticated: &trueValue, ConnectionRetryIntv: config.DefaultVaultConnectRetryIntv, @@ -316,8 +316,8 @@ var basicConfig = &Config{ }, }, Vaults: map[string]*config.VaultConfig{ - "default": { - Name: "default", + structs.VaultDefaultCluster: { + Name: structs.VaultDefaultCluster, Addr: "127.0.0.1:9500", AllowUnauthenticated: &trueValue, ConnectionRetryIntv: config.DefaultVaultConnectRetryIntv, @@ -612,7 +612,7 @@ func TestConfig_Parse(t *testing.T) { Consul: config.DefaultConsulConfig(), Consuls: map[string]*config.ConsulConfig{"default": config.DefaultConsulConfig()}, Vault: config.DefaultVaultConfig(), - Vaults: map[string]*config.VaultConfig{"default": config.DefaultVaultConfig()}, + Vaults: map[string]*config.VaultConfig{structs.VaultDefaultCluster: config.DefaultVaultConfig()}, Autopilot: config.DefaultAutopilotConfig(), Reporting: config.DefaultReporting(), } @@ -655,7 +655,7 @@ func (c *Config) addDefaults() { } if c.Vault == nil { c.Vault = config.DefaultVaultConfig() - c.Vaults = map[string]*config.VaultConfig{"default": c.Vault} + c.Vaults = map[string]*config.VaultConfig{structs.VaultDefaultCluster: c.Vault} } if c.Telemetry == nil { c.Telemetry = &Telemetry{} @@ -814,14 +814,14 @@ var sample0 = &Config{ }, }, Vault: &config.VaultConfig{ - Name: "default", + Name: structs.VaultDefaultCluster, Enabled: pointer.Of(true), Role: "nomad-cluster", Addr: "http://host.example.com:8200", }, Vaults: map[string]*config.VaultConfig{ - "default": { - Name: "default", + structs.VaultDefaultCluster: { + Name: structs.VaultDefaultCluster, Enabled: pointer.Of(true), Role: "nomad-cluster", Addr: "http://host.example.com:8200", @@ -930,14 +930,14 @@ var sample1 = &Config{ }, }, Vault: &config.VaultConfig{ - Name: "default", + Name: structs.VaultDefaultCluster, Enabled: pointer.Of(true), Role: "nomad-cluster", Addr: "http://host.example.com:8200", }, Vaults: map[string]*config.VaultConfig{ - "default": { - Name: "default", + structs.VaultDefaultCluster: { + Name: structs.VaultDefaultCluster, Enabled: pointer.Of(true), Role: "nomad-cluster", Addr: "http://host.example.com:8200", @@ -1061,29 +1061,29 @@ func TestConfig_MultipleVault(t *testing.T) { // verify the default Vault config is set from the list cfg := DefaultConfig() - must.Eq(t, "default", cfg.Vault.Name) + must.Eq(t, structs.VaultDefaultCluster, cfg.Vault.Name) must.Equal(t, config.DefaultVaultConfig(), cfg.Vault) must.Nil(t, cfg.Vault.Enabled) // unset must.Eq(t, "https://vault.service.consul:8200", cfg.Vault.Addr) must.Eq(t, "", cfg.Vault.Token) must.MapLen(t, 1, cfg.Vaults) - must.Equal(t, cfg.Vault, cfg.Vaults["default"]) - must.True(t, cfg.Vault == cfg.Vaults["default"]) // must be same pointer + must.Equal(t, cfg.Vault, cfg.Vaults[structs.VaultDefaultCluster]) + must.True(t, cfg.Vault == cfg.Vaults[structs.VaultDefaultCluster]) // must be same pointer // merge in the user's configuration fc, err := LoadConfig("testdata/basic.hcl") must.NoError(t, err) cfg = cfg.Merge(fc) - must.Eq(t, "default", cfg.Vault.Name) + must.Eq(t, structs.VaultDefaultCluster, cfg.Vault.Name) must.NotNil(t, cfg.Vault.Enabled, must.Sprint("override should set to non-nil")) must.False(t, *cfg.Vault.Enabled) must.Eq(t, "127.0.0.1:9500", cfg.Vault.Addr) must.Eq(t, "12345", cfg.Vault.Token) must.MapLen(t, 1, cfg.Vaults) - must.Equal(t, cfg.Vault, cfg.Vaults["default"]) + must.Equal(t, cfg.Vault, cfg.Vaults[structs.VaultDefaultCluster]) // add an extra Vault config and override fields in the default fc, err = LoadConfig("testdata/extra-vault.hcl") @@ -1091,13 +1091,14 @@ func TestConfig_MultipleVault(t *testing.T) { cfg = cfg.Merge(fc) - must.Eq(t, "default", cfg.Vault.Name) + must.Eq(t, structs.VaultDefaultCluster, cfg.Vault.Name) must.True(t, *cfg.Vault.Enabled) must.Eq(t, "127.0.0.1:9500", cfg.Vault.Addr) must.Eq(t, "abracadabra", cfg.Vault.Token) must.MapLen(t, 3, cfg.Vaults) must.Equal(t, cfg.Vault, cfg.Vaults["default"]) + must.Equal(t, cfg.Vault, cfg.Vaults[structs.VaultDefaultCluster]) must.Eq(t, "alternate", cfg.Vaults["alternate"].Name) must.True(t, *cfg.Vaults["alternate"].Enabled) diff --git a/command/agent/config_test.go b/command/agent/config_test.go index b7c91ac33..e2e4340e1 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -187,7 +187,7 @@ func TestConfig_Merge(t *testing.T) { "Access-Control-Allow-Origin": "*", }, Vault: &config.VaultConfig{ - Name: "default", + Name: structs.VaultDefaultCluster, Token: "1", AllowUnauthenticated: &falseValue, TaskTokenTTL: "1", @@ -200,8 +200,8 @@ func TestConfig_Merge(t *testing.T) { TLSServerName: "1", }, Vaults: map[string]*config.VaultConfig{ - "default": { - Name: "default", + structs.VaultDefaultCluster: { + Name: structs.VaultDefaultCluster, Token: "1", AllowUnauthenticated: &falseValue, TaskTokenTTL: "1", @@ -435,7 +435,7 @@ func TestConfig_Merge(t *testing.T) { "Access-Control-Allow-Methods": "GET, POST, OPTIONS", }, Vault: &config.VaultConfig{ - Name: "default", + Name: structs.VaultDefaultCluster, Token: "2", AllowUnauthenticated: &trueValue, TaskTokenTTL: "2", @@ -448,8 +448,8 @@ func TestConfig_Merge(t *testing.T) { TLSServerName: "2", }, Vaults: map[string]*config.VaultConfig{ - "default": { - Name: "default", + structs.VaultDefaultCluster: { + Name: structs.VaultDefaultCluster, Token: "2", AllowUnauthenticated: &trueValue, TaskTokenTTL: "2", diff --git a/command/agent/job_endpoint_test.go b/command/agent/job_endpoint_test.go index 326c51d07..535932e79 100644 --- a/command/agent/job_endpoint_test.go +++ b/command/agent/job_endpoint_test.go @@ -3214,7 +3214,7 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) { Vault: &structs.Vault{ Role: "nomad-task", Namespace: "ns1", - Cluster: "default", + Cluster: structs.VaultDefaultCluster, Policies: []string{"a", "b", "c"}, Env: true, DisableFile: false, diff --git a/nomad/config.go b/nomad/config.go index c0d8c0dac..9e7fd2e47 100644 --- a/nomad/config.go +++ b/nomad/config.go @@ -623,7 +623,7 @@ func DefaultConfig() *Config { } c.ConsulConfigs = map[string]*config.ConsulConfig{"default": c.ConsulConfig} - c.VaultConfigs = map[string]*config.VaultConfig{"default": c.VaultConfig} + c.VaultConfigs = map[string]*config.VaultConfig{structs.VaultDefaultCluster: c.VaultConfig} // Enable all known schedulers by default c.EnabledSchedulers = make([]string, 0, len(scheduler.BuiltinSchedulers)) diff --git a/nomad/job_endpoint_hook_implicit_identities.go b/nomad/job_endpoint_hook_implicit_identities.go index 35f7ba6c6..76e6978c1 100644 --- a/nomad/job_endpoint_hook_implicit_identities.go +++ b/nomad/job_endpoint_hook_implicit_identities.go @@ -12,7 +12,6 @@ import ( const ( consulServiceIdentityNamePrefix = "consul-service" consulTaskIdentityNamePrefix = "consul" - vaultIdentityName = "vault" ) // jobImplicitIdentitiesHook adds implicit `identity` blocks for external @@ -111,24 +110,21 @@ func (h jobImplicitIdentitiesHook) handleConsulTasks(t *structs.Task, consul *st // 1. The task has a Vault block. // 2. The server is configured with `vault.use_identity = true` and a // `vault.default_identity` is provided. -// -// If the task already has an identity named `vault` it sets the identity name -// to the expected value. func (h jobImplicitIdentitiesHook) handleVault(t *structs.Task) { if !h.srv.config.UseVaultIdentity() || t.Vault == nil { return } // Use the Vault identity specified in the task. - for _, wid := range t.Identities { - if wid.Name == vaultIdentityName { - return - } + vaultWIDName := t.Vault.IdentityName() + vaultWID := t.GetIdentity(vaultWIDName) + if vaultWID != nil { + return } // If the task doesn't specify an identity for Vault, fallback to the // default identity defined in the server configuration. - vaultWID := h.srv.config.VaultDefaultIdentity() + vaultWID = h.srv.config.VaultDefaultIdentity() if vaultWID == nil { // If no identity is found skip inject the implicit identity and // fallback to the legacy flow. @@ -136,6 +132,6 @@ func (h jobImplicitIdentitiesHook) handleVault(t *structs.Task) { } // Set the expected identity name and inject it into the task. - vaultWID.Name = vaultIdentityName + vaultWID.Name = vaultWIDName t.Identities = append(t.Identities, vaultWID) } diff --git a/nomad/job_endpoint_hook_implicit_identities_test.go b/nomad/job_endpoint_hook_implicit_identities_test.go index 88e791194..1fc80646e 100644 --- a/nomad/job_endpoint_hook_implicit_identities_test.go +++ b/nomad/job_endpoint_hook_implicit_identities_test.go @@ -404,10 +404,12 @@ func Test_jobImplicitIndentitiesHook_Mutate_vault(t *testing.T) { TaskGroups: []*structs.TaskGroup{{ Tasks: []*structs.Task{{ Identities: []*structs.WorkloadIdentity{{ - Name: "vault", + Name: "vault_default", Audience: []string{"vault.io"}, }}, - Vault: &structs.Vault{}, + Vault: &structs.Vault{ + Cluster: structs.VaultDefaultCluster, + }, }}, }}, }, @@ -423,10 +425,12 @@ func Test_jobImplicitIndentitiesHook_Mutate_vault(t *testing.T) { TaskGroups: []*structs.TaskGroup{{ Tasks: []*structs.Task{{ Identities: []*structs.WorkloadIdentity{{ - Name: "vault", + Name: "vault_default", Audience: []string{"vault.io"}, }}, - Vault: &structs.Vault{}, + Vault: &structs.Vault{ + Cluster: structs.VaultDefaultCluster, + }, }}, }}, }, @@ -436,7 +440,9 @@ func Test_jobImplicitIndentitiesHook_Mutate_vault(t *testing.T) { inputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ Tasks: []*structs.Task{{ - Vault: &structs.Vault{}, + Vault: &structs.Vault{ + Cluster: structs.VaultDefaultCluster, + }, }}, }}, }, @@ -452,10 +458,12 @@ func Test_jobImplicitIndentitiesHook_Mutate_vault(t *testing.T) { TaskGroups: []*structs.TaskGroup{{ Tasks: []*structs.Task{{ Identities: []*structs.WorkloadIdentity{{ - Name: "vault", + Name: "vault_default", Audience: []string{"vault.io"}, }}, - Vault: &structs.Vault{}, + Vault: &structs.Vault{ + Cluster: structs.VaultDefaultCluster, + }, }}, }}, }, @@ -468,6 +476,7 @@ func Test_jobImplicitIndentitiesHook_Mutate_vault(t *testing.T) { config: tc.inputConfig, }} actualJob, actualWarnings, actualError := impl.Mutate(tc.inputJob) + must.Eq(t, tc.expectedOutputJob, actualJob) must.NoError(t, actualError) must.Nil(t, actualWarnings) diff --git a/nomad/job_endpoint_hook_vault_ce_test.go b/nomad/job_endpoint_hook_vault_ce_test.go index 8063b1a47..027e55be8 100644 --- a/nomad/job_endpoint_hook_vault_ce_test.go +++ b/nomad/job_endpoint_hook_vault_ce_test.go @@ -29,14 +29,14 @@ func TestJobEndpointHook_VaultCE(t *testing.T) { // create two different Vault blocks and assign to clusters job.TaskGroups[0].Tasks = append(job.TaskGroups[0].Tasks, job.TaskGroups[0].Tasks[0].Copy()) - job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{Cluster: "default"} + job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{Cluster: structs.VaultDefaultCluster} job.TaskGroups[0].Tasks[1].Name = "web2" job.TaskGroups[0].Tasks[1].Vault = &structs.Vault{Cluster: "infra"} hook := jobVaultHook{srv} _, _, err := hook.Mutate(job) must.NoError(t, err) - must.Eq(t, "default", job.TaskGroups[0].Tasks[0].Vault.Cluster) + must.Eq(t, structs.VaultDefaultCluster, job.TaskGroups[0].Tasks[0].Vault.Cluster) must.Eq(t, "infra", job.TaskGroups[0].Tasks[1].Vault.Cluster) // skipping over the rest of Validate b/c it requires an actual diff --git a/nomad/job_endpoint_hooks.go b/nomad/job_endpoint_hooks.go index f3eedf7a2..3afc16ee3 100644 --- a/nomad/job_endpoint_hooks.go +++ b/nomad/job_endpoint_hooks.go @@ -6,6 +6,7 @@ package nomad import ( "fmt" "sort" + "strings" "github.com/dustin/go-humanize" "github.com/hashicorp/go-multierror" @@ -237,7 +238,7 @@ func (jobImpliedConstraints) Mutate(j *structs.Job) (*structs.Job, []error, erro // fingerprint or non-default cluster are allowed well before we get here, so no // need to split out the behavior to ENT-specific code. func vaultConstraintFn(vault *structs.Vault) *structs.Constraint { - if vault.Cluster != "default" && vault.Cluster != "" { + if vault.Cluster != structs.VaultDefaultCluster && vault.Cluster != "" { return &structs.Constraint{ LTarget: fmt.Sprintf("${attr.vault.%s.version}", vault.Cluster), RTarget: ">= 0.6.1", @@ -404,17 +405,24 @@ func (v *jobValidate) validateVaultIdentity(t *structs.Task) ([]error, error) { var warnings []error hasVault := t.Vault != nil - hasTaskWID := t.GetIdentity(vaultIdentityName) != nil hasDefaultWID := v.srv.config.VaultDefaultIdentity() != nil + vaultWIDs := []string{} + for _, wid := range t.Identities { + if strings.HasPrefix(wid.Name, structs.WorkloadIdentityVaultPrefix) { + vaultWIDs = append(vaultWIDs, wid.Name) + } + } + hasVaultWID := len(vaultWIDs) > 0 + useIdentity := hasVault && v.srv.config.UseVaultIdentity() - hasWID := hasTaskWID || hasDefaultWID + hasWID := hasVaultWID || hasDefaultWID if useIdentity { if !hasWID { mErr = multierror.Append(mErr, fmt.Errorf( "Task %s expected to have a Vault identity, add an identity block called %s or provide a default using the default_identity block in the server Vault configuration", - t.Name, vaultIdentityName, + t.Name, t.Vault.IdentityName(), )) } @@ -428,15 +436,15 @@ func (v *jobValidate) validateVaultIdentity(t *structs.Task) ([]error, error) { mErr = multierror.Append(mErr, fmt.Errorf("Task %s has a Vault block with an empty list of policies", t.Name)) } - if hasTaskWID { + for _, wid := range vaultWIDs { if !v.srv.config.UseVaultIdentity() { warnings = append(warnings, fmt.Errorf( "Task %s has an identity called %s but server is not configured to use Vault identities, set use_identity to true in the Vault server configuration", - t.Name, vaultIdentityName, + t.Name, wid, )) } if !hasVault { - warnings = append(warnings, fmt.Errorf("Task %s has an identity called %s but no vault block", t.Name, vaultIdentityName)) + warnings = append(warnings, fmt.Errorf("Task %s has an identity called %s but no vault block", t.Name, wid)) } } diff --git a/nomad/job_endpoint_hooks_test.go b/nomad/job_endpoint_hooks_test.go index 2a1e44b45..792995f7d 100644 --- a/nomad/job_endpoint_hooks_test.go +++ b/nomad/job_endpoint_hooks_test.go @@ -204,7 +204,7 @@ func Test_jobValidate_Validate_vault(t *testing.T) { name: "no error when vault identity is enabled and identity is provided via task", inputTaskVault: &structs.Vault{}, inputTaskIdentities: []*structs.WorkloadIdentity{{ - Name: vaultIdentityName, + Name: "vault_default", Audience: []string{"vault.io"}, TTL: time.Hour, }}, @@ -251,7 +251,7 @@ func Test_jobValidate_Validate_vault(t *testing.T) { Policies: []string{"nomad-workload"}, }, inputTaskIdentities: []*structs.WorkloadIdentity{{ - Name: vaultIdentityName, + Name: "vault_default", Audience: []string{"vault.io"}, TTL: time.Hour, }}, @@ -259,14 +259,14 @@ func Test_jobValidate_Validate_vault(t *testing.T) { UseIdentity: pointer.Of(false), }, expectedWarns: []string{ - "has an identity called vault but server is not configured to use Vault identities", + "has an identity called vault_default but server is not configured to use Vault identities", }, }, { name: "warn when vault identity is provided but task does not have vault block", inputTaskVault: nil, inputTaskIdentities: []*structs.WorkloadIdentity{{ - Name: vaultIdentityName, + Name: "vault_default", Audience: []string{"vault.io"}, TTL: time.Hour, }}, @@ -274,7 +274,7 @@ func Test_jobValidate_Validate_vault(t *testing.T) { UseIdentity: pointer.Of(true), }, expectedWarns: []string{ - "has an identity called vault but no vault block", + "has an identity called vault_default but no vault block", }, }, } diff --git a/nomad/structs/config/vault.go b/nomad/structs/config/vault.go index 965198955..53238cb87 100644 --- a/nomad/structs/config/vault.go +++ b/nomad/structs/config/vault.go @@ -95,7 +95,8 @@ type VaultConfig struct { UseIdentity *bool `mapstructure:"use_identity"` // DefaultIdentity is the default workload identity configuration used when - // a job has a `vault` block but no `identity` named "vault". + // a job has a `vault` block but no `identity` named "vault_", where + // matches this block `name` parameter. DefaultIdentity *WorkloadIdentityConfig `mapstructure:"default_identity"` // Deprecated fields. diff --git a/nomad/structs/diff_test.go b/nomad/structs/diff_test.go index 69a5e97c3..eb256c10e 100644 --- a/nomad/structs/diff_test.go +++ b/nomad/structs/diff_test.go @@ -7717,7 +7717,7 @@ func TestTaskDiff(t *testing.T) { Vault: &Vault{ Role: "nomad-task", Namespace: "ns1", - Cluster: "default", + Cluster: VaultDefaultCluster, Policies: []string{"foo", "bar"}, Env: true, DisableFile: true, @@ -7729,7 +7729,7 @@ func TestTaskDiff(t *testing.T) { Vault: &Vault{ Role: "nomad-task", Namespace: "ns1", - Cluster: "default", + Cluster: VaultDefaultCluster, Policies: []string{"bar", "baz"}, Env: true, DisableFile: true, @@ -7759,8 +7759,8 @@ func TestTaskDiff(t *testing.T) { { Type: DiffTypeNone, Name: "Cluster", - Old: "default", - New: "default", + Old: VaultDefaultCluster, + New: VaultDefaultCluster, }, { Type: DiffTypeNone, diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index b54cc5c17..a82f64e62 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -9976,6 +9976,12 @@ type Vault struct { ChangeSignal string } +// IdentityName returns the name of the workload identity to be used to access +// this Vault cluster. +func (v *Vault) IdentityName() string { + return fmt.Sprintf("%s%s", WorkloadIdentityVaultPrefix, v.Cluster) +} + func (v *Vault) Equal(o *Vault) bool { if v == nil || o == nil { return v == o @@ -10013,6 +10019,9 @@ func (v *Vault) Copy() *Vault { } func (v *Vault) Canonicalize() { + // The Vault cluster name is canonicalized in the jobVaultHook during job + // registration because the value may be read from the server config. + if v.ChangeSignal != "" { v.ChangeSignal = strings.ToUpper(v.ChangeSignal) } diff --git a/nomad/structs/vault.go b/nomad/structs/vault.go index 596b3340d..8dc6b6583 100644 --- a/nomad/structs/vault.go +++ b/nomad/structs/vault.go @@ -11,6 +11,12 @@ import ( "github.com/mitchellh/mapstructure" ) +const ( + // VaultDefaultCluster is the name used for the Vault cluster that doesn't + // have a name. + VaultDefaultCluster = "default" +) + // VaultTokenData represents some of the fields returned in the Data map of the // sercret returned by the Vault API when doing a token lookup request. type VaultTokenData struct { diff --git a/nomad/structs/workload_id.go b/nomad/structs/workload_id.go index 2a1a33c79..107e3f6f6 100644 --- a/nomad/structs/workload_id.go +++ b/nomad/structs/workload_id.go @@ -5,6 +5,7 @@ package structs import ( "fmt" + "regexp" "slices" "time" @@ -19,6 +20,10 @@ const ( // WorkloadIdentityDefaultAud is the audience of the default identity. WorkloadIdentityDefaultAud = "nomadproject.io" + // WorkloadIdentityVaultPrefix is the name prefix of workload identities + // used to derive Vault tokens. + WorkloadIdentityVaultPrefix = "vault_" + // WIRejectionReasonMissingAlloc is the WorkloadIdentityRejection.Reason // returned when an allocation longer exists. This may be due to the alloc // being GC'd or the job being updated. @@ -36,9 +41,7 @@ const ( var ( // validIdentityName is used to validate workload identity Name fields. Must // be safe to use in filenames. - // - // Reuse validNamespaceName to save a bit of memory. - validIdentityName = validNamespaceName + validIdentityName = regexp.MustCompile("^[a-zA-Z0-9-_]{1,128}$") ) // WorkloadIdentity is the jobspec block which determines if and how a workload diff --git a/testutil/vault.go b/testutil/vault.go index a1363915c..a51764888 100644 --- a/testutil/vault.go +++ b/testutil/vault.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/nomad/helper/testlog" "github.com/hashicorp/nomad/helper/useragent" "github.com/hashicorp/nomad/helper/uuid" + "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs/config" vapi "github.com/hashicorp/vault/api" testing "github.com/mitchellh/go-testing-interface" @@ -72,7 +73,7 @@ func NewTestVaultFromPath(t testing.T, binary string) *TestVault { RootToken: token, Client: client, Config: &config.VaultConfig{ - Name: "default", + Name: structs.VaultDefaultCluster, Enabled: &enable, Token: token, Addr: http, diff --git a/website/content/docs/configuration/vault.mdx b/website/content/docs/configuration/vault.mdx index ed72d4ddb..832648131 100644 --- a/website/content/docs/configuration/vault.mdx +++ b/website/content/docs/configuration/vault.mdx @@ -140,7 +140,8 @@ agents with [`server.enabled`] set to `true`. - `default_identity` ([Identity](#default_identity-parameters): nil) - Specifies the default workload identity configuration to use when a task with a `vault` block does not specify an [`identity`][jobspec_identity] block - named `vault`. + named `vault_`, where `` matches the value of this `vault` block + [`name`](#name) parameter. ### Deprecated Parameters