Secret dir materialized in alloc/task directory

This commit is contained in:
Alex Dadgar 2016-09-02 12:44:05 -07:00
parent c5035de902
commit eef786dd9d
19 changed files with 179 additions and 10 deletions

View file

@ -46,6 +46,10 @@ var (
// regardless of driver. // regardless of driver.
TaskLocal = "local" TaskLocal = "local"
// TaskSecrets is the the name of the secret directory inside each task
// directory
TaskSecrets = "secrets"
// TaskDirs is the set of directories created in each tasks directory. // TaskDirs is the set of directories created in each tasks directory.
TaskDirs = []string{"tmp"} TaskDirs = []string{"tmp"}
) )
@ -154,6 +158,14 @@ func (d *AllocDir) UnmountAll() error {
} }
} }
taskSecret := filepath.Join(dir, TaskSecrets)
if d.pathExists(taskSecret) {
if err := d.removeSecretDir(taskSecret); err != nil {
mErr.Errors = append(mErr.Errors,
fmt.Errorf("failed to remove the secret dir %q: %v", taskSecret, err))
}
}
// Unmount dev/ and proc/ have been mounted. // Unmount dev/ and proc/ have been mounted.
d.unmountSpecialDirs(dir) d.unmountSpecialDirs(dir)
} }
@ -223,6 +235,16 @@ func (d *AllocDir) Build(tasks []*structs.Task) error {
return err return err
} }
} }
// Create the secret directory
secret := filepath.Join(taskDir, TaskSecrets)
if err := d.createSecretDir(secret); err != nil {
return err
}
if err := d.dropDirPermissions(secret); err != nil {
return err
}
} }
return nil return nil

View file

@ -1,6 +1,7 @@
package allocdir package allocdir
import ( import (
"os"
"syscall" "syscall"
) )
@ -14,6 +15,16 @@ func (d *AllocDir) unmountSharedDir(dir string) error {
return syscall.Unlink(dir) return syscall.Unlink(dir)
} }
// createSecretDir creates the secrets dir folder at the given path
func (d *AllocDir) createSecretDir(dir string) error {
return os.MkdirAll(dir, 0777)
}
// removeSecretDir removes the secrets dir folder
func (d *AllocDir) removeSecretDir(dir string) error {
return os.RemoveAll(dir)
}
// MountSpecialDirs mounts the dev and proc file system on the chroot of the // MountSpecialDirs mounts the dev and proc file system on the chroot of the
// task. It's a no-op on darwin. // task. It's a no-op on darwin.
func (d *AllocDir) MountSpecialDirs(taskDir string) error { func (d *AllocDir) MountSpecialDirs(taskDir string) error {

View file

@ -1,6 +1,7 @@
package allocdir package allocdir
import ( import (
"os"
"syscall" "syscall"
) )
@ -14,6 +15,16 @@ func (d *AllocDir) unmountSharedDir(dir string) error {
return syscall.Unlink(dir) return syscall.Unlink(dir)
} }
// createSecretDir creates the secrets dir folder at the given path
func (d *AllocDir) createSecretDir(dir string) error {
return os.MkdirAll(dir, 0777)
}
// removeSecretDir removes the secrets dir folder
func (d *AllocDir) removeSecretDir(dir string) error {
return os.RemoveAll(dir)
}
// MountSpecialDirs mounts the dev and proc file system on the chroot of the // MountSpecialDirs mounts the dev and proc file system on the chroot of the
// task. It's a no-op on FreeBSD right now. // task. It's a no-op on FreeBSD right now.
func (d *AllocDir) MountSpecialDirs(taskDir string) error { func (d *AllocDir) MountSpecialDirs(taskDir string) error {

View file

@ -6,9 +6,16 @@ import (
"path/filepath" "path/filepath"
"syscall" "syscall"
"golang.org/x/sys/unix"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
) )
const (
// secretDirTmpfsSize is the size of the tmpfs per task in MBs
secretDirTmpfsSize = 1
)
// Bind mounts the shared directory into the task directory. Must be root to // Bind mounts the shared directory into the task directory. Must be root to
// run. // run.
func (d *AllocDir) mountSharedDir(taskDir string) error { func (d *AllocDir) mountSharedDir(taskDir string) error {
@ -23,6 +30,36 @@ func (d *AllocDir) unmountSharedDir(dir string) error {
return syscall.Unmount(dir, 0) return syscall.Unmount(dir, 0)
} }
// createSecretDir creates the secrets dir folder at the given path using a
// tmpfs
func (d *AllocDir) createSecretDir(dir string) error {
// Only mount the tmpfs if we are root
if unix.Geteuid() == 0 {
if err := os.MkdirAll(dir, 0777); err != nil {
return err
}
var flags uintptr
flags = syscall.MS_NOEXEC
options := fmt.Sprintf("size=%dm", secretDirTmpfsSize)
err := syscall.Mount("tmpfs", dir, "tmpfs", flags, options)
return os.NewSyscallError("mount", err)
}
return os.MkdirAll(dir, 0777)
}
// createSecretDir removes the secrets dir folder
func (d *AllocDir) removeSecretDir(dir string) error {
if unix.Geteuid() == 0 {
if err := syscall.Unmount(dir, 0); err != nil {
return err
}
}
return os.RemoveAll(dir)
}
// MountSpecialDirs mounts the dev and proc file system from the host to the // MountSpecialDirs mounts the dev and proc file system from the host to the
// chroot // chroot
func (d *AllocDir) MountSpecialDirs(taskDir string) error { func (d *AllocDir) MountSpecialDirs(taskDir string) error {

View file

@ -73,6 +73,10 @@ func TestAllocDir_BuildAlloc(t *testing.T) {
if _, err := os.Stat(tDir); os.IsNotExist(err) { if _, err := os.Stat(tDir); os.IsNotExist(err) {
t.Fatalf("Build(%v) didn't create TaskDir %v", tasks, tDir) t.Fatalf("Build(%v) didn't create TaskDir %v", tasks, tDir)
} }
if _, err := os.Stat(filepath.Join(tDir, TaskSecrets)); os.IsNotExist(err) {
t.Fatalf("Build(%v) didn't create secret dir %v", tasks)
}
} }
} }

View file

@ -14,11 +14,17 @@ import (
) )
var ( var (
//Path inside container for mounted directory shared across tasks in a task group. // SharedAllocContainerPath is the path inside container for mounted
// directory shared across tasks in a task group.
SharedAllocContainerPath = filepath.Join("/", SharedAllocName) SharedAllocContainerPath = filepath.Join("/", SharedAllocName)
//Path inside container for mounted directory for local storage. // TaskLocalContainer is the path inside a container for mounted directory
// for local storage.
TaskLocalContainerPath = filepath.Join("/", TaskLocal) TaskLocalContainerPath = filepath.Join("/", TaskLocal)
// TaskSecretsContainerPath is the path inside a container for mounted
// secrets directory
TaskSecretsContainerPath = filepath.Join("/", TaskSecrets)
) )
func (d *AllocDir) linkOrCopy(src, dst string, perm os.FileMode) error { func (d *AllocDir) linkOrCopy(src, dst string, perm os.FileMode) error {

View file

@ -7,11 +7,17 @@ import (
) )
var ( var (
//Path inside container for mounted directory that is shared across tasks in a task group. // SharedAllocContainerPath is the path inside container for mounted
// directory shared across tasks in a task group.
SharedAllocContainerPath = filepath.Join("c:\\", SharedAllocName) SharedAllocContainerPath = filepath.Join("c:\\", SharedAllocName)
//Path inside container for mounted directory for local storage. // TaskLocalContainer is the path inside a container for mounted directory
// for local storage.
TaskLocalContainerPath = filepath.Join("c:\\", TaskLocal) TaskLocalContainerPath = filepath.Join("c:\\", TaskLocal)
// TaskSecretsContainerPath is the path inside a container for mounted
// secrets directory
TaskSecretsContainerPath = filepath.Join("c:\\", TaskSecrets)
) )
func (d *AllocDir) linkOrCopy(src, dst string, perm os.FileMode) error { func (d *AllocDir) linkOrCopy(src, dst string, perm os.FileMode) error {
@ -23,6 +29,16 @@ func (d *AllocDir) mountSharedDir(dir string) error {
return errors.New("Mount on Windows not supported.") return errors.New("Mount on Windows not supported.")
} }
// createSecretDir creates the secrets dir folder at the given path
func (d *AllocDir) createSecretDir(dir string) error {
return os.MkdirAll(dir, 0777)
}
// removeSecretDir removes the secrets dir folder
func (d *AllocDir) removeSecretDir(dir string) error {
return os.RemoveAll(dir)
}
// The windows version does nothing currently. // The windows version does nothing currently.
func (d *AllocDir) dropDirPermissions(path string) error { func (d *AllocDir) dropDirPermissions(path string) error {
return nil return nil

View file

@ -575,6 +575,14 @@ func TestClient_SaveRestoreState(t *testing.T) {
}, func(err error) { }, func(err error) {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
}) })
// Destroy all the allocations
c2.allocLock.Lock()
for _, ar := range c2.allocs {
ar.Destroy()
<-ar.WaitCh()
}
c2.allocLock.Unlock()
} }
func TestClient_Init(t *testing.T) { func TestClient_Init(t *testing.T) {
@ -608,6 +616,7 @@ func TestClient_BlockedAllocations(t *testing.T) {
c1 := testClient(t, func(c *config.Config) { c1 := testClient(t, func(c *config.Config) {
c.RPCHandler = s1 c.RPCHandler = s1
}) })
defer c1.Shutdown()
// Wait for the node to be ready // Wait for the node to be ready
state := s1.State() state := s1.State()
@ -691,4 +700,13 @@ func TestClient_BlockedAllocations(t *testing.T) {
}, func(err error) { }, func(err error) {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
}) })
// Destroy all the allocations
c1.allocLock.Lock()
for _, ar := range c1.allocs {
ar.Destroy()
<-ar.WaitCh()
}
c1.allocLock.Unlock()
} }

View file

@ -381,6 +381,7 @@ func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task,
// Set environment variables. // Set environment variables.
d.taskEnv.SetAllocDir(allocdir.SharedAllocContainerPath) d.taskEnv.SetAllocDir(allocdir.SharedAllocContainerPath)
d.taskEnv.SetTaskLocalDir(allocdir.TaskLocalContainerPath) d.taskEnv.SetTaskLocalDir(allocdir.TaskLocalContainerPath)
d.taskEnv.SetTaskLocalDir(allocdir.TaskSecretsContainerPath)
config := &docker.Config{ config := &docker.Config{
Image: driverConfig.ImageName, Image: driverConfig.ImageName,

View file

@ -117,7 +117,8 @@ func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, DriverHandle
// This test should always pass, even if docker daemon is not available // This test should always pass, even if docker daemon is not available
func TestDockerDriver_Fingerprint(t *testing.T) { func TestDockerDriver_Fingerprint(t *testing.T) {
driverCtx, _ := testDriverContexts(&structs.Task{Name: "foo", Resources: basicResources}) driverCtx, execCtx := testDriverContexts(&structs.Task{Name: "foo", Resources: basicResources})
defer execCtx.AllocDir.Destroy()
d := NewDockerDriver(driverCtx) d := NewDockerDriver(driverCtx)
node := &structs.Node{ node := &structs.Node{
Attributes: make(map[string]string), Attributes: make(map[string]string),

View file

@ -153,6 +153,7 @@ func GetTaskEnv(allocDir *allocdir.AllocDir, node *structs.Node,
} }
env.SetTaskLocalDir(filepath.Join(taskdir, allocdir.TaskLocal)) env.SetTaskLocalDir(filepath.Join(taskdir, allocdir.TaskLocal))
env.SetSecretDir(filepath.Join(taskdir, allocdir.TaskSecrets))
} }
if task.Resources != nil { if task.Resources != nil {

View file

@ -21,6 +21,10 @@ const (
// removed. // removed.
TaskLocalDir = "NOMAD_TASK_DIR" TaskLocalDir = "NOMAD_TASK_DIR"
// SecretDir is the environment variable with the path to the tasks secret
// directory where it can store sensitive data.
SecretDir = "NOMAD_SECRET_DIR"
// MemLimit is the environment variable with the tasks memory limit in MBs. // MemLimit is the environment variable with the tasks memory limit in MBs.
MemLimit = "NOMAD_MEMORY_LIMIT" MemLimit = "NOMAD_MEMORY_LIMIT"
@ -79,6 +83,7 @@ type TaskEnvironment struct {
JobMeta map[string]string JobMeta map[string]string
AllocDir string AllocDir string
TaskDir string TaskDir string
SecretDir string
CpuLimit int CpuLimit int
MemLimit int MemLimit int
TaskName string TaskName string
@ -153,6 +158,9 @@ func (t *TaskEnvironment) Build() *TaskEnvironment {
if t.TaskDir != "" { if t.TaskDir != "" {
t.TaskEnv[TaskLocalDir] = t.TaskDir t.TaskEnv[TaskLocalDir] = t.TaskDir
} }
if t.SecretDir != "" {
t.TaskEnv[SecretDir] = t.SecretDir
}
// Build the resource limits // Build the resource limits
if t.MemLimit != 0 { if t.MemLimit != 0 {
@ -249,6 +257,16 @@ func (t *TaskEnvironment) ClearTaskLocalDir() *TaskEnvironment {
return t return t
} }
func (t *TaskEnvironment) SetSecretDir(dir string) *TaskEnvironment {
t.SecretDir = dir
return t
}
func (t *TaskEnvironment) ClearSecretDir() *TaskEnvironment {
t.SecretDir = ""
return t
}
func (t *TaskEnvironment) SetMemLimit(limit int) *TaskEnvironment { func (t *TaskEnvironment) SetMemLimit(limit int) *TaskEnvironment {
t.MemLimit = limit t.MemLimit = limit
return t return t

View file

@ -26,7 +26,8 @@ func TestExecDriver_Fingerprint(t *testing.T) {
Name: "foo", Name: "foo",
Resources: structs.DefaultResources(), Resources: structs.DefaultResources(),
} }
driverCtx, _ := testDriverContexts(task) driverCtx, execCtx := testDriverContexts(task)
defer execCtx.AllocDir.Destroy()
d := NewExecDriver(driverCtx) d := NewExecDriver(driverCtx)
node := &structs.Node{ node := &structs.Node{
Attributes: map[string]string{ Attributes: map[string]string{

View file

@ -81,7 +81,23 @@ func TestExecutor_IsolationAndConstraints(t *testing.T) {
t.Fatalf("file %v hasn't been removed", memLimits) t.Fatalf("file %v hasn't been removed", memLimits)
} }
expected := "/:\nalloc/\nbin/\ndev/\netc/\nlib/\nlib64/\nlocal/\nproc/\ntmp/\nusr/\n\n/etc/:\nld.so.cache\nld.so.conf\nld.so.conf.d/" expected := `/:
alloc/
bin/
dev/
etc/
lib/
lib64/
local/
proc/
secrets/
tmp/
usr/
/etc/:
ld.so.cache
ld.so.conf
ld.so.conf.d/`
file := filepath.Join(ctx.AllocDir.LogDir(), "web.stdout.0") file := filepath.Join(ctx.AllocDir.LogDir(), "web.stdout.0")
output, err := ioutil.ReadFile(file) output, err := ioutil.ReadFile(file)
if err != nil { if err != nil {

View file

@ -35,7 +35,8 @@ func TestJavaDriver_Fingerprint(t *testing.T) {
Name: "foo", Name: "foo",
Resources: structs.DefaultResources(), Resources: structs.DefaultResources(),
} }
driverCtx, _ := testDriverContexts(task) driverCtx, execCtx := testDriverContexts(task)
defer execCtx.AllocDir.Destroy()
d := NewJavaDriver(driverCtx) d := NewJavaDriver(driverCtx)
node := &structs.Node{ node := &structs.Node{
Attributes: map[string]string{ Attributes: map[string]string{

View file

@ -19,7 +19,8 @@ func TestQemuDriver_Fingerprint(t *testing.T) {
Name: "foo", Name: "foo",
Resources: structs.DefaultResources(), Resources: structs.DefaultResources(),
} }
driverCtx, _ := testDriverContexts(task) driverCtx, execCtx := testDriverContexts(task)
defer execCtx.AllocDir.Destroy()
d := NewQemuDriver(driverCtx) d := NewQemuDriver(driverCtx)
node := &structs.Node{ node := &structs.Node{
Attributes: make(map[string]string), Attributes: make(map[string]string),

View file

@ -21,7 +21,8 @@ func TestRawExecDriver_Fingerprint(t *testing.T) {
Name: "foo", Name: "foo",
Resources: structs.DefaultResources(), Resources: structs.DefaultResources(),
} }
driverCtx, _ := testDriverContexts(task) driverCtx, execCtx := testDriverContexts(task)
defer execCtx.AllocDir.Destroy()
d := NewRawExecDriver(driverCtx) d := NewRawExecDriver(driverCtx)
node := &structs.Node{ node := &structs.Node{
Attributes: make(map[string]string), Attributes: make(map[string]string),

View file

@ -376,6 +376,7 @@ func TestRktTaskValidate(t *testing.T) {
"dns_servers": []string{"8.8.8.8", "8.8.4.4"}, "dns_servers": []string{"8.8.8.8", "8.8.4.4"},
"dns_search_domains": []string{"example.com", "example.org", "example.net"}, "dns_search_domains": []string{"example.com", "example.org", "example.net"},
}, },
Resources: basicResources,
} }
driverCtx, execCtx := testDriverContexts(task) driverCtx, execCtx := testDriverContexts(task)
defer execCtx.AllocDir.Destroy() defer execCtx.AllocDir.Destroy()

View file

@ -385,6 +385,8 @@ func TestTaskRunner_Download_Retries(t *testing.T) {
func TestTaskRunner_Validate_UserEnforcement(t *testing.T) { func TestTaskRunner_Validate_UserEnforcement(t *testing.T) {
_, tr := testTaskRunner(false) _, tr := testTaskRunner(false)
defer tr.Destroy(structs.NewTaskEvent(structs.TaskKilled))
defer tr.ctx.AllocDir.Destroy()
// Try to run as root with exec. // Try to run as root with exec.
tr.task.Driver = "exec" tr.task.Driver = "exec"