Skip to content

Commit

Permalink
Allow setting user/group ownership of templete output
Browse files Browse the repository at this point in the history
Related issues hashicorp#639, hashicorp#1497, hashicorp#1185 and also nomad/5020

Signed-off-by: Alessandro De Blasis <[email protected]>
  • Loading branch information
deblasis committed Nov 3, 2021
1 parent 1402218 commit 24cb142
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 1 deletion.
42 changes: 42 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,48 @@ func TestParse(t *testing.T) {
},
false,
},
{
"template_uid",
`template {
uid = 1000
}`,
&Config{
Templates: &TemplateConfigs{
&TemplateConfig{
Uid: Int(1000),
},
},
},
false,
},
{
"template_gid",
`template {
gid = 1000
}`,
&Config{
Templates: &TemplateConfigs{
&TemplateConfig{
Gid: Int(1000),
},
},
},
false,
},
{
"template_uid_gid_default",
`template {
}`,
&Config{
Templates: &TemplateConfigs{
&TemplateConfig{
Uid: nil,
Gid: nil,
},
},
},
false,
},
{
"template_source",
`template {
Expand Down
14 changes: 14 additions & 0 deletions config/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ type TemplateConfig struct {
// secrets from Vault.
Perms *os.FileMode `mapstructure:"perms"`

// Uid is the numeric uid that will be set when creating the file on disk.
// Useful when simply setting Perms is not enough.
//
// Platform dependent: this doesn't work on Windows but it fails gracefully
// with a warning
Uid *int `mapstructure:"uid"`

// Gid is the numeric gid that will be set when creating the file on disk.
// Useful when simply setting Perms is not enough.
//
// Platform dependent: this doesn't work on Windows but it fails gracefully
// with a warning
Gid *int `mapstructure:"gid"`

// Source is the path on disk to the template contents to evaluate. Either
// this or Contents should be specified, but not both.
Source *string `mapstructure:"source"`
Expand Down
2 changes: 2 additions & 0 deletions manager/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,8 @@ func (r *Runner) runTemplate(tmpl *template.Template, runCtx *templateRunCtx) (*
DryStream: r.outStream,
Path: config.StringVal(templateConfig.Destination),
Perms: config.FileModeVal(templateConfig.Perms),
Uid: templateConfig.Uid,
Gid: templateConfig.Gid,
})
if err != nil {
return nil, errors.Wrap(err, "error rendering "+templateConfig.Display())
Expand Down
49 changes: 49 additions & 0 deletions renderer/file_ownership.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//go:build !windows
// +build !windows

package renderer

import (
"os"
"syscall"
)

func getFileOwnership(path string) (*int, *int) {
file_info, err := os.Stat(path)

if err != nil {
return nil, nil
}

file_sys := file_info.Sys()
st := file_sys.(*syscall.Stat_t)
return intPtr(int(st.Uid)), intPtr(int(st.Gid))
}

func setFileOwnership(path string, uid, gid *int) error {
wantedUid := sanitizeUidGid(uid)
wantedGid := sanitizeUidGid(gid)
if wantedUid == -1 && wantedGid == -1 {
return nil //noop
}
return os.Chown(path, wantedUid, wantedGid)
}

func isChownNeeded(path string, uid, gid *int) bool {
wantedUid := sanitizeUidGid(uid)
wantedGid := sanitizeUidGid(gid)
if wantedUid == -1 && wantedGid == -1 {
return false
}

currUid, currGid := getFileOwnership(path)
return wantedUid != *currUid || wantedGid != *currGid
}

// sanitizeUidGid sanitizes the uid/gid so that can be an input for os.Chown
func sanitizeUidGid(id *int) int {
if id == nil {
return -1
}
return *id
}
20 changes: 20 additions & 0 deletions renderer/file_ownership_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build windows
// +build windows

package renderer

import (
"log"
"os"
)

func setFileOwnership(path string, uid, gid *int) error {
if uid != nil || gid != nil {
log.Printf("[WARN] (runner) cannot set uid/gid for rendered files on Windows")
}
return nil
}

func isChownNeeded(path string, wantedUid, wantedGid *int) bool {
return false
}
18 changes: 17 additions & 1 deletion renderer/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type RenderInput struct {
DryStream io.Writer
Path string
Perms os.FileMode
Uid, Gid *int
}

// RenderResult is returned and stored. It contains the status of the render
Expand Down Expand Up @@ -65,7 +66,13 @@ func Render(i *RenderInput) (*RenderResult, error) {
return nil, errors.Wrap(err, "failed reading file")
}

if bytes.Equal(existing, i.Contents) && fileExists {
var chownNeeded bool

if fileExists {
chownNeeded = isChownNeeded(i.Path, i.Uid, i.Gid)
}

if bytes.Equal(existing, i.Contents) && fileExists && !chownNeeded {
return &RenderResult{
DidRender: false,
WouldRender: true,
Expand All @@ -79,6 +86,10 @@ func Render(i *RenderInput) (*RenderResult, error) {
if err := AtomicWrite(i.Path, i.CreateDestDirs, i.Contents, i.Perms, i.Backup); err != nil {
return nil, errors.Wrap(err, "failed writing file")
}

if err = setFileOwnership(i.Path, i.Uid, i.Gid); err != nil {
return nil, errors.Wrap(err, "failed setting file ownership")
}
}

return &RenderResult{
Expand Down Expand Up @@ -186,3 +197,8 @@ func AtomicWrite(path string, createDestDirs bool, contents []byte, perms os.Fil

return nil
}

// intPtr returns a pointer to the given int.
func intPtr(i int) *int {
return &i
}
Loading

0 comments on commit 24cb142

Please sign in to comment.