tr: add validate task hook

This commit is contained in:
Michael Schurter 2018-07-18 16:51:50 -07:00
parent 7f4ec50906
commit 9a63d6103d
5 changed files with 133 additions and 1 deletions

View file

@ -664,6 +664,7 @@ func TestTaskRunner_UnregisterConsul_Retries(t *testing.T) {
} }
} }
//XXX Ported to allocrunnerv2/task_runner/validate_hook_test.go
func TestTaskRunner_Validate_UserEnforcement(t *testing.T) { func TestTaskRunner_Validate_UserEnforcement(t *testing.T) {
t.Parallel() t.Parallel()
ctx := testTaskRunner(t, false) ctx := testTaskRunner(t, false)

View file

@ -18,6 +18,7 @@ func (tr *TaskRunner) initHooks() {
// Create the task directory hook. This is run first to ensure the // Create the task directory hook. This is run first to ensure the
// directoy path exists for other hooks. // directoy path exists for other hooks.
tr.runnerHooks = []interfaces.TaskHook{ tr.runnerHooks = []interfaces.TaskHook{
newValidateHook(tr.clientConfig, hookLogger),
newTaskDirHook(tr, hookLogger), newTaskDirHook(tr, hookLogger),
newArtifactHook(tr, hookLogger), newArtifactHook(tr, hookLogger),
newShutdownDelayHook(task.ShutdownDelay, hookLogger), newShutdownDelayHook(task.ShutdownDelay, hookLogger),

View file

@ -0,0 +1,66 @@
package taskrunner
import (
"context"
"fmt"
log "github.com/hashicorp/go-hclog"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/nomad/client/allocrunnerv2/interfaces"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/driver/env"
"github.com/hashicorp/nomad/nomad/structs"
)
// validateHook validates the task is able to be run.
type validateHook struct {
config *config.Config
logger log.Logger
}
func newValidateHook(config *config.Config, logger log.Logger) *validateHook {
h := &validateHook{
config: config,
}
h.logger = logger.Named(h.Name())
return h
}
func (*validateHook) Name() string {
return "validate"
}
func (h *validateHook) Prestart(ctx context.Context, req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {
if err := validateTask(req.Task, req.TaskEnv, h.config); err != nil {
return err
}
resp.Done = true
return nil
}
func validateTask(task *structs.Task, taskEnv *env.TaskEnv, conf *config.Config) error {
var mErr multierror.Error
// Validate the user
unallowedUsers := conf.ReadStringListToMapDefault("user.blacklist", config.DefaultUserBlacklist)
checkDrivers := conf.ReadStringListToMapDefault("user.checked_drivers", config.DefaultUserCheckedDrivers)
if _, driverMatch := checkDrivers[task.Driver]; driverMatch {
if _, unallowed := unallowedUsers[task.User]; unallowed {
mErr.Errors = append(mErr.Errors, fmt.Errorf("running as user %q is disallowed", task.User))
}
}
// Validate the Service names once they're interpolated
for i, service := range task.Services {
name := taskEnv.ReplaceEnv(service.Name)
if err := service.ValidateName(name); err != nil {
mErr.Errors = append(mErr.Errors, fmt.Errorf("service (%d) failed validation: %v", i, err))
}
}
if len(mErr.Errors) == 1 {
return mErr.Errors[0]
}
return mErr.ErrorOrNil()
}

View file

@ -0,0 +1,63 @@
package taskrunner
import (
"testing"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/driver/env"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/require"
)
func TestTaskRunner_Validate_UserEnforcement(t *testing.T) {
t.Parallel()
taskEnv := env.NewEmptyBuilder().Build()
conf := config.DefaultConfig()
// Try to run as root with exec.
task := &structs.Task{
Driver: "exec",
User: "root",
}
if err := validateTask(task, taskEnv, conf); err == nil {
t.Fatalf("expected error running as root with exec")
}
// Try to run a non-blacklisted user with exec.
task.User = "foobar"
require.NoError(t, validateTask(task, taskEnv, conf))
// Try to run as root with docker.
task.Driver = "docker"
task.User = "root"
require.NoError(t, validateTask(task, taskEnv, conf))
}
func TestTaskRunner_Validate_ServiceName(t *testing.T) {
t.Parallel()
builder := env.NewEmptyBuilder()
conf := config.DefaultConfig()
// Create a task with a service for validation
task := &structs.Task{
Services: []*structs.Service{
{
Name: "ok",
},
},
}
require.NoError(t, validateTask(task, builder.Build(), conf))
// Add an env var that should validate
builder.SetGenericEnv(map[string]string{"FOO": "bar"})
task.Services[0].Name = "${FOO}"
require.NoError(t, validateTask(task, builder.Build(), conf))
// Add an env var that should *not* validate
builder.SetGenericEnv(map[string]string{"BAD": "invalid/in/consul"})
task.Services[0].Name = "${BAD}"
require.Error(t, validateTask(task, builder.Build(), conf))
}

View file

@ -247,7 +247,8 @@ func NewBuilder(node *structs.Node, alloc *structs.Allocation, task *structs.Tas
// NewEmptyBuilder creates a new environment builder. // NewEmptyBuilder creates a new environment builder.
func NewEmptyBuilder() *Builder { func NewEmptyBuilder() *Builder {
return &Builder{ return &Builder{
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
envvars: make(map[string]string),
} }
} }