Files
nomad/client/allocrunner/taskrunner/dynamic_users_hook.go
James Rasell 4c4cb2c6ad agent: Fix misaligned contextual k/v logging arguments. (#25629)
Arguments passed to hclog log lines should always have an even
number to provide the expected k/v output.
2025-04-10 14:40:21 +01:00

125 lines
3.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package taskrunner
import (
"context"
"fmt"
"sync"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/client/allocrunner/interfaces"
"github.com/hashicorp/nomad/helper/users/dynamic"
)
const (
dynamicUsersHookName = "workload_users"
dynamicUsersStateKey = "dynamic_user_ugid"
)
// dynamicUsersHook is used for allocating a one-time use UID/GID on behalf of
// a single workload (task). No other task will be assigned the same UID/GID
// while this task is running.
type dynamicUsersHook struct {
shutdownCtx context.Context
logger hclog.Logger
usable bool
lock *sync.Mutex
pool dynamic.Pool
}
func newDynamicUsersHook(ctx context.Context, usable bool, logger hclog.Logger, pool dynamic.Pool) *dynamicUsersHook {
return &dynamicUsersHook{
shutdownCtx: ctx,
logger: logger.Named(dynamicUsersHookName),
lock: new(sync.Mutex),
pool: pool,
usable: usable,
}
}
func (*dynamicUsersHook) Name() string {
return dynamicUsersHookName
}
// Prestart runs on both initial start and on restart.
func (h *dynamicUsersHook) Prestart(_ context.Context, request *interfaces.TaskPrestartRequest, response *interfaces.TaskPrestartResponse) error {
// if the task driver does not support the DynamicWorkloadUsers capability,
// do nothing
if !h.usable {
return nil
}
// if the task has a user set, do nothing
//
// it's up to the job-submitter to set a user that exists on the system
if request.Task.User != "" {
return nil
}
response.State = make(map[string]string, 1)
// if this is the restart case, the UGID will already be acquired and we
// just need to read it back out of the hook's state
if request.PreviousState != nil {
ugid, exists := request.PreviousState[dynamicUsersStateKey]
if exists {
response.State[dynamicUsersStateKey] = ugid
return nil
}
}
// otherwise we will acquire a dynamic UGID from the pool.
h.lock.Lock()
defer h.lock.Unlock()
// allocate an unused UID/GID from the pool
ugid, err := h.pool.Acquire()
if err != nil {
h.logger.Error("unable to acquire anonymous UID/GID", "error", err)
return err
}
h.logger.Trace("acquired dynamic workload user", "ugid", ugid)
// set the special user of the task
request.Task.User = dynamic.String(ugid)
// set the user on the hook so we may release it later
response.State[dynamicUsersStateKey] = request.Task.User
return nil
}
func (h *dynamicUsersHook) Stop(_ context.Context, request *interfaces.TaskStopRequest, response *interfaces.TaskStopResponse) error {
// if the task driver does not support the DWU capability, nothing to do
if !h.usable {
return nil
}
// if we did not store a user for this task; nothing to release
user, exists := request.ExistingState[dynamicUsersStateKey]
if !exists {
return nil
}
// otherwise we need to release the UGID back to the pool
h.lock.Lock()
defer h.lock.Unlock()
// parse the UID/GID from the pseudo username
ugid, err := dynamic.Parse(user)
if err != nil {
return fmt.Errorf("unable to release dynamic workload user: %w", err)
}
// release the UID/GID to the pool
if err = h.pool.Release(ugid); err != nil {
return fmt.Errorf("unable to release dynamic workload user: %w", err)
}
h.logger.Trace("released dynamic workload user", "ugid", ugid)
return nil
}