client: enable specifying user/group permissions in the template stanza (#13755)

* Adds Uid/Gid parameters to template.

* Updated diff_test

* fixed order

* update jobspec and api

* removed obsolete code

* helper functions for jobspec parse test

* updated documentation

* adjusted API jobs test.

* propagate uid/gid setting to job_endpoint

* adjusted job_endpoint tests

* making uid/gid into pointers

* refactor

* updated documentation

* updated documentation

* Update client/allocrunner/taskrunner/template/template_test.go

Co-authored-by: Luiz Aoqui <luiz@hashicorp.com>

* Update website/content/api-docs/json-jobs.mdx

Co-authored-by: Luiz Aoqui <luiz@hashicorp.com>

* propagating documentation change from Luiz

* formatting

* changelog entry

* changed changelog entry

Co-authored-by: Luiz Aoqui <luiz@hashicorp.com>
This commit is contained in:
Piotr Kazmierczak 2022-08-02 22:15:38 +02:00 committed by GitHub
parent 13bf88fdf7
commit 530280505f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 146 additions and 30 deletions

3
.changelog/13755.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
template: Templates support new uid/gid parameter pair
```

View File

@ -765,6 +765,8 @@ func TestJobs_Canonicalize(t *testing.T) {
ChangeSignal: stringToPtr(""),
Splay: timeToPtr(5 * time.Second),
Perms: stringToPtr("0644"),
Uid: intToPtr(0),
Gid: intToPtr(0),
LeftDelim: stringToPtr("{{"),
RightDelim: stringToPtr("}}"),
Envvars: boolToPtr(false),
@ -778,6 +780,8 @@ func TestJobs_Canonicalize(t *testing.T) {
ChangeSignal: stringToPtr(""),
Splay: timeToPtr(5 * time.Second),
Perms: stringToPtr("0644"),
Uid: intToPtr(0),
Gid: intToPtr(0),
LeftDelim: stringToPtr("{{"),
RightDelim: stringToPtr("}}"),
Envvars: boolToPtr(true),

View File

@ -799,6 +799,8 @@ type Template struct {
ChangeSignal *string `mapstructure:"change_signal" hcl:"change_signal,optional"`
Splay *time.Duration `mapstructure:"splay" hcl:"splay,optional"`
Perms *string `mapstructure:"perms" hcl:"perms,optional"`
Uid *int `mapstructure:"uid" hcl:"uid,optional"`
Gid *int `mapstructure:"gid" hcl:"gid,optional"`
LeftDelim *string `mapstructure:"left_delimiter" hcl:"left_delimiter,optional"`
RightDelim *string `mapstructure:"right_delimiter" hcl:"right_delimiter,optional"`
Envvars *bool `mapstructure:"env" hcl:"env,optional"`
@ -835,6 +837,12 @@ func (tmpl *Template) Canonicalize() {
if tmpl.Perms == nil {
tmpl.Perms = stringToPtr("0644")
}
if tmpl.Uid == nil {
tmpl.Uid = intToPtr(0)
}
if tmpl.Gid == nil {
tmpl.Gid = intToPtr(0)
}
if tmpl.LeftDelim == nil {
tmpl.LeftDelim = stringToPtr("{{")
}

View File

@ -626,6 +626,12 @@ func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[*ctconf.Templa
m := os.FileMode(v)
ct.Perms = &m
}
// Set ownership
if tmpl.Uid >= 0 && tmpl.Gid >= 0 {
ct.Uid = &tmpl.Uid
ct.Gid = &tmpl.Gid
}
ct.Finalize()
ctmpls[ct] = tmpl

View File

@ -16,6 +16,7 @@ import (
"strconv"
"strings"
"sync"
"syscall"
"testing"
"time"
@ -33,6 +34,7 @@ import (
sconfig "github.com/hashicorp/nomad/nomad/structs/config"
"github.com/hashicorp/nomad/testutil"
"github.com/kr/pretty"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -512,6 +514,8 @@ func TestTaskTemplateManager_Permissions(t *testing.T) {
DestPath: file,
ChangeMode: structs.TemplateChangeModeNoop,
Perms: "777",
Uid: 503,
Gid: 20,
}
harness := newTestHarness(t, []*structs.Template{template}, false, false)
@ -535,6 +539,13 @@ func TestTaskTemplateManager_Permissions(t *testing.T) {
if m := fi.Mode(); m != os.ModePerm {
t.Fatalf("Got mode %v; want %v", m, os.ModePerm)
}
sys := fi.Sys()
uid := int(sys.(*syscall.Stat_t).Uid)
gid := int(sys.(*syscall.Stat_t).Gid)
must.Eq(t, template.Uid, uid)
must.Eq(t, template.Gid, gid)
}
func TestTaskTemplateManager_Unblock_Static_NomadEnv(t *testing.T) {

View File

@ -1209,6 +1209,14 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup,
if len(apiTask.Templates) > 0 {
structsTask.Templates = []*structs.Template{}
for _, template := range apiTask.Templates {
uid := -1
if template.Uid != nil {
uid = *template.Uid
}
gid := -1
if template.Gid != nil {
gid = *template.Gid
}
structsTask.Templates = append(structsTask.Templates,
&structs.Template{
SourcePath: *template.SourcePath,
@ -1218,6 +1226,8 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup,
ChangeSignal: *template.ChangeSignal,
Splay: *template.Splay,
Perms: *template.Perms,
Uid: uid,
Gid: gid,
LeftDelim: *template.LeftDelim,
RightDelim: *template.RightDelim,
Envvars: *template.Envvars,

View File

@ -2733,6 +2733,8 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
ChangeSignal: helper.StringToPtr("signal"),
Splay: helper.TimeToPtr(1 * time.Minute),
Perms: helper.StringToPtr("666"),
Uid: helper.IntToPtr(1000),
Gid: helper.IntToPtr(1000),
LeftDelim: helper.StringToPtr("abc"),
RightDelim: helper.StringToPtr("def"),
Envvars: helper.BoolToPtr(true),
@ -3138,6 +3140,8 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
ChangeSignal: "SIGNAL",
Splay: 1 * time.Minute,
Perms: "666",
Uid: 1000,
Gid: 1000,
LeftDelim: "abc",
RightDelim: "def",
Envvars: true,

View File

@ -18,6 +18,11 @@ func stringToPtr(str string) *string {
return &str
}
// intToPtr returns the pointer to an int
func intToPtr(i int) *int {
return &i
}
// timeToPtr returns the pointer to a time.Duration.
func timeToPtr(t time.Duration) *time.Duration {
return &t

View File

@ -1,24 +0,0 @@
package jobspec
// These functions are copied from helper/funcs.go
// added here to avoid jobspec depending on any other package
// intToPtr returns the pointer to an int
func intToPtr(i int) *int {
return &i
}
// int8ToPtr returns the pointer to an int8
func int8ToPtr(i int8) *int8 {
return &i
}
// int64ToPtr returns the pointer to an int
func int64ToPtr(i int64) *int64 {
return &i
}
// Uint64ToPtr returns the pointer to an uint64
func uint64ToPtr(u uint64) *uint64 {
return &u
}

View File

@ -441,6 +441,8 @@ func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error {
"destination",
"left_delimiter",
"perms",
"uid",
"gid",
"right_delimiter",
"source",
"splay",
@ -460,6 +462,8 @@ func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error {
ChangeMode: stringToPtr("restart"),
Splay: timeToPtr(5 * time.Second),
Perms: stringToPtr("0644"),
Uid: intToPtr(0),
Gid: intToPtr(0),
}
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{

View File

@ -24,6 +24,17 @@ const (
templateChangeModeRestart = "restart"
)
// Helper functions below are only used by this test suite
func int8ToPtr(i int8) *int8 {
return &i
}
func uint64ToPtr(u uint64) *uint64 {
return &u
}
func int64ToPtr(i int64) *int64 {
return &i
}
func TestParse(t *testing.T) {
ci.Parallel(t)
@ -363,6 +374,8 @@ func TestParse(t *testing.T) {
ChangeSignal: stringToPtr("foo"),
Splay: timeToPtr(10 * time.Second),
Perms: stringToPtr("0644"),
Uid: intToPtr(0),
Gid: intToPtr(0),
Envvars: boolToPtr(true),
VaultGrace: timeToPtr(33 * time.Second),
},
@ -372,6 +385,8 @@ func TestParse(t *testing.T) {
ChangeMode: stringToPtr(templateChangeModeRestart),
Splay: timeToPtr(5 * time.Second),
Perms: stringToPtr("777"),
Uid: intToPtr(1001),
Gid: intToPtr(20),
LeftDelim: stringToPtr("--"),
RightDelim: stringToPtr("__"),
},

View File

@ -318,6 +318,8 @@ job "binstore-storagelocker" {
source = "bar"
destination = "bar"
perms = "777"
uid = 1001
gid = 20
left_delimiter = "--"
right_delimiter = "__"
}

View File

@ -1,5 +0,0 @@
package jobspec2
func intToPtr(v int) *int {
return &v
}

View File

@ -107,6 +107,12 @@ func normalizeTemplates(templates []*api.Template) {
if t.Perms == nil {
t.Perms = stringToPtr("0644")
}
if t.Uid == nil {
t.Uid = intToPtr(0)
}
if t.Gid == nil {
t.Gid = intToPtr(0)
}
if t.Splay == nil {
t.Splay = durationToPtr(5 * time.Second)
}
@ -121,6 +127,10 @@ func boolToPtr(v bool) *bool {
return &v
}
func intToPtr(v int) *int {
return &v
}
func stringToPtr(v string) *string {
return &v
}

View File

@ -7044,6 +7044,8 @@ func TestTaskDiff(t *testing.T) {
ChangeSignal: "SIGHUP",
Splay: 1,
Perms: "0644",
Uid: 1001,
Gid: 21,
Wait: &WaitConfig{
Min: helper.TimeToPtr(5 * time.Second),
Max: helper.TimeToPtr(5 * time.Second),
@ -7057,6 +7059,8 @@ func TestTaskDiff(t *testing.T) {
ChangeSignal: "SIGHUP2",
Splay: 2,
Perms: "0666",
Uid: 1000,
Gid: 20,
Envvars: true,
},
},
@ -7071,6 +7075,8 @@ func TestTaskDiff(t *testing.T) {
ChangeSignal: "SIGHUP",
Splay: 1,
Perms: "0644",
Uid: 1001,
Gid: 21,
Wait: &WaitConfig{
Min: helper.TimeToPtr(5 * time.Second),
Max: helper.TimeToPtr(10 * time.Second),
@ -7084,6 +7090,8 @@ func TestTaskDiff(t *testing.T) {
ChangeSignal: "SIGHUP3",
Splay: 3,
Perms: "0776",
Uid: 1002,
Gid: 22,
Wait: &WaitConfig{
Min: helper.TimeToPtr(5 * time.Second),
Max: helper.TimeToPtr(10 * time.Second),
@ -7154,6 +7162,12 @@ func TestTaskDiff(t *testing.T) {
Old: "",
New: "false",
},
{
Type: DiffTypeAdded,
Name: "Gid",
Old: "",
New: "22",
},
{
Type: DiffTypeAdded,
Name: "Perms",
@ -7172,6 +7186,12 @@ func TestTaskDiff(t *testing.T) {
Old: "",
New: "3",
},
{
Type: DiffTypeAdded,
Name: "Uid",
Old: "",
New: "1002",
},
{
Type: DiffTypeAdded,
Name: "VaultGrace",
@ -7234,6 +7254,12 @@ func TestTaskDiff(t *testing.T) {
Old: "true",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "Gid",
Old: "20",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "Perms",
@ -7252,6 +7278,12 @@ func TestTaskDiff(t *testing.T) {
Old: "2",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "Uid",
Old: "1000",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "VaultGrace",

View File

@ -7711,6 +7711,9 @@ type Template struct {
// Perms is the permission the file should be written out with.
Perms string
// User and group that should own the file.
Uid int
Gid int
// LeftDelim and RightDelim are optional configurations to control what
// delimiter is utilized when parsing the template.

View File

@ -1077,6 +1077,20 @@ README][ct].
- `Perms` - Specifies the rendered template's permissions. File permissions are
given as octal of the Unix file permissions `rwxrwxrwx`.
- `Uid` - Specifies the rendered template owner's user ID.
~> **Caveat:** Works only on Unix-based systems. Be careful when using
containerized drivers, suck as `docker` or `podman`, as groups and users
inside the container may have different IDs than on the host system. This
feature will also **not** work with Docker Desktop.
- `Gid` - Specifies the rendered template owner's group ID.
~> **Caveat:** Works only on Unix-based systems. Be careful when using
containerized drivers, suck as `docker` or `podman`, as groups and users
inside the container may have different IDs than on the host system. This
feature will also **not** work with Docker Desktop.
- `RightDelim` - Specifies the right delimiter to use in the template. The default
is "}}" for some templates, it may be easier to use a different delimiter that
does not conflict with the output file itself.

View File

@ -84,6 +84,20 @@ refer to the [Learn Go Template Syntax][gt_learn] Learn guide.
- `perms` `(string: "644")` - Specifies the rendered template's permissions.
File permissions are given as octal of the Unix file permissions `rwxrwxrwx`.
- `uid` `(int: 0)` - Specifies the rendered template owner's user ID.
~> **Caveat:** Works only on Unix-based systems. Be careful when using
containerized drivers, suck as `docker` or `podman`, as groups and users
inside the container may have different IDs than on the host system. This
feature will also **not** work with Docker Desktop.
- `gid` `(int: 0)` - Specifies the rendered template owner's group ID.
~> **Caveat:** Works only on Unix-based systems. Be careful when using
containerized drivers, suck as `docker` or `podman`, as groups and users
inside the container may have different IDs than on the host system. This
feature will also **not** work with Docker Desktop.
- `right_delimiter` `(string: "}}")` - Specifies the right delimiter to use in the
template. The default is "}}" for some templates, it may be easier to use a
different delimiter that does not conflict with the output file itself.
@ -561,4 +575,4 @@ options](/docs/configuration/client#options):
[task working directory]: /docs/runtime/environment#task-directories 'Task Directories'
[filesystem internals]: /docs/concepts/filesystem#templates-artifacts-and-dispatch-payloads
[`client.template.wait_bounds`]: /docs/configuration/client#wait_bounds
[rhash]: https://en.wikipedia.org/wiki/Rendezvous_hashing
[rhash]: https://en.wikipedia.org/wiki/Rendezvous_hashing