resources: Add User/Group to ExecRes

This commit is contained in:
Guillaume Herail
2017-11-21 14:44:38 +01:00
committed by James Shubin
parent 2affcba3b4
commit 3575d597f7
7 changed files with 293 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ import (
"fmt"
"log"
"os/exec"
"os/user"
"strings"
"sync"
"syscall"
@@ -46,6 +47,8 @@ type ExecRes struct {
WatchShell string `yaml:"watchshell"` // the (optional) shell to use to run the watch cmd
IfCmd string `yaml:"ifcmd"` // the if command to run
IfShell string `yaml:"ifshell"` // the (optional) shell to use to run the if cmd
User string `yaml:"user"` // the (optional) user to use to execute the command
Group string `yaml:"group"` // the (optional) group to use to execute the command
Output *string // all cmd output, read only, do not set!
Stdout *string // the cmd stdout, read only, do not set!
Stderr *string // the cmd stderr, read only, do not set!
@@ -66,6 +69,17 @@ func (obj *ExecRes) Validate() error {
return fmt.Errorf("command can't be empty")
}
// check that, if an user or a group is set, we're running as root
if obj.User != "" || obj.Group != "" {
currentUser, err := user.Current()
if err != nil {
return errwrap.Wrapf(err, "error looking up current user")
}
if currentUser.Uid != "0" {
return errwrap.Errorf("running as root is required if you want to use exec with a different user/group")
}
}
return obj.BaseRes.Validate()
}
@@ -121,6 +135,12 @@ func (obj *ExecRes) Watch() error {
Pgid: 0,
}
// if we have a user and group, use them
var err error
if cmd.SysProcAttr.Credential, err = obj.getCredential(); err != nil {
return errwrap.Wrapf(err, "error while setting credential")
}
cmdReader, err := cmd.StdoutPipe()
if err != nil {
return errwrap.Wrapf(err, "error creating StdoutPipe for Cmd")
@@ -208,6 +228,13 @@ func (obj *ExecRes) CheckApply(apply bool) (bool, error) {
Setpgid: true,
Pgid: 0,
}
// if we have an user and group, use them
var err error
if cmd.SysProcAttr.Credential, err = obj.getCredential(); err != nil {
return false, errwrap.Wrapf(err, "error while setting credential")
}
if err := cmd.Run(); err != nil {
// TODO: check exit value
return true, nil // don't run
@@ -245,6 +272,12 @@ func (obj *ExecRes) CheckApply(apply bool) (bool, error) {
Pgid: 0,
}
// if we have a user and group, use them
var err error
if cmd.SysProcAttr.Credential, err = obj.getCredential(); err != nil {
return false, errwrap.Wrapf(err, "error while setting credential")
}
var out splitWriter
out.Init()
// from the docs: "If Stdout and Stderr are the same writer, at most one
@@ -263,7 +296,6 @@ func (obj *ExecRes) CheckApply(apply bool) (bool, error) {
done := make(chan error)
go func() { done <- cmd.Wait() }()
var err error // error returned by cmd
select {
case e := <-done:
err = e // store
@@ -422,6 +454,12 @@ func (obj *ExecRes) Compare(r Res) bool {
if obj.IfShell != res.IfShell {
return false
}
if obj.User != res.User {
return false
}
if obj.Group != res.Group {
return false
}
return true
}
@@ -446,6 +484,37 @@ func (obj *ExecRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
// getCredential returns the correct *syscall.Credential if an User and Group
// are set.
func (obj *ExecRes) getCredential() (*syscall.Credential, error) {
var uid, gid int
var err error
var currentUser *user.User
if currentUser, err = user.Current(); err != nil {
return nil, errwrap.Wrapf(err, "error looking up current user")
}
if currentUser.Uid != "0" {
// since we're not root, we've got nothing to do
return nil, nil
}
if obj.Group != "" {
gid, err = GetGID(obj.Group)
if err != nil {
return nil, errwrap.Wrapf(err, "error looking up gid for %s", obj.Group)
}
}
if obj.User != "" {
uid, err = GetUID(obj.User)
if err != nil {
return nil, errwrap.Wrapf(err, "error looking up uid for %s", obj.User)
}
}
return &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}, nil
}
// splitWriter mimics what the ssh.CombinedOutput command does, but stores the
// the stdout and stderr separately. This is slightly tricky because we don't
// want the combined output to be interleaved incorrectly. It creates sub writer

76
test/shell/exec-usergroup.sh Executable file
View File

@@ -0,0 +1,76 @@
#!/bin/bash
set -x
set -o pipefail
if ! timeout 1s sudo -A true; then
echo "sudo disabled: not checking exec user and group"
exit
fi
BASE_PATH="/tmp/mgmt/"
BASE_PATH_TEST="${BASE_PATH}test-exec-usergroup/"
# on Fedora, it's nobody while on ubuntu it's nogroup
GROUP="nogroup"
if grep -q nobody /etc/group; then
GROUP="nobody"
fi
function setup {
mkdir -p "${BASE_PATH_TEST}"
sudo -A chown nobody:${GROUP} "${BASE_PATH_TEST}"
sudo -A chmod ug=rwx,o=rx "${BASE_PATH_TEST}"
}
function cleanup {
sudo -A rm -rf "${BASE_PATH_TEST}"
}
# run_test will run each test. It takes 3 parameters:
# - $1: graph (e.g. exec-usergroup-nobody.yaml)
# - $2: user to be tested (e.g. nobody or "")
# - $3: group to be tested (e.g. nobody or "")
function run_usergroup_test() {
graph=$1
user=$2
group=$3
setup
# run till completion
sudo -A timeout --kill-after=30s 25s ./mgmt run --yaml ./exec-usergroup/${graph} --converged-timeout=5 --no-watch --tmp-prefix &
pid=$!
wait $pid # get exit status
e=$?
# tests
test -e "${BASE_PATH_TEST}/result-exec-usergroup"
if [ $? != 0 ]; then
echo "${BASE_PATH_TEST}result-exec-usergroup has not been created"
exit 1
fi
if [ "${user}" != "" ]; then
test $(stat -c%U "${BASE_PATH_TEST}/result-exec-usergroup") = $user
if [ $? != 0 ]; then
echo "${BASE_PATH_TEST}result-exec-usergroup owner is not ${user}"
exit 1
fi
fi
if [ "${group}" != "" ]; then
test $(stat -c%G "${BASE_PATH_TEST}/result-exec-usergroup") = $group
if [ $? != 0 ]; then
echo "${BASE_PATH_TEST}result-exec-usergroup group is not ${group}"
exit 1
fi
fi
cleanup
}
# ensure the workspace is clean
cleanup
# run_usergroup_test <yaml file in ./exec-usergroup> <user to test> <group to test>
run_usergroup_test "exec-usergroup-${GROUP}.yaml" "nobody" "${GROUP}"
run_usergroup_test "exec-usergroup-user.yaml" "nobody" ""
run_usergroup_test "exec-usergroup-group-${GROUP}.yaml" "" "${GROUP}"

View File

@@ -0,0 +1,29 @@
---
graph: mygraph
resources:
exec:
- name: test.sh
cmd: /tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh
shell: /bin/bash
group: nobody
meta:
autoedge: true
file:
- name: file1
meta:
autoedge: true
path: "/tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh"
content: |
# this is an mgmt test
id
echo "this is a test" > /tmp/mgmt/test-exec-usergroup/result-exec-usergroup
state: exists
mode: "0777"
edges:
- name: e1
from:
kind: file
name: file1
to:
kind: exec
name: test.sh

View File

@@ -0,0 +1,29 @@
---
graph: mygraph
resources:
exec:
- name: test.sh
cmd: /tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh
shell: /bin/bash
group: nogroup
meta:
autoedge: true
file:
- name: file1
meta:
autoedge: true
path: "/tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh"
content: |
# this is an mgmt test
id
echo "this is a test" > /tmp/mgmt/test-exec-usergroup/result-exec-usergroup
state: exists
mode: "0777"
edges:
- name: e1
from:
kind: file
name: file1
to:
kind: exec
name: test.sh

View File

@@ -0,0 +1,30 @@
---
graph: mygraph
resources:
exec:
- name: test.sh
cmd: /tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh
shell: /bin/bash
user: nobody
group: nobody
meta:
autoedge: true
file:
- name: file1
meta:
autoedge: true
path: "/tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh"
content: |
# this is an mgmt test
id
echo "this is a test" > /tmp/mgmt/test-exec-usergroup/result-exec-usergroup
state: exists
mode: "0777"
edges:
- name: e1
from:
kind: file
name: file1
to:
kind: exec
name: test.sh

View File

@@ -0,0 +1,30 @@
---
graph: mygraph
resources:
exec:
- name: test.sh
cmd: /tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh
shell: /bin/bash
user: nobody
group: nogroup
meta:
autoedge: true
file:
- name: file1
meta:
autoedge: true
path: "/tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh"
content: |
# this is an mgmt test
id
echo "this is a test" > /tmp/mgmt/test-exec-usergroup/result-exec-usergroup
state: exists
mode: "0777"
edges:
- name: e1
from:
kind: file
name: file1
to:
kind: exec
name: test.sh

View File

@@ -0,0 +1,29 @@
---
graph: mygraph
resources:
exec:
- name: test.sh
cmd: /tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh
shell: /bin/bash
user: nobody
meta:
autoedge: true
file:
- name: file1
meta:
autoedge: true
path: "/tmp/mgmt/test-exec-usergroup/test-exec-usergroup.sh"
content: |
# this is an mgmt test
id
echo "this is a test" > /tmp/mgmt/test-exec-usergroup/result-exec-usergroup
state: exists
mode: "0777"
edges:
- name: e1
from:
kind: file
name: file1
to:
kind: exec
name: test.sh