diff --git a/client/allocrunner/consul_hook.go b/client/allocrunner/consul_hook.go index 8fa230786..000568fe2 100644 --- a/client/allocrunner/consul_hook.go +++ b/client/allocrunner/consul_hook.go @@ -16,17 +16,6 @@ import ( structsc "github.com/hashicorp/nomad/nomad/structs/config" ) -const ( - // consulServicesAuthMethodName is the JWT auth method name that has to be - // configured in Consul in order to authenticate Nomad services. - consulServicesAuthMethodName = "nomad-workloads" - - // consulTasksAuthMethodName the JWT auth method name that has to be - // configured in Consul in order to authenticate Nomad tasks (used by - // templates). - consulTasksAuthMethodName = "nomad-tasks" -) - type consulHook struct { alloc *structs.Allocation allocdir *allocdir.AllocDir @@ -79,14 +68,14 @@ func (h *consulHook) Prerun() error { return fmt.Errorf("alloc %v does not have a valid task group", h.alloc.Name) } - if err := h.prepareConsulTokensForServices(tg.Services, tokens); err != nil { + if err := h.prepareConsulTokensForServices(tg.Services, tokens, tg); err != nil { mErr.Errors = append(mErr.Errors, err) } for _, task := range tg.Tasks { - if err := h.prepareConsulTokensForServices(task.Services, tokens); err != nil { + if err := h.prepareConsulTokensForServices(task.Services, tokens, tg); err != nil { mErr.Errors = append(mErr.Errors, err) } - if err := h.prepareConsulTokensForTask(job, task, tg.Name, tokens); err != nil { + if err := h.prepareConsulTokensForTask(task, tg, tokens); err != nil { mErr.Errors = append(mErr.Errors, err) } } @@ -97,31 +86,24 @@ func (h *consulHook) Prerun() error { return mErr.ErrorOrNil() } -func (h *consulHook) prepareConsulTokensForTask(job *structs.Job, task *structs.Task, tgName string, tokens map[string]map[string]string) error { - if task.Consul == nil { - return nil - } +func (h *consulHook) prepareConsulTokensForTask(task *structs.Task, tg *structs.TaskGroup, tokens map[string]map[string]string) error { - consulClusterName := task.Consul.Cluster - - // get consul config - consulConfig := h.consulConfigs[consulClusterName] - - // if UseIdentity is unset of set to false, quit - if consulConfig.UseIdentity == nil || !*consulConfig.UseIdentity { - return nil + clusterName := task.GetConsulClusterName(tg) + consulConfig, ok := h.consulConfigs[clusterName] + if !ok { + return fmt.Errorf("no such consul cluster: %s", clusterName) } // get tokens for alt identities for Consul mErr := multierror.Error{} for _, i := range task.Identities { - if i.Name != fmt.Sprintf("%s_%s", structs.ConsulTaskIdentityNamePrefix, consulClusterName) { + if i.Name != fmt.Sprintf("%s_%s", structs.ConsulTaskIdentityNamePrefix, consulConfig.Name) { continue } ti := *task.IdentityHandle(i) - req, err := h.prepareConsulClientReq(ti, consulTasksAuthMethodName) + req, err := h.prepareConsulClientReq(ti, consulConfig.TaskIdentityAuthMethod) if err != nil { mErr.Errors = append(mErr.Errors, err) continue @@ -136,10 +118,10 @@ func (h *consulHook) prepareConsulTokensForTask(job *structs.Job, task *structs. req[task.Identity.Name] = consul.JWTLoginRequest{ JWT: jwt.JWT, - AuthMethodName: consulTasksAuthMethodName, + AuthMethodName: consulConfig.TaskIdentityAuthMethod, } - if err := h.getConsulTokens(consulClusterName, ti.IdentityName, tokens, req); err != nil { + if err := h.getConsulTokens(consulConfig.Name, ti.IdentityName, tokens, req); err != nil { return err } } @@ -147,7 +129,7 @@ func (h *consulHook) prepareConsulTokensForTask(job *structs.Job, task *structs. return mErr.ErrorOrNil() } -func (h *consulHook) prepareConsulTokensForServices(services []*structs.Service, tokens map[string]map[string]string) error { +func (h *consulHook) prepareConsulTokensForServices(services []*structs.Service, tokens map[string]map[string]string, tg *structs.TaskGroup) error { if len(services) == 0 { return nil } @@ -162,7 +144,14 @@ func (h *consulHook) prepareConsulTokensForServices(services []*structs.Service, continue } - req, err := h.prepareConsulClientReq(*service.IdentityHandle(), consulServicesAuthMethodName) + clusterName := service.GetConsulClusterName(tg) + consulConfig, ok := h.consulConfigs[clusterName] + if !ok { + return fmt.Errorf("no such consul cluster: %s", clusterName) + } + + req, err := h.prepareConsulClientReq( + *service.IdentityHandle(), consulConfig.ServiceIdentityAuthMethod) if err != nil { mErr.Errors = append(mErr.Errors, err) continue diff --git a/command/agent/config_parse_test.go b/command/agent/config_parse_test.go index 303e3d223..1b8bea719 100644 --- a/command/agent/config_parse_test.go +++ b/command/agent/config_parse_test.go @@ -213,29 +213,29 @@ var basicConfig = &Config{ DisableUpdateCheck: pointer.Of(true), DisableAnonymousSignature: true, Consul: &config.ConsulConfig{ - Name: structs.ConsulDefaultCluster, - ServerServiceName: "nomad", - ServerHTTPCheckName: "nomad-server-http-health-check", - ServerSerfCheckName: "nomad-server-serf-health-check", - ServerRPCCheckName: "nomad-server-rpc-health-check", - ClientServiceName: "nomad-client", - ClientHTTPCheckName: "nomad-client-http-health-check", - Addr: "127.0.0.1:9500", - AllowUnauthenticated: &trueValue, - Token: "token1", - Auth: "username:pass", - EnableSSL: &trueValue, - VerifySSL: &trueValue, - CAFile: "/path/to/ca/file", - CertFile: "/path/to/cert/file", - KeyFile: "/path/to/key/file", - ServerAutoJoin: &trueValue, - ClientAutoJoin: &trueValue, - AutoAdvertise: &trueValue, - ChecksUseAdvertise: &trueValue, - Timeout: 5 * time.Second, - TimeoutHCL: "5s", - UseIdentity: &trueValue, + Name: structs.ConsulDefaultCluster, + ServerServiceName: "nomad", + ServerHTTPCheckName: "nomad-server-http-health-check", + ServerSerfCheckName: "nomad-server-serf-health-check", + ServerRPCCheckName: "nomad-server-rpc-health-check", + ClientServiceName: "nomad-client", + ClientHTTPCheckName: "nomad-client-http-health-check", + Addr: "127.0.0.1:9500", + AllowUnauthenticated: &trueValue, + Token: "token1", + Auth: "username:pass", + EnableSSL: &trueValue, + VerifySSL: &trueValue, + CAFile: "/path/to/ca/file", + CertFile: "/path/to/cert/file", + KeyFile: "/path/to/key/file", + ServerAutoJoin: &trueValue, + ClientAutoJoin: &trueValue, + AutoAdvertise: &trueValue, + ChecksUseAdvertise: &trueValue, + Timeout: 5 * time.Second, + TimeoutHCL: "5s", + ServiceIdentityAuthMethod: "nomad-workloads", ServiceIdentity: &config.WorkloadIdentityConfig{ Audience: []string{"consul.io", "nomad.dev"}, Env: pointer.Of(false), @@ -243,6 +243,7 @@ var basicConfig = &Config{ TTL: pointer.Of(1 * time.Hour), TTLHCL: "1h", }, + TaskIdentityAuthMethod: "nomad-tasks", TaskIdentity: &config.WorkloadIdentityConfig{ Audience: []string{"consul.io"}, Env: pointer.Of(true), @@ -253,29 +254,29 @@ var basicConfig = &Config{ }, Consuls: map[string]*config.ConsulConfig{ structs.ConsulDefaultCluster: { - Name: structs.ConsulDefaultCluster, - ServerServiceName: "nomad", - ServerHTTPCheckName: "nomad-server-http-health-check", - ServerSerfCheckName: "nomad-server-serf-health-check", - ServerRPCCheckName: "nomad-server-rpc-health-check", - ClientServiceName: "nomad-client", - ClientHTTPCheckName: "nomad-client-http-health-check", - Addr: "127.0.0.1:9500", - AllowUnauthenticated: &trueValue, - Token: "token1", - Auth: "username:pass", - EnableSSL: &trueValue, - VerifySSL: &trueValue, - CAFile: "/path/to/ca/file", - CertFile: "/path/to/cert/file", - KeyFile: "/path/to/key/file", - ServerAutoJoin: &trueValue, - ClientAutoJoin: &trueValue, - AutoAdvertise: &trueValue, - ChecksUseAdvertise: &trueValue, - Timeout: 5 * time.Second, - TimeoutHCL: "5s", - UseIdentity: &trueValue, + Name: structs.ConsulDefaultCluster, + ServerServiceName: "nomad", + ServerHTTPCheckName: "nomad-server-http-health-check", + ServerSerfCheckName: "nomad-server-serf-health-check", + ServerRPCCheckName: "nomad-server-rpc-health-check", + ClientServiceName: "nomad-client", + ClientHTTPCheckName: "nomad-client-http-health-check", + Addr: "127.0.0.1:9500", + AllowUnauthenticated: &trueValue, + Token: "token1", + Auth: "username:pass", + EnableSSL: &trueValue, + VerifySSL: &trueValue, + CAFile: "/path/to/ca/file", + CertFile: "/path/to/cert/file", + KeyFile: "/path/to/key/file", + ServerAutoJoin: &trueValue, + ClientAutoJoin: &trueValue, + AutoAdvertise: &trueValue, + ChecksUseAdvertise: &trueValue, + Timeout: 5 * time.Second, + TimeoutHCL: "5s", + ServiceIdentityAuthMethod: "nomad-workloads", ServiceIdentity: &config.WorkloadIdentityConfig{ Audience: []string{"consul.io", "nomad.dev"}, Env: pointer.Of(false), @@ -283,6 +284,7 @@ var basicConfig = &Config{ TTL: pointer.Of(1 * time.Hour), TTLHCL: "1h", }, + TaskIdentityAuthMethod: "nomad-tasks", TaskIdentity: &config.WorkloadIdentityConfig{ Audience: []string{"consul.io"}, Env: pointer.Of(true), diff --git a/command/agent/job_endpoint_test.go b/command/agent/job_endpoint_test.go index 364e977a0..d9974d661 100644 --- a/command/agent/job_endpoint_test.go +++ b/command/agent/job_endpoint_test.go @@ -787,16 +787,16 @@ func TestHTTP_JobUpdate_EvalPriority(t *testing.T) { // Make the HTTP request req, err := http.NewRequest(http.MethodPut, "/v1/job/"+*job.ID, buf) - assert.Nil(t, err) + must.NoError(t, err) respW := httptest.NewRecorder() // Make the request obj, err := s.Server.JobSpecificRequest(respW, req) if tc.expectedError { - assert.NotNil(t, err) + must.Error(t, err) return } else { - assert.Nil(t, err) + must.NoError(t, err) } // Check the response @@ -813,7 +813,7 @@ func TestHTTP_JobUpdate_EvalPriority(t *testing.T) { }, } var getResp structs.SingleJobResponse - assert.Nil(t, s.Agent.RPC("Job.GetJob", &getReq, &getResp)) + must.NoError(t, s.Agent.RPC("Job.GetJob", &getReq, &getResp)) assert.NotNil(t, getResp.Job) // Check the evaluation that resulted from the job register. diff --git a/command/agent/testdata/basic.hcl b/command/agent/testdata/basic.hcl index 5a788347b..18bfed34a 100644 --- a/command/agent/testdata/basic.hcl +++ b/command/agent/testdata/basic.hcl @@ -245,13 +245,16 @@ consul { auto_advertise = true checks_use_advertise = true timeout = "5s" - use_identity = true + service_auth_method = "nomad-workloads" + task_auth_method = "nomad-tasks" + service_identity { aud = ["consul.io", "nomad.dev"] env = false file = true ttl = "1h" } + task_identity { aud = ["consul.io"] env = true diff --git a/command/agent/testdata/basic.json b/command/agent/testdata/basic.json index 644b646f5..dbce26ead 100644 --- a/command/agent/testdata/basic.json +++ b/command/agent/testdata/basic.json @@ -167,6 +167,9 @@ "server_rpc_check_name": "nomad-server-rpc-health-check", "server_serf_check_name": "nomad-server-serf-health-check", "server_service_name": "nomad", + "service_auth_method": "nomad-workloads", + "task_auth_method": "nomad-tasks", + "service_identity": { "aud": [ "consul.io", @@ -187,7 +190,6 @@ }, "timeout": "5s", "token": "token1", - "use_identity": true, "verify_ssl": true } ], diff --git a/nomad/config.go b/nomad/config.go index e5f766407..b8577c0ed 100644 --- a/nomad/config.go +++ b/nomad/config.go @@ -495,13 +495,6 @@ func (c *Config) VaultDefaultIdentity() *structs.WorkloadIdentity { return workloadIdentityFromConfig(c.VaultConfig.DefaultIdentity) } -// UseConsulIdentity returns true when Consul workload identity is enabled. -func (c *Config) UseConsulIdentity() bool { - return c.ConsulConfig != nil && - c.ConsulConfig.UseIdentity != nil && - *c.ConsulConfig.UseIdentity -} - // workloadIdentityFromConfig returns a structs.WorkloadIdentity to be used in // a job from a config.WorkloadIdentityConfig parsed from an agent config file. func workloadIdentityFromConfig(widConfig *config.WorkloadIdentityConfig) *structs.WorkloadIdentity { diff --git a/nomad/job_endpoint_hook_consul_ce.go b/nomad/job_endpoint_hook_consul_ce.go index 320856ef3..711de2096 100644 --- a/nomad/job_endpoint_hook_consul_ce.go +++ b/nomad/job_endpoint_hook_consul_ce.go @@ -65,6 +65,9 @@ func (j jobConsulHook) Mutate(job *structs.Job) (*structs.Job, []error, error) { } for _, task := range group.Tasks { + if task.Consul != nil && task.Consul.Cluster == "" { + task.Consul.Cluster = structs.ConsulDefaultCluster + } for _, service := range task.Services { if service.IsConsul() && service.Cluster == "" { service.Cluster = structs.ConsulDefaultCluster diff --git a/nomad/job_endpoint_hook_implicit_identities.go b/nomad/job_endpoint_hook_implicit_identities.go index 570db53ce..d3a219c14 100644 --- a/nomad/job_endpoint_hook_implicit_identities.go +++ b/nomad/job_endpoint_hook_implicit_identities.go @@ -40,17 +40,12 @@ func (h jobImplicitIdentitiesHook) Mutate(job *structs.Job) (*structs.Job, []err } // handleConsulService injects a workload identity to the service if: -// 1. The service uses the Consul provider. -// 2. The server is configured with `consul.use_identity = true` and a -// `consul.service_identity` is provided. +// 1. The service uses the Consul provider, and +// 2. The server is configured with `consul.service_identity` // -// If the service already has an identity it sets the identity name and service -// name values. +// If the service already has an identity the server sets the identity name and +// service name values. func (h jobImplicitIdentitiesHook) handleConsulService(s *structs.Service) { - if !h.srv.config.UseConsulIdentity() { - return - } - if s.Provider != "" && s.Provider != "consul" { return } @@ -77,10 +72,6 @@ func (h jobImplicitIdentitiesHook) handleConsulService(s *structs.Service) { } func (h jobImplicitIdentitiesHook) handleConsulTasks(t *structs.Task) { - if !h.srv.config.UseConsulIdentity() { - return - } - widName := t.Consul.IdentityName() // Use the Consul identity specified in the task if present diff --git a/nomad/job_endpoint_hook_implicit_identities_test.go b/nomad/job_endpoint_hook_implicit_identities_test.go index 8a4b10551..511203f76 100644 --- a/nomad/job_endpoint_hook_implicit_identities_test.go +++ b/nomad/job_endpoint_hook_implicit_identities_test.go @@ -7,13 +7,12 @@ import ( "testing" "github.com/hashicorp/nomad/ci" - "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs/config" "github.com/shoenig/test/must" ) -func Test_jobImplicitIndentitiesHook_Mutate_consul_service(t *testing.T) { +func Test_jobImplicitIdentitiesHook_Mutate_consul_service(t *testing.T) { ci.Parallel(t) testCases := []struct { @@ -23,7 +22,7 @@ func Test_jobImplicitIndentitiesHook_Mutate_consul_service(t *testing.T) { expectedOutputJob *structs.Job }{ { - name: "no mutation when identity is disabled", + name: "no mutation when no service identity is configured", inputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ Services: []*structs.Service{{ @@ -32,31 +31,7 @@ func Test_jobImplicitIndentitiesHook_Mutate_consul_service(t *testing.T) { }}, }, inputConfig: &Config{ - ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(false), - }, - }, - expectedOutputJob: &structs.Job{ - TaskGroups: []*structs.TaskGroup{{ - Services: []*structs.Service{{ - Provider: "consul", - }}, - }}, - }, - }, - { - name: "no mutation when identity is enabled but no service identity is configured", - inputJob: &structs.Job{ - TaskGroups: []*structs.TaskGroup{{ - Services: []*structs.Service{{ - Provider: "consul", - }}, - }}, - }, - inputConfig: &Config{ - ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), - }, + ConsulConfig: &config.ConsulConfig{}, }, expectedOutputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ @@ -77,7 +52,6 @@ func Test_jobImplicitIndentitiesHook_Mutate_consul_service(t *testing.T) { }, inputConfig: &Config{ ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), ServiceIdentity: &config.WorkloadIdentityConfig{ Audience: []string{"consul.io"}, }, @@ -135,7 +109,6 @@ func Test_jobImplicitIndentitiesHook_Mutate_consul_service(t *testing.T) { }, inputConfig: &Config{ ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), ServiceIdentity: &config.WorkloadIdentityConfig{ Audience: []string{"consul.io"}, }, @@ -210,7 +183,6 @@ func Test_jobImplicitIndentitiesHook_Mutate_consul_service(t *testing.T) { }, inputConfig: &Config{ ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), ServiceIdentity: &config.WorkloadIdentityConfig{ Audience: []string{"consul.io"}, }, @@ -258,7 +230,6 @@ func Test_jobImplicitIndentitiesHook_Mutate_consul_service(t *testing.T) { }, inputConfig: &Config{ ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), TaskIdentity: &config.WorkloadIdentityConfig{ Audience: []string{"consul.io"}, }, @@ -279,7 +250,7 @@ func Test_jobImplicitIndentitiesHook_Mutate_consul_service(t *testing.T) { }, }, { - name: "no mutation for templates when identity is enabled but no task identity is configured", + name: "no mutation for templates when no task identity is configured", inputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ Tasks: []*structs.Task{{ @@ -289,36 +260,7 @@ func Test_jobImplicitIndentitiesHook_Mutate_consul_service(t *testing.T) { }}, }, inputConfig: &Config{ - ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), - }, - }, - expectedOutputJob: &structs.Job{ - TaskGroups: []*structs.TaskGroup{{ - Tasks: []*structs.Task{{ - Name: "web-task", - Templates: []*structs.Template{{}}, - }}, - }}, - }, - }, - { - name: "no task mutation for templates when identity is disabled", - inputJob: &structs.Job{ - TaskGroups: []*structs.TaskGroup{{ - Tasks: []*structs.Task{{ - Name: "web-task", - Templates: []*structs.Template{{}}, - }}, - }}, - }, - inputConfig: &Config{ - ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(false), - TaskIdentity: &config.WorkloadIdentityConfig{ - Audience: []string{"consul.io"}, - }, - }, + ConsulConfig: &config.ConsulConfig{}, }, expectedOutputJob: &structs.Job{ TaskGroups: []*structs.TaskGroup{{ diff --git a/nomad/job_endpoint_hooks.go b/nomad/job_endpoint_hooks.go index 5624dfa49..0477185f2 100644 --- a/nomad/job_endpoint_hooks.go +++ b/nomad/job_endpoint_hooks.go @@ -377,27 +377,11 @@ func (v *jobValidate) Validate(job *structs.Job) (warnings []error, err error) { } func (v *jobValidate) validateServiceIdentity(s *structs.Service, parent string) error { - var mErr *multierror.Error - - if s.Identity != nil { - if !v.srv.config.UseConsulIdentity() { - mErr = multierror.Append(mErr, fmt.Errorf( - "Service %s in %s defines an identity but server is not configured to use Consul identities, set use_identity to true in the Consul server configuration", - s.Name, parent, - )) - } - - if s.Identity.Name == "" { - mErr = multierror.Append(mErr, fmt.Errorf("Service %s in %s has an identity with an empty name", s.Name, parent)) - } - } else if v.srv.config.UseConsulIdentity() && v.srv.config.ConsulServiceIdentity() == nil { - mErr = multierror.Append(mErr, fmt.Errorf( - "Service %s in %s expected to have an identity, add an identity block to the service or provide a default using the service_identity block in the server Consul configuration", - s.Name, parent, - )) + if s.Identity != nil && s.Identity.Name == "" { + return fmt.Errorf("Service %s in %s has an identity with an empty name", s.Name, parent) } - return mErr.ErrorOrNil() + return nil } func (v *jobValidate) validateVaultIdentity(t *structs.Task) ([]error, error) { diff --git a/nomad/job_endpoint_hooks_test.go b/nomad/job_endpoint_hooks_test.go index 8d02a63a7..59e0b8d1d 100644 --- a/nomad/job_endpoint_hooks_test.go +++ b/nomad/job_endpoint_hooks_test.go @@ -4,7 +4,6 @@ package nomad import ( - "fmt" "strings" "testing" "time" @@ -34,9 +33,7 @@ func Test_jobValidate_Validate_consul_service(t *testing.T) { Name: "web", }, inputConfig: &Config{ - ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(false), - }, + ConsulConfig: &config.ConsulConfig{}, }, }, { @@ -47,7 +44,6 @@ func Test_jobValidate_Validate_consul_service(t *testing.T) { }, inputConfig: &Config{ ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), ServiceIdentity: &config.WorkloadIdentityConfig{ Audience: []string{"consul.io"}, TTL: pointer.Of(time.Hour), @@ -56,7 +52,7 @@ func Test_jobValidate_Validate_consul_service(t *testing.T) { }, }, { - name: "no error when consul identity is enabled and identity is provided via service", + name: "no error when consul identity is missing and identity is provided via service", inputService: &structs.Service{ Provider: "consul", Name: "web", @@ -70,9 +66,7 @@ func Test_jobValidate_Validate_consul_service(t *testing.T) { }, }, inputConfig: &Config{ - ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), - }, + ConsulConfig: &config.ConsulConfig{}, }, }, { @@ -89,47 +83,12 @@ func Test_jobValidate_Validate_consul_service(t *testing.T) { }, }, inputConfig: &Config{ - ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), - }, + ConsulConfig: &config.ConsulConfig{}, }, expectedWarns: []string{ "identities without an expiration are insecure", }, }, - { - name: "error when consul identity is disabled and service has identity", - inputService: &structs.Service{ - Provider: "consul", - Name: "web", - Identity: &structs.WorkloadIdentity{ - Name: fmt.Sprintf("%s_web", structs.ConsulServiceIdentityNamePrefix), - Audience: []string{"consul.io"}, - File: true, - Env: false, - TTL: time.Hour, - }, - }, - inputConfig: &Config{ - ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(false), - }, - }, - expectedErr: "defines an identity but server is not configured to use Consul identities", - }, - { - name: "error when consul identity is enabled but no service identity is provided", - inputService: &structs.Service{ - Provider: "consul", - Name: "web", - }, - inputConfig: &Config{ - ConsulConfig: &config.ConsulConfig{ - UseIdentity: pointer.Of(true), - }, - }, - expectedErr: "expected to have an identity", - }, } for _, tc := range testCases { diff --git a/nomad/structs/config/consul.go b/nomad/structs/config/consul.go index 3060d3fce..14f51f61b 100644 --- a/nomad/structs/config/consul.go +++ b/nomad/structs/config/consul.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/go-secure-stdlib/listenerutil" "github.com/hashicorp/nomad/helper/pointer" + "github.com/hashicorp/nomad/nomad/structs" ) // ConsulConfig contains the configuration information necessary to @@ -137,36 +138,39 @@ type ConsulConfig struct { // Consul API. If this is unset, then Nomad does not specify a consul namespace. Namespace string `mapstructure:"namespace"` - // UseIdentity tells the server to sign identities for Consul. In Nomad 1.9+ this - // field will be ignored (and treated as though it were set to true). - // - // UseIdentity is set on the server. - UseIdentity *bool `mapstructure:"use_identity"` - // ServiceIdentity is intended to reduce overhead for jobspec authors and make // for graceful upgrades without forcing rewrite of all jobspecs. If set, when a - // job has a service block with the “consul” provider, the Nomad server will sign + // job has a service block with the "consul" provider, the Nomad server will sign // a Workload Identity for that service and add it to the service block. The // client will use this identity rather than the client's Consul token for the // group_service and envoy_bootstrap_hook. // // The name field of the identity is always set to + // "consul-service/${service_task_name}-${service_name}-${service_port}" or // "consul-service/${service_name}-${service_port}". // // ServiceIdentity is set on the server. ServiceIdentity *WorkloadIdentityConfig `mapstructure:"service_identity"` + // ServiceIdentityAuthMethod is the name of the Consul authentication method + // that will be used to login with a Nomad JWT for services. + ServiceIdentityAuthMethod string `mapstructure:"service_auth_method"` + // TaskIdentity is intended to reduce overhead for jobspec authors and make // for graceful upgrades without forcing rewrite of all jobspecs. If set, when a // job has both a template block and a consul block, the Nomad server will sign a // Workload Identity for that task. The client will use this identity rather than // the client's Consul token for the template hook. // - // The name field of the identity is always set to "consul". + // The name field of the identity is always set to "consul_$clusterName". // // TaskIdentity is set on the server. TaskIdentity *WorkloadIdentityConfig `mapstructure:"task_identity"` + // TaskIdentityAuthMethod is the name of the Consul authentication method + // that will be used to login with a Nomad JWT for tasks. + TaskIdentityAuthMethod string `mapstructure:"task_auth_method"` + // ExtraKeysHCL is used by hcl to surface unexpected keys ExtraKeysHCL []string `mapstructure:",unusedKeys" json:"-"` } @@ -177,20 +181,21 @@ type ConsulConfig struct { func DefaultConsulConfig() *ConsulConfig { def := consul.DefaultConfig() return &ConsulConfig{ - Name: "default", - ServerServiceName: "nomad", - ServerHTTPCheckName: "Nomad Server HTTP Check", - ServerSerfCheckName: "Nomad Server Serf Check", - ServerRPCCheckName: "Nomad Server RPC Check", - ClientServiceName: "nomad-client", - ClientHTTPCheckName: "Nomad Client HTTP Check", - AutoAdvertise: pointer.Of(true), - ChecksUseAdvertise: pointer.Of(false), - ServerAutoJoin: pointer.Of(true), - ClientAutoJoin: pointer.Of(true), - AllowUnauthenticated: pointer.Of(true), - Timeout: 5 * time.Second, - UseIdentity: pointer.Of(false), + Name: "default", + ServerServiceName: "nomad", + ServerHTTPCheckName: "Nomad Server HTTP Check", + ServerSerfCheckName: "Nomad Server Serf Check", + ServerRPCCheckName: "Nomad Server RPC Check", + ClientServiceName: "nomad-client", + ClientHTTPCheckName: "Nomad Client HTTP Check", + AutoAdvertise: pointer.Of(true), + ChecksUseAdvertise: pointer.Of(false), + ServerAutoJoin: pointer.Of(true), + ClientAutoJoin: pointer.Of(true), + AllowUnauthenticated: pointer.Of(true), + Timeout: 5 * time.Second, + ServiceIdentityAuthMethod: structs.ConsulServicesDefaultAuthMethodName, + TaskIdentityAuthMethod: structs.ConsulTasksDefaultAuthMethodName, // From Consul api package defaults Addr: def.Address, @@ -293,8 +298,11 @@ func (c *ConsulConfig) Merge(b *ConsulConfig) *ConsulConfig { if b.Namespace != "" { result.Namespace = b.Namespace } - if b.UseIdentity != nil { - result.UseIdentity = pointer.Of(*b.UseIdentity) + if b.ServiceIdentityAuthMethod != "" { + result.ServiceIdentityAuthMethod = b.ServiceIdentityAuthMethod + } + if b.TaskIdentityAuthMethod != "" { + result.TaskIdentityAuthMethod = b.TaskIdentityAuthMethod } if result.ServiceIdentity == nil && b.ServiceIdentity != nil { @@ -383,36 +391,37 @@ func (c *ConsulConfig) Copy() *ConsulConfig { } return &ConsulConfig{ - Name: c.Name, - ServerServiceName: c.ServerServiceName, - ServerHTTPCheckName: c.ServerHTTPCheckName, - ServerSerfCheckName: c.ServerSerfCheckName, - ServerRPCCheckName: c.ServerRPCCheckName, - ClientServiceName: c.ClientServiceName, - ClientHTTPCheckName: c.ClientHTTPCheckName, - Tags: slices.Clone(c.Tags), - AutoAdvertise: c.AutoAdvertise, - ChecksUseAdvertise: c.ChecksUseAdvertise, - Addr: c.Addr, - GRPCAddr: c.GRPCAddr, - Timeout: c.Timeout, - TimeoutHCL: c.TimeoutHCL, - Token: c.Token, - AllowUnauthenticated: c.AllowUnauthenticated, - Auth: c.Auth, - EnableSSL: c.EnableSSL, - ShareSSL: c.ShareSSL, - VerifySSL: c.VerifySSL, - GRPCCAFile: c.GRPCCAFile, - CAFile: c.CAFile, - CertFile: c.CertFile, - KeyFile: c.KeyFile, - ServerAutoJoin: c.ServerAutoJoin, - ClientAutoJoin: c.ClientAutoJoin, - Namespace: c.Namespace, - UseIdentity: c.UseIdentity, - ServiceIdentity: c.ServiceIdentity.Copy(), - TaskIdentity: c.TaskIdentity.Copy(), - ExtraKeysHCL: slices.Clone(c.ExtraKeysHCL), + Name: c.Name, + ServerServiceName: c.ServerServiceName, + ServerHTTPCheckName: c.ServerHTTPCheckName, + ServerSerfCheckName: c.ServerSerfCheckName, + ServerRPCCheckName: c.ServerRPCCheckName, + ClientServiceName: c.ClientServiceName, + ClientHTTPCheckName: c.ClientHTTPCheckName, + Tags: slices.Clone(c.Tags), + AutoAdvertise: c.AutoAdvertise, + ChecksUseAdvertise: c.ChecksUseAdvertise, + Addr: c.Addr, + GRPCAddr: c.GRPCAddr, + Timeout: c.Timeout, + TimeoutHCL: c.TimeoutHCL, + Token: c.Token, + AllowUnauthenticated: c.AllowUnauthenticated, + Auth: c.Auth, + EnableSSL: c.EnableSSL, + ShareSSL: c.ShareSSL, + VerifySSL: c.VerifySSL, + GRPCCAFile: c.GRPCCAFile, + CAFile: c.CAFile, + CertFile: c.CertFile, + KeyFile: c.KeyFile, + ServerAutoJoin: c.ServerAutoJoin, + ClientAutoJoin: c.ClientAutoJoin, + Namespace: c.Namespace, + ServiceIdentity: c.ServiceIdentity.Copy(), + TaskIdentity: c.TaskIdentity.Copy(), + ServiceIdentityAuthMethod: c.ServiceIdentityAuthMethod, + TaskIdentityAuthMethod: c.TaskIdentityAuthMethod, + ExtraKeysHCL: slices.Clone(c.ExtraKeysHCL), } } diff --git a/nomad/structs/config/consul_test.go b/nomad/structs/config/consul_test.go index 7f6c666d2..f89665cba 100644 --- a/nomad/structs/config/consul_test.go +++ b/nomad/structs/config/consul_test.go @@ -93,7 +93,6 @@ func TestConsulConfig_Merge(t *testing.T) { KeyFile: "2", ServerAutoJoin: &yes, ClientAutoJoin: &yes, - UseIdentity: &yes, ServiceIdentity: &WorkloadIdentityConfig{ Name: "test", Audience: []string{"consul.io", "nomad.dev"}, @@ -129,7 +128,6 @@ func TestConsulConfig_Merge(t *testing.T) { KeyFile: "2", ServerAutoJoin: &yes, ClientAutoJoin: &yes, - UseIdentity: &yes, ServiceIdentity: &WorkloadIdentityConfig{ Name: "test", Audience: []string{"consul.io", "nomad.dev"}, diff --git a/nomad/structs/consul.go b/nomad/structs/consul.go index 5b73a710a..8d8e76c92 100644 --- a/nomad/structs/consul.go +++ b/nomad/structs/consul.go @@ -19,6 +19,16 @@ const ( // ConsulTaskIdentityNamePrefix is used in naming identities of consul tasks ConsulTaskIdentityNamePrefix = "consul" + + // ConsulServicesDefaultAuthMethodName is the default JWT auth method name + // that has to be configured in Consul in order to authenticate Nomad + // services. + ConsulServicesDefaultAuthMethodName = "nomad-workloads" + + // ConsulTasksDefaultAuthMethodName the default JWT auth method name that + // has to be configured in Consul in order to authenticate Nomad tasks (used + // by templates). + ConsulTasksDefaultAuthMethodName = "nomad-tasks" ) // Consul represents optional per-group consul configuration. diff --git a/nomad/structs/consul_ce.go b/nomad/structs/consul_ce.go index 0cfd94a57..71cd65460 100644 --- a/nomad/structs/consul_ce.go +++ b/nomad/structs/consul_ce.go @@ -9,3 +9,15 @@ package structs func (c *Consul) GetNamespace() string { return "" } + +// GetConsulClusterName gets the Consul cluster for this task. Only a single +// default cluster is supported in Nomad CE. +func (t *Task) GetConsulClusterName(_ *TaskGroup) string { + return ConsulDefaultCluster +} + +// GetConsulClusterName gets the Consul cluster for this service. Only a single +// default cluster is supported in Nomad CE. +func (s *Service) GetConsulClusterName(_ *TaskGroup) string { + return ConsulDefaultCluster +} diff --git a/nomad/structs/services.go b/nomad/structs/services.go index 695288adc..c3f5970a8 100644 --- a/nomad/structs/services.go +++ b/nomad/structs/services.go @@ -1072,7 +1072,6 @@ func (s *Service) Equal(o *Service) bool { func (s *Service) IsConsul() bool { return s.Provider == ServiceProviderConsul || s.Provider == "" - } // ConsulConnect represents a Consul Connect jobspec block.