Skip to content

Commit

Permalink
WI: interpolate parent job ID in vault.default_identity.extra_claims (
Browse files Browse the repository at this point in the history
#23817)

When we interpolate job fields for the `vault.default_identity.extra_claims`
block, we forgot to use the parent job ID when that's available (as we do for
all other claims). This changeset fixes the bug and adds a helper method that'll
hopefully remind us to do this going forward.

Also added a missing changelog entry for #23675 where we implemented the
`extra_claims` block originally, which shipped in Nomad 1.8.3.

Fixes: #23798
  • Loading branch information
tgross authored Sep 3, 2024
1 parent 6700937 commit c43e30a
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 34 deletions.
3 changes: 3 additions & 0 deletions .changelog/23675.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
identity: Added support for server-configured additional claims on the Vault default_identity block
```
3 changes: 3 additions & 0 deletions .changelog/23817.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
identity: Fixed a bug where dispatch and periodic jobs would have their job ID and not parent job ID used when interpolating vault.default_identity.extra_claims
```
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ IMPROVEMENTS:
* cli: `acl token create` will now emit a warning if the token has a policy that does not yet exist [[GH-16437](https://github.com/hashicorp/nomad/issues/16437)]
* keyring: Added support for encrypting the keyring via Vault transit or external KMS [[GH-23580](https://github.com/hashicorp/nomad/issues/23580)]
* keyring: Added support for prepublishing keys [[GH-23577](https://github.com/hashicorp/nomad/issues/23577)]
* identity: Added support for server-configured additional claims on the Vault default_identity block [[GH-23675](https://github.com/hashicorp/nomad/issues/23675)]
* metrics: Added `client.tasks` metrics to track task states [[GH-23773](https://github.com/hashicorp/nomad/issues/23773)]
* resources: Added `resources.secrets` field to configure size of secrets directory on Linux [[GH-23696](https://github.com/hashicorp/nomad/issues/23696)]
* tls: Allow setting the `tls_min_version` field to `"tls13"` [[GH-23713](https://github.com/hashicorp/nomad/issues/23713)]
Expand Down
5 changes: 1 addition & 4 deletions nomad/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -641,10 +641,7 @@ func (s *Authenticator) ResolvePoliciesForClaims(claims *structs.IdentityClaims)
}

// Find any policies attached to the job
jobId := alloc.Job.ID
if alloc.Job.ParentID != "" {
jobId = alloc.Job.ParentID
}
jobId := alloc.Job.GetIDforWorkloadIdentity()
iter, err := snap.ACLPolicyByJob(nil, alloc.Namespace, jobId)
if err != nil {
return nil, err
Expand Down
9 changes: 9 additions & 0 deletions nomad/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4604,6 +4604,15 @@ func (j *Job) GetNamespace() string {
return j.Namespace
}

// GetIDforWorkloadIdentity is used when we want the job ID for identity; here we
// always want the parent ID if there is one and then fallback to the ID
func (j *Job) GetIDforWorkloadIdentity() string {
if j.ParentID != "" {
return j.ParentID
}
return j.ID
}

// GetCreateIndex implements the CreateIndexGetter interface, required for
// pagination.
func (j *Job) GetCreateIndex() uint64 {
Expand Down
10 changes: 4 additions & 6 deletions nomad/structs/workload_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func (b *IdentityClaimsBuilder) Build(now time.Time) *IdentityClaims {
jwtnow := jwt.NewNumericDate(now.UTC())
claims := &IdentityClaims{
Namespace: b.alloc.Namespace,
JobID: b.job.ID,
JobID: b.job.GetIDforWorkloadIdentity(),
AllocationID: b.alloc.ID,
ServiceName: b.serviceName,
Claims: jwt.Claims{
Expand All @@ -200,10 +200,6 @@ func (b *IdentityClaimsBuilder) Build(now time.Time) *IdentityClaims {
},
ExtraClaims: b.extras,
}
// If this is a child job, use the parent's ID
if b.job.ParentID != "" {
claims.JobID = b.job.ParentID
}
if b.task != nil && b.wihandle.WorkloadType != WorkloadTypeService {
claims.TaskName = b.task.Name
}
Expand Down Expand Up @@ -235,13 +231,15 @@ func (b *IdentityClaimsBuilder) interpolate() {
if len(b.extras) == 0 {
return
}

r := strings.NewReplacer(
// attributes that always exist
"${job.region}", b.job.Region,
"${job.namespace}", b.job.Namespace,
"${job.id}", b.job.ID,
"${job.id}", b.job.GetIDforWorkloadIdentity(),
"${job.node_pool}", b.job.NodePool,
"${group.name}", b.tg.Name,
"${alloc.id}", b.alloc.ID,

// attributes that conditionally exist
"${node.id}", strAttrGet(b.node, func(n *Node) string { return n.ID }),
Expand Down
49 changes: 25 additions & 24 deletions nomad/structs/workload_id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func TestNewIdentityClaims(t *testing.T) {

job := &Job{
ID: "job",
ParentID: "parentJob",
Name: "job",
Namespace: "default",
Region: "global",
Expand Down Expand Up @@ -178,7 +179,7 @@ func TestNewIdentityClaims(t *testing.T) {
// group: no consul.
"job/group/services/group-service": {
Namespace: "default",
JobID: "job",
JobID: "parentJob",
ServiceName: "group-service",
Claims: jwt.Claims{
Subject: "global:default:job:group:group-service:consul-service_group-service-http",
Expand All @@ -190,7 +191,7 @@ func TestNewIdentityClaims(t *testing.T) {
// task: no consul, no vault.
"job/group/task/default-identity": {
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "task",
Claims: jwt.Claims{
Subject: "global:default:job:group:task:default-identity",
Expand All @@ -200,7 +201,7 @@ func TestNewIdentityClaims(t *testing.T) {
},
"job/group/task/alt-identity": {
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "task",
Claims: jwt.Claims{
Subject: "global:default:job:group:task:alt-identity",
Expand All @@ -213,7 +214,7 @@ func TestNewIdentityClaims(t *testing.T) {
"job/group/task/consul_default": {
ConsulNamespace: "",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "task",
Claims: jwt.Claims{
Subject: "global:default:job:group:task:consul_default",
Expand All @@ -226,20 +227,20 @@ func TestNewIdentityClaims(t *testing.T) {
"job/group/task/vault_default": {
VaultNamespace: "",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "task",
VaultRole: "", // not specified in jobspec
Claims: jwt.Claims{
Subject: "global:default:job:group:task:vault_default",
Audience: jwt.Audience{"vault.io"},
},
ExtraClaims: map[string]string{
"nomad_workload_id": "global:default:job",
"nomad_workload_id": "global:default:parentJob",
},
},
"job/group/task/services/task-service": {
Namespace: "default",
JobID: "job",
JobID: "parentJob",
ServiceName: "task-service",
Claims: jwt.Claims{
Subject: "global:default:job:group:task-service:consul-service_task-task-service-http",
Expand All @@ -251,7 +252,7 @@ func TestNewIdentityClaims(t *testing.T) {
// task: with consul, with vault.
"job/group/consul-vault-task/default-identity": {
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "consul-vault-task",
Claims: jwt.Claims{
Subject: "global:default:job:group:consul-vault-task:default-identity",
Expand All @@ -263,7 +264,7 @@ func TestNewIdentityClaims(t *testing.T) {
"job/group/consul-vault-task/consul_default": {
ConsulNamespace: "task-consul-namespace",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "consul-vault-task",
Claims: jwt.Claims{
Subject: "global:default:job:group:consul-vault-task:consul_default",
Expand All @@ -275,22 +276,22 @@ func TestNewIdentityClaims(t *testing.T) {
"job/group/consul-vault-task/vault_default": {
VaultNamespace: "vault-namespace",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "consul-vault-task",
VaultRole: "role-from-spec-group",
Claims: jwt.Claims{
Subject: "global:default:job:group:consul-vault-task:vault_default",
Audience: jwt.Audience{"vault.io"},
},
ExtraClaims: map[string]string{
"nomad_workload_id": "global:default:job",
"nomad_workload_id": "global:default:parentJob",
},
},
// Use task-level Consul namespace for task services.
"job/group/consul-vault-task/services/consul-vault-task-service": {
ConsulNamespace: "task-consul-namespace",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
ServiceName: "consul-vault-task-service",
Claims: jwt.Claims{
Subject: "global:default:job:group:consul-vault-task-service:consul-service_consul-vault-task-service-http",
Expand All @@ -303,7 +304,7 @@ func TestNewIdentityClaims(t *testing.T) {
"job/consul-group/services/group-service": {
ConsulNamespace: "group-consul-namespace",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
ServiceName: "group-service",
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:group-service:consul-service_group-service-http",
Expand All @@ -315,7 +316,7 @@ func TestNewIdentityClaims(t *testing.T) {
// task: no consul, no vault.
"job/consul-group/task/default-identity": {
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "task",
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:task:default-identity",
Expand All @@ -325,7 +326,7 @@ func TestNewIdentityClaims(t *testing.T) {
},
"job/consul-group/task/alt-identity": {
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "task",
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:task:alt-identity",
Expand All @@ -338,7 +339,7 @@ func TestNewIdentityClaims(t *testing.T) {
"job/consul-group/task/consul_default": {
ConsulNamespace: "group-consul-namespace",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "task",
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:task:consul_default",
Expand All @@ -348,23 +349,23 @@ func TestNewIdentityClaims(t *testing.T) {
},
"job/consul-group/task/vault_default": {
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "task",
VaultRole: "", // not specified in jobspec
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:task:vault_default",
Audience: jwt.Audience{"vault.io"},
},
ExtraClaims: map[string]string{
"nomad_workload_id": "global:default:job",
"nomad_workload_id": "global:default:parentJob",
},
},
// Use group-level Consul namespace for task service because task
// doesn't have a consul block.
"job/consul-group/task/services/task-service": {
ConsulNamespace: "group-consul-namespace",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
ServiceName: "task-service",
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:task-service:consul-service_task-task-service-http",
Expand All @@ -376,7 +377,7 @@ func TestNewIdentityClaims(t *testing.T) {
// task: with consul, with vault.
"job/consul-group/consul-vault-task/default-identity": {
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "consul-vault-task",
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:consul-vault-task:default-identity",
Expand All @@ -388,7 +389,7 @@ func TestNewIdentityClaims(t *testing.T) {
"job/consul-group/consul-vault-task/consul_default": {
ConsulNamespace: "task-consul-namespace",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "consul-vault-task",
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:consul-vault-task:consul_default",
Expand All @@ -399,22 +400,22 @@ func TestNewIdentityClaims(t *testing.T) {
"job/consul-group/consul-vault-task/vault_default": {
VaultNamespace: "vault-namespace",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
TaskName: "consul-vault-task",
VaultRole: "role-from-spec-consul-group",
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:consul-vault-task:vault_default",
Audience: jwt.Audience{"vault.io"},
},
ExtraClaims: map[string]string{
"nomad_workload_id": "global:default:job",
"nomad_workload_id": "global:default:parentJob",
},
},
// Use task-level Consul namespace for task services.
"job/consul-group/consul-vault-task/services/consul-task-service": {
ConsulNamespace: "task-consul-namespace",
Namespace: "default",
JobID: "job",
JobID: "parentJob",
ServiceName: "consul-task-service",
Claims: jwt.Claims{
Subject: "global:default:job:consul-group:consul-task-service:consul-service_consul-vault-task-consul-task-service-http",
Expand Down
1 change: 1 addition & 0 deletions website/content/docs/configuration/vault.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ will be removed in a future release.
- `${job.id}` - The job's ID.
- `${job.node_pool}` - The node pool where the allocation is running.
- `${group.name}` - The task group name of the task using Vault.
- `${alloc.id}` - The allocation's ID.
- `${task.name}` - The name of the task using Vault.
- `${node.id}` - The ID of the node where the allocation is running.
- `${node.datacenter}` - The datacenter of the node where the allocation is running.
Expand Down

0 comments on commit c43e30a

Please sign in to comment.