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:
Tim Gross
2023-10-17 14:42:14 -04:00
committed by GitHub
parent ac56855f07
commit d0957eb109
16 changed files with 185 additions and 289 deletions

View File

@@ -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

View File

@@ -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),

View File

@@ -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.

View File

@@ -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

View File

@@ -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
}
],

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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{{

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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),
}
}

View File

@@ -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"},

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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.