Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting ownership by name #1541

Merged
merged 3 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1096,12 +1096,12 @@ func TestParse(t *testing.T) {
{
"template_uid",
`template {
uid = 1000
user = "1000"
}`,
&Config{
Templates: &TemplateConfigs{
&TemplateConfig{
Uid: Int(1000),
User: String("1000"),
},
},
},
Expand All @@ -1110,12 +1110,12 @@ func TestParse(t *testing.T) {
{
"template_gid",
`template {
gid = 1000
group = "1000"
}`,
&Config{
Templates: &TemplateConfigs{
&TemplateConfig{
Gid: Int(1000),
Group: String("1000"),
},
},
},
Expand All @@ -1128,8 +1128,8 @@ func TestParse(t *testing.T) {
&Config{
Templates: &TemplateConfigs{
&TemplateConfig{
Uid: nil,
Gid: nil,
User: nil,
Group: nil,
},
},
},
Expand Down
8 changes: 4 additions & 4 deletions config/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,19 @@ 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.
// User is the username or 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"`
User *string `mapstructure:"user"`

// Gid is the numeric gid that will be set when creating the file on disk.
// Group is the group name or 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"`
Group *string `mapstructure:"group"`

// Source is the path on disk to the template contents to evaluate. Either
// this or Contents should be specified, but not both.
Expand Down
4 changes: 2 additions & 2 deletions manager/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -780,8 +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,
User: config.StringVal(templateConfig.User),
Group: config.StringVal(templateConfig.Group),
})
if err != nil {
return nil, errors.Wrap(err, "error rendering "+templateConfig.Display())
Expand Down
69 changes: 46 additions & 23 deletions renderer/file_ownership.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,68 @@ package renderer

import (
"os"
"os/user"
"strconv"
"syscall"
)

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

if err != nil {
return nil, nil
return 0, 0, err
}

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

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

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

currUid, currGid := getFileOwnership(path)
return wantedUid != *currUid || wantedGid != *currGid
currUid, currGid, err := getFileOwnership(path)
if err != nil {
return false, err
}
return uid != currUid || gid != currGid, nil
}

// parseUidGid parses the uid/gid so that it can be input to os.Chown
func parseUidGid(s string) (int, error) {
if s == "" {
return -1, nil
}
return strconv.Atoi(s)
}

func lookupUser(s string) (int, error) {
if id, err := parseUidGid(s); err == nil {
return id, nil
}
u, err := user.Lookup(s)
if err != nil {
return 0, err
}
return strconv.Atoi(u.Uid)
}

// sanitizeUidGid sanitizes the uid/gid so that can be an input for os.Chown
func sanitizeUidGid(id *int) int {
if id == nil {
return -1
func lookupGroup(s string) (int, error) {
if id, err := parseUidGid(s); err == nil {
return id, nil
}
u, err := user.LookupGroup(s)
if err != nil {
return 0, err
}
return *id
return strconv.Atoi(u.Gid)
}
33 changes: 26 additions & 7 deletions renderer/file_ownership_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,35 @@
package renderer

import (
"log"
"fmt"
)

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")
var notSupportedError error = fmt.Errorf("managing file ownership is not supported on Windows")

func setFileOwnership(path string, uid, gid int) error {
if uid == -1 && gid == -1 {
return nil
}
return notSupportedError
}

func isChownNeeded(path string, wantedUid, wantedGid int) (bool, error) {
if wantedUid == -1 && wantedGid == -1 {
return false, nil
}
return nil
return false, notSupportedError
}

func isChownNeeded(path string, wantedUid, wantedGid *int) bool {
return false
func lookupUser(user string) (int, error) {
if user == "" {
return -1, nil
}
return 0, notSupportedError
}

func lookupGroup(group string) (int, error) {
if group == "" {
return -1, nil
}
return 0, notSupportedError
}
19 changes: 16 additions & 3 deletions renderer/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type RenderInput struct {
DryStream io.Writer
Path string
Perms os.FileMode
Uid, Gid *int
User, Group string
}

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

uid, err := lookupUser(i.User)
if err != nil {
return nil, errors.Wrap(err, "failed looking up user")
}
gid, err := lookupGroup(i.Group)
if err != nil {
return nil, errors.Wrap(err, "failed looking up group")
}

var chownNeeded bool

if fileExists {
chownNeeded = isChownNeeded(i.Path, i.Uid, i.Gid)
chownNeeded, err = isChownNeeded(i.Path, uid, gid)
if err != nil {
log.Printf("[WARN] (runner) could not determine existing output file's permissions")
chownNeeded = true
}
}

if bytes.Equal(existing, i.Contents) && fileExists && !chownNeeded {
Expand All @@ -87,7 +100,7 @@ func Render(i *RenderInput) (*RenderResult, error) {
return nil, errors.Wrap(err, "failed writing file")
}

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