T3MP3ST public launch build
This commit is contained in:
1 parent
972247b881
commit
184c7d4e59
1589 files changed
+57150
-506
No files matched your search
@@ -0,0 +1,696 @@
|
||||
// Copyright IBM Corp. 2015, 2025
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package nomad
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-memdb"
|
||||
metrics "github.com/hashicorp/go-metrics/compat"
|
||||
|
||||
"github.com/hashicorp/nomad/acl"
|
||||
"github.com/hashicorp/nomad/helper/pointer"
|
||||
"github.com/hashicorp/nomad/helper/uuid"
|
||||
"github.com/hashicorp/nomad/nomad/state"
|
||||
"github.com/hashicorp/nomad/nomad/state/paginator"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
|
||||
// Alloc endpoint is used for manipulating allocations
|
||||
type Alloc struct {
|
||||
srv *Server
|
||||
ctx *RPCContext
|
||||
logger hclog.Logger
|
||||
}
|
||||
|
||||
func NewAllocEndpoint(srv *Server, ctx *RPCContext) *Alloc {
|
||||
return &Alloc{srv: srv, ctx: ctx, logger: srv.logger.Named("alloc")}
|
||||
}
|
||||
|
||||
// List is used to list the allocations in the system
|
||||
func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListResponse) error {
|
||||
authErr := a.srv.Authenticate(a.ctx, args)
|
||||
if done, err := a.srv.forward("Alloc.List", args, args, reply); done {
|
||||
return err
|
||||
}
|
||||
a.srv.MeasureRPCRate("alloc", structs.RateMetricList, args)
|
||||
if authErr != nil {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "alloc", "list"}, time.Now())
|
||||
|
||||
namespace := args.RequestNamespace()
|
||||
|
||||
// Check namespace read-job permissions
|
||||
aclObj, err := a.srv.ResolveACL(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
allow := aclObj.AllowNsOpFunc(acl.NamespaceCapabilityReadJob)
|
||||
|
||||
// Setup the blocking query
|
||||
sort := state.SortOption(args.Reverse)
|
||||
opts := blockingOptions{
|
||||
queryOpts: &args.QueryOptions,
|
||||
queryMeta: &reply.QueryMeta,
|
||||
run: func(ws memdb.WatchSet, state *state.StateStore) error {
|
||||
// Scan all the allocations
|
||||
var err error
|
||||
var iter memdb.ResultIterator
|
||||
|
||||
// get list of accessible namespaces
|
||||
allowableNamespaces, err := allowedNSes(aclObj, state, allow)
|
||||
if err == structs.ErrPermissionDenied {
|
||||
// return empty allocation if token is not authorized for any
|
||||
// namespace, matching other endpoints
|
||||
reply.Allocations = make([]*structs.AllocListStub, 0)
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else {
|
||||
var tokenizer paginator.Tokenizer[*structs.Allocation]
|
||||
|
||||
if prefix := args.QueryOptions.Prefix; prefix != "" {
|
||||
iter, err = state.AllocsByIDPrefix(ws, namespace, prefix, sort)
|
||||
tokenizer = paginator.IDTokenizer[*structs.Allocation](args.NextToken)
|
||||
} else if namespace != structs.AllNamespacesSentinel {
|
||||
iter, err = state.AllocsByNamespaceOrdered(ws, namespace, sort)
|
||||
tokenizer = paginator.CreateIndexAndIDTokenizer[*structs.Allocation](args.NextToken)
|
||||
} else {
|
||||
iter, err = state.Allocs(ws, sort)
|
||||
tokenizer = paginator.CreateIndexAndIDTokenizer[*structs.Allocation](args.NextToken)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pager, err := paginator.NewPaginator(iter, args.QueryOptions,
|
||||
paginator.NamespaceSelectorFunc[*structs.Allocation](allowableNamespaces),
|
||||
tokenizer,
|
||||
func(a *structs.Allocation) (*structs.AllocListStub, error) {
|
||||
return a.Stub(args.Fields), nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return structs.NewErrRPCCodedf(
|
||||
http.StatusBadRequest, "failed to create result paginator: %v", err)
|
||||
}
|
||||
|
||||
stubs, nextToken, err := pager.Page()
|
||||
if err != nil {
|
||||
return structs.NewErrRPCCodedf(
|
||||
http.StatusBadRequest, "failed to read result page: %v", err)
|
||||
}
|
||||
|
||||
reply.QueryMeta.NextToken = nextToken
|
||||
reply.Allocations = stubs
|
||||
}
|
||||
|
||||
// Use the last index that affected the allocs table
|
||||
index, err := state.Index("allocs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply.Index = index
|
||||
|
||||
// Set the query response
|
||||
a.srv.setQueryMeta(&reply.QueryMeta)
|
||||
return nil
|
||||
}}
|
||||
return a.srv.blockingRPC(&opts)
|
||||
}
|
||||
|
||||
// GetAlloc is used to lookup a particular allocation
|
||||
func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest,
|
||||
reply *structs.SingleAllocResponse) error {
|
||||
authErr := a.srv.Authenticate(a.ctx, args)
|
||||
if done, err := a.srv.forward("Alloc.GetAlloc", args, args, reply); done {
|
||||
return err
|
||||
}
|
||||
a.srv.MeasureRPCRate("alloc", structs.RateMetricRead, args)
|
||||
if authErr != nil {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "alloc", "get_alloc"}, time.Now())
|
||||
|
||||
// Check namespace read-job permissions before performing blocking query.
|
||||
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
|
||||
aclObj, err := a.srv.ResolveACL(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup the blocking query
|
||||
opts := blockingOptions{
|
||||
queryOpts: &args.QueryOptions,
|
||||
queryMeta: &reply.QueryMeta,
|
||||
run: func(ws memdb.WatchSet, state *state.StateStore) error {
|
||||
// Lookup the allocation
|
||||
out, err := state.AllocByID(ws, args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup the output
|
||||
if out != nil {
|
||||
if out.SignedIdentities != nil {
|
||||
out = out.Sanitize()
|
||||
}
|
||||
reply.Alloc = out
|
||||
|
||||
// Re-check namespace in case it differs from request.
|
||||
if err := a.srv.AuthorizeClientAllocation(aclObj, out, allowNsOp); err != nil {
|
||||
return structs.NewErrUnknownAllocation(args.AllocID)
|
||||
}
|
||||
|
||||
reply.Index = out.ModifyIndex
|
||||
} else {
|
||||
// Use the last index that affected the allocs table
|
||||
index, err := state.Index("allocs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply.Index = index
|
||||
}
|
||||
|
||||
// Set the query response
|
||||
a.srv.setQueryMeta(&reply.QueryMeta)
|
||||
return nil
|
||||
}}
|
||||
return a.srv.blockingRPC(&opts)
|
||||
}
|
||||
|
||||
// GetAllocs is used to lookup a set of allocations
|
||||
func (a *Alloc) GetAllocs(args *structs.AllocsGetRequest,
|
||||
reply *structs.AllocsGetResponse) error {
|
||||
|
||||
aclObj, err := a.srv.AuthenticateClientOnly(a.ctx, args)
|
||||
if done, err := a.srv.forward("Alloc.GetAllocs", args, args, reply); done {
|
||||
return err
|
||||
}
|
||||
a.srv.MeasureRPCRate("alloc", structs.RateMetricWrite, args)
|
||||
if err != nil {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "alloc", "get_allocs"}, time.Now())
|
||||
|
||||
allocs := make([]*structs.Allocation, len(args.AllocIDs))
|
||||
|
||||
// Setup the blocking query. We wait for at least one of the requested
|
||||
// allocations to be above the min query index. This guarantees that the
|
||||
// server has received that index.
|
||||
opts := blockingOptions{
|
||||
queryOpts: &args.QueryOptions,
|
||||
queryMeta: &reply.QueryMeta,
|
||||
run: func(ws memdb.WatchSet, state *state.StateStore) error {
|
||||
// Lookup the allocation
|
||||
thresholdMet := false
|
||||
maxIndex := uint64(0)
|
||||
for i, alloc := range args.AllocIDs {
|
||||
out, err := state.AllocByID(ws, alloc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if out == nil {
|
||||
// The alloc may have been GC'd or purged. Continue so we can
|
||||
// potentially satisfy the query with partial results from the
|
||||
// remaining allocs.
|
||||
continue
|
||||
}
|
||||
|
||||
// It shouldn't be possible to have an alloc with a nil job, but
|
||||
// let's not block the node from getting other allocs if that's
|
||||
// the case.
|
||||
if out.Job == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := a.srv.AuthorizeClientAllocation(aclObj, out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the pointer
|
||||
allocs[i] = out
|
||||
|
||||
// Check if we have passed the minimum index
|
||||
if out.ModifyIndex > args.QueryOptions.MinQueryIndex {
|
||||
thresholdMet = true
|
||||
}
|
||||
|
||||
if maxIndex < out.ModifyIndex {
|
||||
maxIndex = out.ModifyIndex
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the output
|
||||
if thresholdMet {
|
||||
reply.Allocs = allocs
|
||||
reply.Index = maxIndex
|
||||
} else {
|
||||
// Use the last index that affected the allocs table
|
||||
index, err := state.Index("allocs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply.Index = index
|
||||
}
|
||||
|
||||
// Set the query response
|
||||
a.srv.setQueryMeta(&reply.QueryMeta)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return a.srv.blockingRPC(&opts)
|
||||
}
|
||||
|
||||
// Stop is used to stop an allocation and migrate it to another node.
|
||||
func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopResponse) error {
|
||||
|
||||
authErr := a.srv.Authenticate(a.ctx, args)
|
||||
if done, err := a.srv.forward("Alloc.Stop", args, args, reply); done {
|
||||
return err
|
||||
}
|
||||
a.srv.MeasureRPCRate("alloc", structs.RateMetricWrite, args)
|
||||
if authErr != nil {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
defer metrics.MeasureSince([]string{"nomad", "alloc", "stop"}, time.Now())
|
||||
|
||||
alloc, err := getAlloc(a.srv.State(), args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check for namespace alloc-lifecycle permissions.
|
||||
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityAllocLifecycle)
|
||||
aclObj, err := a.srv.ResolveACL(args)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !allowNsOp(aclObj, alloc.Namespace) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
now := time.Now().UTC().UnixNano()
|
||||
eval := &structs.Evaluation{
|
||||
ID: uuid.Generate(),
|
||||
Namespace: alloc.Namespace,
|
||||
Priority: alloc.Job.Priority,
|
||||
Type: alloc.Job.Type,
|
||||
TriggeredBy: structs.EvalTriggerAllocStop,
|
||||
JobID: alloc.Job.ID,
|
||||
JobModifyIndex: alloc.Job.ModifyIndex,
|
||||
Status: structs.EvalStatusPending,
|
||||
CreateTime: now,
|
||||
ModifyTime: now,
|
||||
}
|
||||
|
||||
transitionReq := &structs.AllocUpdateDesiredTransitionRequest{
|
||||
Evals: []*structs.Evaluation{eval},
|
||||
Allocs: map[string]*structs.DesiredTransition{
|
||||
args.AllocID: {
|
||||
Migrate: pointer.Of(true),
|
||||
NoShutdownDelay: pointer.Of(args.NoShutdownDelay),
|
||||
Reschedule: pointer.Of(args.Reschedule),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Commit this update via Raft
|
||||
_, index, err := a.srv.raftApply(structs.AllocUpdateDesiredTransitionRequestType, transitionReq)
|
||||
if err != nil {
|
||||
a.logger.Error("AllocUpdateDesiredTransitionRequest failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup the response
|
||||
reply.Index = index
|
||||
reply.EvalID = eval.ID
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDesiredTransition is used to update the desired transitions of an
|
||||
// allocation.
|
||||
func (a *Alloc) UpdateDesiredTransition(args *structs.AllocUpdateDesiredTransitionRequest, reply *structs.GenericResponse) error {
|
||||
authErr := a.srv.Authenticate(a.ctx, args)
|
||||
if done, err := a.srv.forward("Alloc.UpdateDesiredTransition", args, args, reply); done {
|
||||
return err
|
||||
}
|
||||
a.srv.MeasureRPCRate("alloc", structs.RateMetricWrite, args)
|
||||
if authErr != nil {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
defer metrics.MeasureSince([]string{"nomad", "alloc", "update_desired_transition"}, time.Now())
|
||||
|
||||
// Check that it is a management token.
|
||||
if aclObj, err := a.srv.ResolveACL(args); err != nil {
|
||||
return err
|
||||
} else if !aclObj.IsManagement() {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Ensure at least a single alloc
|
||||
if len(args.Allocs) == 0 {
|
||||
return fmt.Errorf("must update at least one allocation")
|
||||
}
|
||||
|
||||
// Commit this update via Raft
|
||||
_, index, err := a.srv.raftApply(structs.AllocUpdateDesiredTransitionRequestType, args)
|
||||
if err != nil {
|
||||
a.logger.Error("AllocUpdateDesiredTransitionRequest failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup the response
|
||||
reply.Index = index
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetServiceRegistrations returns a list of service registrations which belong
|
||||
// to the passed allocation ID.
|
||||
func (a *Alloc) GetServiceRegistrations(
|
||||
args *structs.AllocServiceRegistrationsRequest,
|
||||
reply *structs.AllocServiceRegistrationsResponse) error {
|
||||
|
||||
authErr := a.srv.Authenticate(a.ctx, args)
|
||||
if done, err := a.srv.forward(structs.AllocServiceRegistrationsRPCMethod, args, args, reply); done {
|
||||
return err
|
||||
}
|
||||
a.srv.MeasureRPCRate("alloc", structs.RateMetricList, args)
|
||||
if authErr != nil {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
defer metrics.MeasureSince([]string{"nomad", "alloc", "get_service_registrations"}, time.Now())
|
||||
|
||||
// Ensure the caller has the read-job namespace capability.
|
||||
aclObj, err := a.srv.ResolveACL(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Set up the blocking query.
|
||||
return a.srv.blockingRPC(&blockingOptions{
|
||||
queryOpts: &args.QueryOptions,
|
||||
queryMeta: &reply.QueryMeta,
|
||||
run: func(ws memdb.WatchSet, stateStore *state.StateStore) error {
|
||||
|
||||
// Read the allocation to ensure its namespace matches the request
|
||||
// args.
|
||||
alloc, err := stateStore.AllocByID(ws, args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Guard against the alloc not-existing or that the namespace does
|
||||
// not match the request arguments.
|
||||
if alloc == nil || alloc.Namespace != args.RequestNamespace() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Perform the state query to get an iterator.
|
||||
iter, err := stateStore.GetServiceRegistrationsByAllocID(ws, args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set up our output after we have checked the error.
|
||||
services := make([]*structs.ServiceRegistration, 0)
|
||||
|
||||
// Iterate the iterator, appending all service registrations
|
||||
// returned to the reply.
|
||||
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
||||
services = append(services, raw.(*structs.ServiceRegistration))
|
||||
}
|
||||
reply.Services = services
|
||||
|
||||
// Use the index table to populate the query meta as we have no way
|
||||
// of tracking the max index on deletes.
|
||||
return a.srv.setReplyQueryMeta(stateStore, state.TableServiceRegistrations, &reply.QueryMeta)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// SignIdentities allows nodes to retrieve workload identities for their
|
||||
// allocations.
|
||||
//
|
||||
// This is an internal-only RPC and not exposed via the HTTP API.
|
||||
func (a *Alloc) SignIdentities(args *structs.AllocIdentitiesRequest, reply *structs.AllocIdentitiesResponse) error {
|
||||
|
||||
aclObj, err := a.srv.AuthenticateClientOnly(a.ctx, args)
|
||||
if done, err := a.srv.forward("Alloc.SignIdentities", args, args, reply); done {
|
||||
return err
|
||||
}
|
||||
a.srv.MeasureRPCRate("alloc", structs.RateMetricRead, args)
|
||||
if err != nil {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "alloc", "sign_identities"}, time.Now())
|
||||
|
||||
if len(args.Identities) == 0 {
|
||||
// Client bug. Fail loudly instead of letting clients waste time with
|
||||
// noops.
|
||||
return fmt.Errorf("no identities requested")
|
||||
}
|
||||
|
||||
// Tracks whether the min index was satisfied by the blocking query
|
||||
thresholdMet := false
|
||||
|
||||
// Most if not all identity requests will be for the same alloc, so create a
|
||||
// set of alloc IDs to avoid unnecessary looping in the blocking query.
|
||||
allocs := make(map[string]*structs.Allocation, len(args.Identities))
|
||||
for _, idReq := range args.Identities {
|
||||
allocs[idReq.AllocID] = nil // to be set while watching
|
||||
}
|
||||
|
||||
opts := blockingOptions{
|
||||
queryOpts: &args.QueryOptions,
|
||||
queryMeta: &reply.QueryMeta,
|
||||
run: func(ws memdb.WatchSet, state *state.StateStore) error {
|
||||
var maxIndex uint64
|
||||
|
||||
// Lookup the allocations
|
||||
for allocID := range allocs {
|
||||
out, err := state.AllocByID(ws, allocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if out == nil {
|
||||
// Alloc may have been GC'd and therefore should not be able to get
|
||||
// identities signed.
|
||||
continue
|
||||
}
|
||||
|
||||
if err := a.srv.AuthorizeClientAllocation(aclObj, out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Keep the alloc around so we can skip the statestore lookup later
|
||||
allocs[allocID] = out
|
||||
|
||||
// If we have found a requested alloc created after the min query index
|
||||
// that means we're observing a new enough version of state to satisfy
|
||||
// the query.
|
||||
if out.CreateIndex > args.QueryOptions.MinQueryIndex {
|
||||
thresholdMet = true
|
||||
}
|
||||
|
||||
if maxIndex < out.CreateIndex {
|
||||
maxIndex = out.CreateIndex
|
||||
}
|
||||
}
|
||||
|
||||
// If we could not find an alloc created after the min index, note when
|
||||
// the index allocs were last updated.
|
||||
if !thresholdMet {
|
||||
index, err := state.Index("allocs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
maxIndex = index
|
||||
}
|
||||
|
||||
reply.Index = maxIndex
|
||||
return nil
|
||||
}}
|
||||
|
||||
if err := a.srv.blockingRPC(&opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Index threshold was not met in the blocking query. Set rejections since
|
||||
// allocs could not be found and should be considered invalid.
|
||||
if !thresholdMet {
|
||||
for _, idReq := range args.Identities {
|
||||
reply.Rejections = append(reply.Rejections, &structs.WorkloadIdentityRejection{
|
||||
WorkloadIdentityRequest: *idReq,
|
||||
Reason: structs.WIRejectionReasonMissingAlloc,
|
||||
})
|
||||
}
|
||||
|
||||
// Return early so the rest of the func acts as the else (thresholdMet)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Threshold met, so create the response
|
||||
now := time.Now().UTC()
|
||||
for _, idReq := range args.Identities {
|
||||
out := allocs[idReq.AllocID]
|
||||
|
||||
if out == nil {
|
||||
// Alloc may have been GC'd and therefore should not be able to get
|
||||
// new identities.
|
||||
reply.Rejections = append(reply.Rejections, &structs.WorkloadIdentityRejection{
|
||||
WorkloadIdentityRequest: *idReq,
|
||||
Reason: structs.WIRejectionReasonMissingAlloc,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
job := out.Job
|
||||
|
||||
switch idReq.WorkloadType {
|
||||
case structs.WorkloadTypeTask:
|
||||
task := out.LookupTask(idReq.WorkloadIdentifier)
|
||||
if task == nil {
|
||||
// Job has likely been updated to remove this task
|
||||
reply.Rejections = append(reply.Rejections, &structs.WorkloadIdentityRejection{
|
||||
WorkloadIdentityRequest: *idReq,
|
||||
Reason: structs.WIRejectionReasonMissingTask,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
widFound, err := a.signTasks(task, out, idReq, reply, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !widFound {
|
||||
reply.Rejections = append(reply.Rejections, &structs.WorkloadIdentityRejection{
|
||||
WorkloadIdentityRequest: *idReq,
|
||||
Reason: structs.WIRejectionReasonMissingIdentity,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
case structs.WorkloadTypeService:
|
||||
widFound, err := a.signServices(job, out, idReq, reply, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !widFound {
|
||||
reply.Rejections = append(reply.Rejections, &structs.WorkloadIdentityRejection{
|
||||
WorkloadIdentityRequest: *idReq,
|
||||
Reason: structs.WIRejectionReasonMissingIdentity,
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Alloc) signTasks(
|
||||
task *structs.Task,
|
||||
alloc *structs.Allocation,
|
||||
idReq *structs.WorkloadIdentityRequest,
|
||||
reply *structs.AllocIdentitiesResponse,
|
||||
now time.Time,
|
||||
) (widFound bool, err error) {
|
||||
for _, wid := range task.Identities {
|
||||
if wid.Name != idReq.IdentityName {
|
||||
continue
|
||||
}
|
||||
|
||||
widFound = true
|
||||
builder := structs.NewIdentityClaimsBuilder(alloc.Job, alloc, &idReq.WIHandle, wid).
|
||||
WithTask(task).
|
||||
WithConsul()
|
||||
|
||||
var node *structs.Node
|
||||
node, err = a.srv.State().NodeByID(nil, alloc.NodeID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
builder.WithNode(node)
|
||||
|
||||
vaultCfg := a.srv.GetConfig().GetVaultForIdentity(wid)
|
||||
if vaultCfg != nil && vaultCfg.DefaultIdentity != nil {
|
||||
builder.WithVault(vaultCfg.DefaultIdentity.ExtraClaims)
|
||||
}
|
||||
|
||||
claims := builder.Build(now)
|
||||
err = a.signClaims(claims, idReq, reply)
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Alloc) signServices(
|
||||
job *structs.Job,
|
||||
alloc *structs.Allocation,
|
||||
idReq *structs.WorkloadIdentityRequest,
|
||||
reply *structs.AllocIdentitiesResponse,
|
||||
now time.Time,
|
||||
) (widFound bool, err error) {
|
||||
wid := idReq.WIHandle
|
||||
|
||||
// services can be on the level of task groups or tasks
|
||||
for _, tg := range job.TaskGroups {
|
||||
for _, service := range tg.Services {
|
||||
if service.IdentityHandle(nil).Equal(wid) {
|
||||
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) {
|
||||
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) signClaims(
|
||||
claims *structs.IdentityClaims,
|
||||
idReq *structs.WorkloadIdentityRequest,
|
||||
reply *structs.AllocIdentitiesResponse,
|
||||
) error {
|
||||
token, _, err := a.srv.encrypter.SignClaims(claims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply.SignedIdentities = append(reply.SignedIdentities, &structs.SignedWorkloadIdentity{
|
||||
WorkloadIdentityRequest: *idReq,
|
||||
JWT: token,
|
||||
Expiration: claims.Expiry.Time(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user