resources: Add User/Group to ExecRes
This commit is contained in:
committed by
James Shubin
parent
2affcba3b4
commit
3575d597f7
@@ -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
76
test/shell/exec-usergroup.sh
Executable 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}"
|
||||
29
test/shell/exec-usergroup/exec-usergroup-group-nobody.yaml
Normal file
29
test/shell/exec-usergroup/exec-usergroup-group-nobody.yaml
Normal 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
|
||||
29
test/shell/exec-usergroup/exec-usergroup-group-nogroup.yaml
Normal file
29
test/shell/exec-usergroup/exec-usergroup-group-nogroup.yaml
Normal 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
|
||||
30
test/shell/exec-usergroup/exec-usergroup-nobody.yaml
Normal file
30
test/shell/exec-usergroup/exec-usergroup-nobody.yaml
Normal 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
|
||||
30
test/shell/exec-usergroup/exec-usergroup-nogroup.yaml
Normal file
30
test/shell/exec-usergroup/exec-usergroup-nogroup.yaml
Normal 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
|
||||
29
test/shell/exec-usergroup/exec-usergroup-user.yaml
Normal file
29
test/shell/exec-usergroup/exec-usergroup-user.yaml
Normal 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
|
||||
Reference in New Issue
Block a user