mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
Consul: agent config updates for WI (#18774)
This changeset makes two changes: * Removes the `consul.use_identity` field from the agent configuration. This behavior is properly covered by the presence of `consul.service_identity` / `consul.task_identity` blocks. * Adds a `consul.task_auth_method` and `consul.service_auth_method` fields to the agent configuration. This allows the cluster administrator to choose specific Consul Auth Method names for their environment, with a reasonable default.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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.
|
||||
|
||||
5
command/agent/testdata/basic.hcl
vendored
5
command/agent/testdata/basic.hcl
vendored
@@ -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
|
||||
|
||||
4
command/agent/testdata/basic.json
vendored
4
command/agent/testdata/basic.json
vendored
@@ -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
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{{
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user