Skip to content

Commit

Permalink
config: fix identity config for Consul service (#18363)
Browse files Browse the repository at this point in the history
Rename the agent configuraion for workload identity to
`WorkloadIdentityConfig` to make its use more explicit and remove the
`ServiceName` field since it is never expected to be defined in a
configuration file.

Also update the job mutation to inject a service identity following
these rules:

1. Don't inject identity if `consul.use_identity` is false.
2. Don't inject identity if `consul.service_identity` is not specified.
3. Don't inject identity if service provider is not `consul`.
4. Set name and service name if the service specifies an identity.
5. Inject `consul.service_identity` if service does not specify an
   identity.
  • Loading branch information
lgfa29 authored Aug 31, 2023
1 parent 05c3322 commit 7466496
Show file tree
Hide file tree
Showing 15 changed files with 904 additions and 239 deletions.
2 changes: 1 addition & 1 deletion api/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ type Service struct {
TaggedAddresses map[string]string `hcl:"tagged_addresses,block"`
TaskName string `mapstructure:"task" hcl:"task,optional"`
OnUpdate string `mapstructure:"on_update" hcl:"on_update,optional"`
Identity *WorkloadIdentity `hcl:"identity,optional"`
Identity *WorkloadIdentity `hcl:"identity,block"`

// Provider defines which backend system provides the service registration,
// either "consul" (default) or "nomad".
Expand Down
8 changes: 4 additions & 4 deletions command/agent/config_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ func ParseConfigFile(path string) (*Config, error) {
ACL: &ACLConfig{},
Audit: &config.AuditConfig{},
Consul: &config.ConsulConfig{
ServiceIdentity: &config.WorkloadIdentity{},
TemplateIdentity: &config.WorkloadIdentity{},
ServiceIdentity: &config.WorkloadIdentityConfig{},
TemplateIdentity: &config.WorkloadIdentityConfig{},
},
Consuls: map[string]*config.ConsulConfig{},
Autopilot: &config.AutopilotConfig{},
Expand Down Expand Up @@ -418,7 +418,7 @@ func parseConsuls(c *Config, list *ast.ObjectList) error {
return err
}

var serviceIdentity config.WorkloadIdentity
var serviceIdentity config.WorkloadIdentityConfig
if err := mapstructure.WeakDecode(m, &serviceIdentity); err != nil {
return err
}
Expand All @@ -432,7 +432,7 @@ func parseConsuls(c *Config, list *ast.ObjectList) error {
return err
}

var templateIdentity config.WorkloadIdentity
var templateIdentity config.WorkloadIdentityConfig
if err := mapstructure.WeakDecode(m, &templateIdentity); err != nil {
return err
}
Expand Down
24 changes: 12 additions & 12 deletions command/agent/config_parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,15 @@ var basicConfig = &Config{
Timeout: 5 * time.Second,
TimeoutHCL: "5s",
UseIdentity: &trueValue,
ServiceIdentity: &config.WorkloadIdentity{
ServiceIdentity: &config.WorkloadIdentityConfig{
Audience: []string{"consul.io", "nomad.dev"},
Env: false,
File: true,
Env: pointer.Of(false),
File: pointer.Of(true),
},
TemplateIdentity: &config.WorkloadIdentity{
TemplateIdentity: &config.WorkloadIdentityConfig{
Audience: []string{"consul.io"},
Env: true,
File: false,
Env: pointer.Of(true),
File: pointer.Of(false),
},
},
Consuls: map[string]*config.ConsulConfig{
Expand Down Expand Up @@ -272,15 +272,15 @@ var basicConfig = &Config{
Timeout: 5 * time.Second,
TimeoutHCL: "5s",
UseIdentity: &trueValue,
ServiceIdentity: &config.WorkloadIdentity{
ServiceIdentity: &config.WorkloadIdentityConfig{
Audience: []string{"consul.io", "nomad.dev"},
Env: false,
File: true,
Env: pointer.Of(false),
File: pointer.Of(true),
},
TemplateIdentity: &config.WorkloadIdentity{
TemplateIdentity: &config.WorkloadIdentityConfig{
Audience: []string{"consul.io"},
Env: true,
File: false,
Env: pointer.Of(true),
File: pointer.Of(false),
},
},
},
Expand Down
49 changes: 49 additions & 0 deletions nomad/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,55 @@ func (c *Config) Copy() *Config {
return &nc
}

// ConsulServiceIdentity returns the workload identity to be used for accessing
// the Consul API to register and manage Consul services.
func (c *Config) ConsulServiceIdentity() *structs.WorkloadIdentity {
if c.ConsulConfig == nil {
return nil
}

return workloadIdentityFromConfig(c.ConsulConfig.ServiceIdentity)
}

// ConsulTemplateIdentity returns the workload identity to be used for
// accessing the Consul API from templates.
func (c *Config) ConsulTemplateIdentity() *structs.WorkloadIdentity {
if c.ConsulConfig == nil {
return nil
}

return workloadIdentityFromConfig(c.ConsulConfig.TemplateIdentity)
}

// 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 {
if widConfig == nil {
return nil
}

wid := &structs.WorkloadIdentity{}

if len(widConfig.Audience) > 0 {
wid.Audience = widConfig.Audience
}
if widConfig.Env != nil {
wid.Env = *widConfig.Env
}
if widConfig.File != nil {
wid.File = *widConfig.File
}

return wid
}

// DefaultConfig returns the default configuration. Only used as the basis for
// merging agent or test parameters.
func DefaultConfig() *Config {
Expand Down
2 changes: 1 addition & 1 deletion nomad/job_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func NewJobEndpoints(s *Server, ctx *RPCContext) *Job {
jobExposeCheckHook{},
jobImpliedConstraints{},
jobNodePoolMutatingHook{srv: s},
jobIdentityCreator{srv: s},
jobImplicitIdentitiesHook{srv: s},
},
validators: []jobValidator{
jobConnectHook{},
Expand Down
2 changes: 1 addition & 1 deletion nomad/job_endpoint_hook_connect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ func TestJobEndpointConnect_groupConnectHook_MeshGateway(t *testing.T) {
func TestJobEndpointConnect_ConnectInterpolation(t *testing.T) {
ci.Parallel(t)

server := &Server{logger: testlog.HCLogger(t)}
server := &Server{logger: testlog.HCLogger(t), config: DefaultConfig()}
jobEndpoint := NewJobEndpoints(server, nil)

j := mock.ConnectJob()
Expand Down
76 changes: 76 additions & 0 deletions nomad/job_endpoint_hook_implicit_identities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package nomad

import (
"fmt"

"github.com/hashicorp/nomad/nomad/structs"
)

var (
consulServiceIdentityNamePrefix = "consul-service"
)

// jobImplicitIdentitiesHook adds implicit `identity` blocks for external
// services, like Consul and Vault.
type jobImplicitIdentitiesHook struct {
srv *Server
}

func (jobImplicitIdentitiesHook) Name() string {
return "implicit-identities"
}

func (h jobImplicitIdentitiesHook) Mutate(job *structs.Job) (*structs.Job, []error, error) {
for _, tg := range job.TaskGroups {
for _, s := range tg.Services {
h.handleConsulService(s)
}

for _, t := range tg.Tasks {
for _, s := range t.Services {
h.handleConsulService(s)
}
}
}

return job, nil, nil
}

// 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.
//
// If the service already has an identity it 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
}

// Use the identity specified in the service.
serviceWID := s.Identity
if serviceWID == nil {
// If the service doesn't specify an identity, fallback to the service
// identity defined in the server configuration.
serviceWID = h.srv.config.ConsulServiceIdentity()
if serviceWID == nil {
// If no identity is found, skip injecting the implicit identity
// and fallback to the legacy flow.
return
}
}

// Set the expected identity name and service name.
serviceWID.Name = fmt.Sprintf("%s/%s", consulServiceIdentityNamePrefix, s.Name)
serviceWID.ServiceName = s.Name

s.Identity = serviceWID
}
Loading

0 comments on commit 7466496

Please sign in to comment.