Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport of refactor identity claims constructor to builder into release/1.8.x #23741

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions nomad/acl_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1944,9 +1944,12 @@ func TestACLEndpoint_WhoAmI(t *testing.T) {
// Lookup identity claim
alloc := mock.Alloc()
s1.fsm.State().UpsertAllocs(structs.MsgTypeTestSetup, 1500, []*structs.Allocation{alloc})
claims := structs.NewIdentityClaims(alloc.Job, alloc,
task := alloc.LookupTask("web")
claims := structs.NewIdentityClaimsBuilder(alloc.Job, alloc,
wiHandle, // see encrypter_test.go
alloc.LookupTask("web").Identity, time.Now().Add(-10*time.Minute))
task.Identity).
WithTask(task).
Build(time.Now().Add(-10 * time.Minute))
jwtToken, _, err := s1.encrypter.SignClaims(claims)
must.NoError(t, err)

Expand Down
16 changes: 14 additions & 2 deletions nomad/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,23 @@ func TestAuthenticate_mTLS(t *testing.T) {
WorkloadType: structs.WorkloadTypeTask,
}

claims1 := structs.NewIdentityClaims(job, alloc1, wiHandle, alloc1.LookupTask("web").Identity, time.Now())
task1 := alloc1.LookupTask("web")
claims1 := structs.NewIdentityClaimsBuilder(job, alloc1,
wiHandle,
task1.Identity).
WithTask(task1).
Build(time.Now())

claims1Token, _, err := leader.encrypter.SignClaims(claims1)
must.NoError(t, err, must.Sprint("could not sign claims"))

claims2 := structs.NewIdentityClaims(job, alloc2, wiHandle, alloc2.LookupTask("web").Identity, time.Now())
task2 := alloc2.LookupTask("web")
claims2 := structs.NewIdentityClaimsBuilder(job, alloc2,
wiHandle,
task2.Identity).
WithTask(task1).
Build(time.Now())

claims2Token, _, err := leader.encrypter.SignClaims(claims2)
must.NoError(t, err, must.Sprint("could not sign claims"))

Expand Down
29 changes: 21 additions & 8 deletions nomad/alloc_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,12 @@ func (a *Alloc) signTasks(
}

widFound = true
err = a.signIdentities(alloc, wid, idReq, reply, now)
claims := structs.NewIdentityClaimsBuilder(alloc.Job, alloc, &idReq.WIHandle, wid).
WithTask(task).
WithConsul().
WithVault().
Build(now)
err = a.signClaims(claims, idReq, reply)
break
}
return
Expand All @@ -638,28 +643,36 @@ func (a *Alloc) signServices(
for _, tg := range job.TaskGroups {
for _, service := range tg.Services {
if service.IdentityHandle(nil).Equal(wid) {
return true, a.signIdentities(alloc, service.Identity, idReq, reply, now)
claims := structs.NewIdentityClaimsBuilder(
alloc.Job, alloc, &idReq.WIHandle, service.Identity).
WithConsul().
WithService(service).
Build(now)
return true, a.signClaims(claims, idReq, reply)
}
}
for _, task := range tg.Tasks {
for _, service := range task.Services {
if service.IdentityHandle(nil).Equal(wid) {
return true, a.signIdentities(alloc, service.Identity, idReq, reply, now)
claims := structs.NewIdentityClaimsBuilder(
alloc.Job, alloc, &idReq.WIHandle, service.Identity).
WithTask(task).
WithConsul().
WithService(service).
Build(now)
return true, a.signClaims(claims, idReq, reply)
}
}
}
}
return
}

func (a *Alloc) signIdentities(
alloc *structs.Allocation,
wid *structs.WorkloadIdentity,
func (a *Alloc) signClaims(
claims *structs.IdentityClaims,
idReq *structs.WorkloadIdentityRequest,
reply *structs.AllocIdentitiesResponse,
now time.Time,
) error {
claims := structs.NewIdentityClaims(alloc.Job, alloc, &idReq.WIHandle, wid, now)
token, _, err := a.srv.encrypter.SignClaims(claims)
if err != nil {
return err
Expand Down
17 changes: 13 additions & 4 deletions nomad/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,11 @@ func TestAuthenticateDefault(t *testing.T) {
identity := task.Identity
wih := task.IdentityHandle(identity)
alloc.ClientStatus = structs.AllocClientStatusRunning
claims := structs.NewIdentityClaims(alloc.Job, alloc, wih, identity, time.Now())

claims := structs.NewIdentityClaimsBuilder(alloc.Job, alloc,
wih,
identity).
Build(time.Now())
auth := testAuthenticator(t, store, true, true)
token, err := auth.encrypter.(*testEncrypter).signClaim(claims)
must.NoError(t, err)
Expand Down Expand Up @@ -311,7 +314,10 @@ func TestAuthenticateDefault(t *testing.T) {
identity := task.Identity
wih := task.IdentityHandle(identity)
alloc.ClientStatus = structs.AllocClientStatusRunning
claims := structs.NewIdentityClaims(alloc.Job, alloc, wih, identity, time.Now())
claims := structs.NewIdentityClaimsBuilder(alloc.Job, alloc,
wih,
identity).
Build(time.Now())

auth := testAuthenticator(t, store, true, true)
token, err := auth.encrypter.(*testEncrypter).signClaim(claims)
Expand Down Expand Up @@ -898,8 +904,11 @@ func TestIdentityToACLClaim(t *testing.T) {
task := tg.Tasks[0]

defaultWI := &structs.WorkloadIdentity{Name: "default"}
claims := structs.NewIdentityClaims(alloc.Job, alloc,
task.IdentityHandle(defaultWI), task.Identity, time.Now())
claims := structs.NewIdentityClaimsBuilder(alloc.Job, alloc,
task.IdentityHandle(defaultWI),
task.Identity).
WithTask(task).
Build(time.Now())

store := testStateStore(t)

Expand Down
18 changes: 15 additions & 3 deletions nomad/encrypter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,11 @@ func TestEncrypter_SignVerify(t *testing.T) {
testutil.WaitForKeyring(t, srv.RPC, "global")

alloc := mock.Alloc()
claims := structs.NewIdentityClaims(alloc.Job, alloc, wiHandle, alloc.LookupTask("web").Identity, time.Now())
task := alloc.LookupTask("web")

claims := structs.NewIdentityClaimsBuilder(alloc.Job, alloc, wiHandle, task.Identity).
WithTask(task).
Build(time.Now())
e := srv.encrypter

out, _, err := e.SignClaims(claims)
Expand Down Expand Up @@ -486,7 +490,11 @@ func TestEncrypter_SignVerify_Issuer(t *testing.T) {
testutil.WaitForKeyring(t, srv.RPC, "global")

alloc := mock.Alloc()
claims := structs.NewIdentityClaims(alloc.Job, alloc, wiHandle, alloc.LookupTask("web").Identity, time.Now())
task := alloc.LookupTask("web")
claims := structs.NewIdentityClaimsBuilder(alloc.Job, alloc, wiHandle, task.Identity).
WithTask(task).
Build(time.Now())

e := srv.encrypter

out, _, err := e.SignClaims(claims)
Expand All @@ -511,7 +519,11 @@ func TestEncrypter_SignVerify_AlgNone(t *testing.T) {
testutil.WaitForKeyring(t, srv.RPC, "global")

alloc := mock.Alloc()
claims := structs.NewIdentityClaims(alloc.Job, alloc, wiHandle, alloc.LookupTask("web").Identity, time.Now())
task := alloc.LookupTask("web")
claims := structs.NewIdentityClaimsBuilder(alloc.Job, alloc, wiHandle, task.Identity).
WithTask(task).
Build(time.Now())

e := srv.encrypter

keyset, err := e.activeKeySet()
Expand Down
7 changes: 6 additions & 1 deletion nomad/plan_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,12 @@ func signAllocIdentities(signer claimSigner, job *structs.Job, allocations []*st
continue
}
defaultWI := &structs.WorkloadIdentity{Name: "default"}
claims := structs.NewIdentityClaims(job, alloc, task.IdentityHandle(defaultWI), task.Identity, now)

claims := structs.NewIdentityClaimsBuilder(
job, alloc, task.IdentityHandle(defaultWI), task.Identity).
WithTask(task).
Build(now)

token, keyID, err := signer.SignClaims(claims)
if err != nil {
return err
Expand Down
135 changes: 0 additions & 135 deletions nomad/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"strings"
"time"

jwt "github.com/go-jose/go-jose/v3/jwt"
"github.com/hashicorp/cronexpr"
"github.com/hashicorp/go-msgpack/v2/codec"
"github.com/hashicorp/go-multierror"
Expand Down Expand Up @@ -11705,140 +11704,6 @@ func (a *Allocation) LastRescheduleFailed() bool {
a.RescheduleTracker.LastReschedule != LastRescheduleSuccess
}

// IdentityClaims are the input to a JWT identifying a workload. It
// should never be serialized to msgpack unsigned.
type IdentityClaims struct {
Namespace string `json:"nomad_namespace"`
JobID string `json:"nomad_job_id"`
AllocationID string `json:"nomad_allocation_id"`
TaskName string `json:"nomad_task,omitempty"`
ServiceName string `json:"nomad_service,omitempty"`

ConsulNamespace string `json:"consul_namespace,omitempty"`
VaultNamespace string `json:"vault_namespace,omitempty"`
VaultRole string `json:"vault_role,omitempty"`

jwt.Claims
}

// NewIdentityClaims returns new workload identity claims. Since it may be
// called with a denormalized Allocation, the Job must be passed in distinctly.
//
// ID claim is random (nondeterministic) so multiple calls with the same values
// will not return equal claims by design. JWT IDs should never collide.
func NewIdentityClaims(job *Job, alloc *Allocation, wihandle *WIHandle, wid *WorkloadIdentity, now time.Time) *IdentityClaims {
tg := job.LookupTaskGroup(alloc.TaskGroup)
if tg == nil {
return nil
}

if wid == nil {
return nil
}

jwtnow := jwt.NewNumericDate(now.UTC())
claims := &IdentityClaims{
Namespace: alloc.Namespace,
JobID: alloc.JobID,
AllocationID: alloc.ID,
Claims: jwt.Claims{
NotBefore: jwtnow,
IssuedAt: jwtnow,
},
}

// If this is a child job, use the parent's ID
if job.ParentID != "" {
claims.JobID = job.ParentID
}

var taskName string

switch wihandle.WorkloadType {
case WorkloadTypeService:
serviceName := wihandle.WorkloadIdentifier
if wihandle.InterpolatedWorkloadIdentifier != "" {
serviceName = wihandle.InterpolatedWorkloadIdentifier
}
claims.ServiceName = serviceName

// Find task name if this is a task service.
for _, t := range tg.Tasks {
for _, s := range t.Services {
if s.Name == serviceName {
taskName = t.Name
break
}
}
if taskName != "" {
break
}
}

case WorkloadTypeTask:
taskName = wihandle.WorkloadIdentifier
claims.TaskName = taskName

default:
// in case of an unknown workload type we quit
return nil
}

// Add ConsulNamespace and VaultNamespace claims if necessary.
if taskName != "" {
task := tg.LookupTask(taskName)
if task == nil {
return nil
}

if wid.IsConsul() {
if task.Consul != nil {
claims.ConsulNamespace = task.Consul.Namespace
} else if tg.Consul != nil {
claims.ConsulNamespace = tg.Consul.Namespace
}
}

if wid.IsVault() && task.Vault != nil {
claims.VaultNamespace = task.Vault.Namespace
claims.VaultRole = task.Vault.Role
}

} else if wid.IsConsul() && tg.Consul != nil {
claims.ConsulNamespace = tg.Consul.Namespace
}

claims.Audience = slices.Clone(wid.Audience)
claims.setSubject(job, alloc.TaskGroup, wihandle.WorkloadIdentifier, wid.Name)
claims.setExp(now, wid)

claims.ID = uuid.Generate()

return claims
}

// setSubject creates the standard subject claim for workload identities.
func (claims *IdentityClaims) setSubject(job *Job, group, widentifier, id string) {
claims.Subject = strings.Join([]string{
job.Region,
job.Namespace,
job.ID,
group,
widentifier,
id,
}, ":")
}

// setExp sets the absolute time at which these identity claims expire.
func (claims *IdentityClaims) setExp(now time.Time, wid *WorkloadIdentity) {
if wid.TTL == 0 {
// No expiry
return
}

claims.Expiry = jwt.NewNumericDate(now.Add(wid.TTL))
}

// AllocationDiff is another named type for Allocation (to use the same fields),
// which is used to represent the delta for an Allocation. If you need a method
// defined on the al
Expand Down
Loading