Merge pull request #37647 from americanexpress/cron-non-root

Revising cron for OS agnostic support of cron when running non-root
This commit is contained in:
Mike Place 2016-11-29 07:33:48 -07:00 committed by GitHub
commit c25a306759
3 changed files with 296 additions and 41 deletions

View File

@ -156,14 +156,20 @@ def _get_cron_cmdstr(path, user=None):
'''
Returns a format string, to be used to build a crontab command.
'''
cmd = 'crontab'
if user and __grains__.get('os_family') not in ('Solaris', 'AIX'):
cmd += ' -u {0}'.format(user)
if user:
cmd = 'crontab -u {0}'.format(user)
else:
cmd = 'crontab'
return '{0} {1}'.format(cmd, path)
def _check_instance_uid_match(user):
'''
Returns true if running instance's UID matches the specified user UID
'''
return os.geteuid() == __salt__['file.user_to_uid'](user)
def write_cron_file(user, path):
'''
Writes the contents of a file to a user's crontab
@ -178,11 +184,10 @@ def write_cron_file(user, path):
.. note::
Solaris and AIX require that `path` is readable by `user`
Some OS' do not support specifying user via the `crontab` command i.e. (Solaris, AIX)
'''
appUser = __opts__['user']
if __grains__.get('os_family') in ('Solaris', 'AIX') and appUser != user:
return __salt__['cmd.retcode'](_get_cron_cmdstr(path, user),
if _check_instance_uid_match(user) or __grains__.get('os_family') in ('Solaris', 'AIX'):
return __salt__['cmd.retcode'](_get_cron_cmdstr(path),
runas=user,
python_shell=False) == 0
else:
@ -204,11 +209,10 @@ def write_cron_file_verbose(user, path):
.. note::
Solaris and AIX require that `path` is readable by `user`
Some OS' do not support specifying user via the `crontab` command i.e. (Solaris, AIX)
'''
appUser = __opts__['user']
if __grains__.get('os_family') in ('Solaris', 'AIX') and appUser != user:
return __salt__['cmd.run_all'](_get_cron_cmdstr(path, user),
if _check_instance_uid_match(user) or __grains__.get('os_family') in ('Solaris', 'AIX'):
return __salt__['cmd.run_all'](_get_cron_cmdstr(path),
runas=user,
python_shell=False)
else:
@ -220,13 +224,12 @@ def _write_cron_lines(user, lines):
'''
Takes a list of lines to be committed to a user's crontab and writes it
'''
appUser = __opts__['user']
path = salt.utils.files.mkstemp()
if __grains__.get('os_family') in ('Solaris', 'AIX') and appUser != user:
# on solaris/aix we change to the user before executing the commands
if _check_instance_uid_match(user) or __grains__.get('os_family') in ('Solaris', 'AIX'):
# In some cases crontab command should be executed as user rather than root
with salt.utils.fpopen(path, 'w+', uid=__salt__['file.user_to_uid'](user), mode=0o600) as fp_:
fp_.writelines(lines)
ret = __salt__['cmd.run_all'](_get_cron_cmdstr(path, user),
ret = __salt__['cmd.run_all'](_get_cron_cmdstr(path),
runas=user,
python_shell=False)
else:
@ -234,7 +237,6 @@ def _write_cron_lines(user, lines):
fp_.writelines(lines)
ret = __salt__['cmd.run_all'](_get_cron_cmdstr(path, user),
python_shell=False)
os.remove(path)
return ret
@ -259,28 +261,20 @@ def raw_cron(user):
salt '*' cron.raw_cron root
'''
appUser = __opts__['user']
if __grains__.get('os_family') in ('Solaris', 'AIX'):
if appUser == user:
cmd = 'crontab -l'
else:
cmd = 'crontab -l {0}'.format(user)
if _check_instance_uid_match(user) or __grains__.get('os_family') in ('Solaris', 'AIX'):
cmd = 'crontab -l'
# Preserve line endings
lines = __salt__['cmd.run_stdout'](cmd,
runas=user,
rstrip=False,
python_shell=False).splitlines(True)
else:
if appUser == user:
cmd = 'crontab -l'
else:
cmd = 'crontab -l -u {0}'.format(user)
cmd = 'crontab -u {0} -l'.format(user)
# Preserve line endings
lines = __salt__['cmd.run_stdout'](cmd,
ignore_retcode=True,
rstrip=False,
python_shell=False).splitlines(True)
if len(lines) != 0 and lines[0].startswith('# DO NOT EDIT THIS FILE - edit the master and reinstall.'):
del lines[0:3]
return ''.join(lines)

View File

@ -511,7 +511,15 @@ def file(name,
'''
# Initial set up
mode = salt.utils.normalize_mode('0600')
owner, group, crontab_dir = _get_cron_info()
try:
group = __salt__['user.info'](user)['groups'][0]
except Exception:
ret = {'changes': {},
'comment': "Could not identify group for user {0}".format(user),
'name': name,
'result': False}
return ret
cron_path = salt.utils.files.mkstemp()
with salt.utils.fopen(cron_path, 'w+') as fp_:
@ -540,7 +548,7 @@ def file(name,
source,
source_hash,
source_hash_name,
owner,
user,
group,
mode,
template,
@ -565,7 +573,7 @@ def file(name,
template,
source,
source_hash,
owner,
user,
group,
mode,
__env__,
@ -593,7 +601,7 @@ def file(name,
ret,
source,
source_sum,
owner,
user,
group,
mode,
__env__,
@ -606,17 +614,22 @@ def file(name,
return ret
cron_ret = None
if ret['changes']:
if "diff" in ret['changes']:
cron_ret = __salt__['cron.write_cron_file_verbose'](user, cron_path)
ret['comment'] = 'Crontab for user {0} was updated'.format(user)
# Check cmd return code and show success or failure
if cron_ret['retcode'] == 0:
ret['comment'] = 'Crontab for user {0} was updated'.format(user)
ret['result'] = True
ret['changes'] = ret['changes']['diff']
else:
ret['comment'] = 'Unable to update user {0} crontab {1}.' \
' Error: {2}'.format(user, cron_path, cron_ret['stderr'])
ret['result'] = False
ret['changes'] = {}
elif ret['result']:
ret['comment'] = 'Crontab for user {0} is in the correct ' \
'state'.format(user)
if cron_ret and cron_ret['retcode']:
ret['comment'] = 'Unable to update user {0} crontab {1}.' \
' Error: {2}'.format(user, cron_path, cron_ret['stderr'])
ret['result'] = False
ret['changes'] = {}
os.unlink(cron_path)
return ret

View File

@ -35,6 +35,10 @@ L = '# Lines below here are managed by Salt, do not edit\n'
CRONTAB = StringIO()
# Setup globals
cron.__salt__ = {}
cron.__grains__ = {}
def get_crontab(*args, **kw):
return CRONTAB.getvalue()
@ -597,6 +601,243 @@ class CronTestCase(TestCase):
'minute': '*',
'month': '*'})
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=True))
def test_write_cron_file_root_rh(self):
'''
Assert that write_cron_file() is called with the correct cron command and user: RedHat
- If instance running uid matches crontab user uid, runas STUB_USER without -u flag.
'''
cron.__grains__ = {'os_family': 'RedHat'}
with patch.dict(cron.__salt__, {'cmd.retcode': MagicMock()}):
cron.write_cron_file(STUB_USER, STUB_PATH)
cron.__salt__['cmd.retcode'].assert_called_with("crontab /tmp",
runas=STUB_USER,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=False))
def test_write_cron_file_foo_rh(self):
'''
Assert that write_cron_file() is called with the correct cron command and user: RedHat
- If instance running with uid that doesn't match crontab user uid, run with -u flag
'''
cron.__grains__ = {'os_family': 'RedHat'}
with patch.dict(cron.__salt__, {'cmd.retcode': MagicMock()}):
cron.write_cron_file('foo', STUB_PATH)
cron.__salt__['cmd.retcode'].assert_called_with("crontab -u foo /tmp",
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=True))
def test_write_cron_file_root_sol(self):
'''
Assert that write_cron_file() is called with the correct cron command and user: Solaris
- Solaris should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'Solaris'}
with patch.dict(cron.__salt__, {'cmd.retcode': MagicMock()}):
cron.write_cron_file(STUB_USER, STUB_PATH)
cron.__salt__['cmd.retcode'].assert_called_with("crontab /tmp",
runas=STUB_USER,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=False))
def test_write_cron_file_foo_sol(self):
'''
Assert that write_cron_file() is called with the correct cron command and user: Solaris
- Solaris should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'Solaris'}
with patch.dict(cron.__salt__, {'cmd.retcode': MagicMock()}):
cron.write_cron_file('foo', STUB_PATH)
cron.__salt__['cmd.retcode'].assert_called_with("crontab /tmp",
runas='foo',
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=True))
def test_write_cron_file_root_aix(self):
'''
Assert that write_cron_file() is called with the correct cron command and user: AIX
- AIX should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'AIX'}
with patch.dict(cron.__salt__, {'cmd.retcode': MagicMock()}):
cron.write_cron_file(STUB_USER, STUB_PATH)
cron.__salt__['cmd.retcode'].assert_called_with("crontab /tmp",
runas=STUB_USER,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=False))
def test_write_cron_file_foo_aix(self):
'''
Assert that write_cron_file() is called with the correct cron command and user: AIX
- AIX should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'AIX'}
with patch.dict(cron.__salt__, {'cmd.retcode': MagicMock()}):
cron.write_cron_file('foo', STUB_PATH)
cron.__salt__['cmd.retcode'].assert_called_with("crontab /tmp",
runas='foo',
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=True))
def test_write_cr_file_v_root_rh(self):
'''
Assert that write_cron_file_verbose() is called with the correct cron command and user: RedHat
- If instance running uid matches crontab user uid, runas STUB_USER without -u flag.
'''
cron.__grains__ = {'os_family': 'RedHat'}
with patch.dict(cron.__salt__, {'cmd.run_all': MagicMock()}):
cron.write_cron_file_verbose(STUB_USER, STUB_PATH)
cron.__salt__['cmd.run_all'].assert_called_with("crontab /tmp",
runas=STUB_USER,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=False))
def test_write_cr_file_v_foo_rh(self):
'''
Assert that write_cron_file_verbose() is called with the correct cron command and user: RedHat
- If instance running with uid that doesn't match crontab user uid, run with -u flag
'''
cron.__grains__ = {'os_family': 'RedHat'}
with patch.dict(cron.__salt__, {'cmd.run_all': MagicMock()}):
cron.write_cron_file_verbose('foo', STUB_PATH)
cron.__salt__['cmd.run_all'].assert_called_with("crontab -u foo /tmp",
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=True))
def test_write_cr_file_v_root_sol(self):
'''
Assert that write_cron_file_verbose() is called with the correct cron command and user: Solaris
- Solaris should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'Solaris'}
with patch.dict(cron.__salt__, {'cmd.run_all': MagicMock()}):
cron.write_cron_file_verbose(STUB_USER, STUB_PATH)
cron.__salt__['cmd.run_all'].assert_called_with("crontab /tmp",
runas=STUB_USER,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=False))
def test_write_cr_file_v_foo_sol(self):
'''
Assert that write_cron_file_verbose() is called with the correct cron command and user: Solaris
- Solaris should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'Solaris'}
with patch.dict(cron.__salt__, {'cmd.run_all': MagicMock()}):
cron.write_cron_file_verbose('foo', STUB_PATH)
cron.__salt__['cmd.run_all'].assert_called_with("crontab /tmp",
runas='foo',
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=True))
def test_write_cr_file_v_root_aix(self):
'''
Assert that write_cron_file_verbose() is called with the correct cron command and user: AIX
- AIX should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'AIX'}
with patch.dict(cron.__salt__, {'cmd.run_all': MagicMock()}):
cron.write_cron_file_verbose(STUB_USER, STUB_PATH)
cron.__salt__['cmd.run_all'].assert_called_with("crontab /tmp",
runas=STUB_USER,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=False))
def test_write_cr_file_v_foo_aix(self):
'''
Assert that write_cron_file_verbose() is called with the correct cron command and user: AIX
- AIX should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'AIX'}
with patch.dict(cron.__salt__, {'cmd.run_all': MagicMock()}):
cron.write_cron_file_verbose('foo', STUB_PATH)
cron.__salt__['cmd.run_all'].assert_called_with("crontab /tmp",
runas='foo',
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=True))
def test_raw_cron_root_redhat(self):
'''
Assert that raw_cron() is called with the correct cron command and user: RedHat
- If instance running uid matches crontab user uid, runas STUB_USER without -u flag.
'''
cron.__grains__ = {'os_family': 'RedHat'}
with patch.dict(cron.__salt__, {'cmd.run_stdout': MagicMock()}):
cron.raw_cron(STUB_USER)
cron.__salt__['cmd.run_stdout'].assert_called_with("crontab -l",
runas=STUB_USER,
rstrip=False,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=False))
def test_raw_cron_foo_redhat(self):
'''
Assert that raw_cron() is called with the correct cron command and user: RedHat
- If instance running with uid that doesn't match crontab user uid, run with -u flag
'''
cron.__grains__ = {'os_family': 'RedHat'}
with patch.dict(cron.__salt__, {'cmd.run_stdout': MagicMock()}):
cron.raw_cron(STUB_USER)
cron.__salt__['cmd.run_stdout'].assert_called_with("crontab -u root -l",
rstrip=False,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=True))
def test_raw_cron_root_solaris(self):
'''
Assert that raw_cron() is called with the correct cron command and user: Solaris
- Solaris should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'Solaris'}
with patch.dict(cron.__salt__, {'cmd.run_stdout': MagicMock()}):
cron.raw_cron(STUB_USER)
cron.__salt__['cmd.run_stdout'].assert_called_with("crontab -l",
runas=STUB_USER,
rstrip=False,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=False))
def test_raw_cron_foo_solaris(self):
'''
Assert that raw_cron() is called with the correct cron command and user: Solaris
- Solaris should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'Solaris'}
with patch.dict(cron.__salt__, {'cmd.run_stdout': MagicMock()}):
cron.raw_cron(STUB_USER)
cron.__salt__['cmd.run_stdout'].assert_called_with("crontab -l",
runas=STUB_USER,
rstrip=False,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=True))
def test_raw_cron_root_aix(self):
'''
Assert that raw_cron() is called with the correct cron command and user: AIX
- AIX should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'AIX'}
with patch.dict(cron.__salt__, {'cmd.run_stdout': MagicMock()}):
cron.raw_cron(STUB_USER)
cron.__salt__['cmd.run_stdout'].assert_called_with("crontab -l",
runas=STUB_USER,
rstrip=False,
python_shell=False)
@patch("salt.modules.cron._check_instance_uid_match", new=MagicMock(return_value=False))
def test_raw_cron_foo_aix(self):
'''
Assert that raw_cron() is called with the correct cron command and user: AIX
- AIX should always run without a -u flag
'''
cron.__grains__ = {'os_family': 'AIX'}
with patch.dict(cron.__salt__, {'cmd.run_stdout': MagicMock()}):
cron.raw_cron(STUB_USER)
cron.__salt__['cmd.run_stdout'].assert_called_with("crontab -l",
runas=STUB_USER,
rstrip=False,
python_shell=False)
@skipIf(NO_MOCK, NO_MOCK_REASON)
class PsTestCase(TestCase):
@ -615,6 +856,13 @@ class PsTestCase(TestCase):
def test__get_cron_cmdstr(self):
self.assertEqual('crontab /tmp', cron._get_cron_cmdstr(STUB_PATH))
# Test get_cron_cmdstr() when user is added
def test__get_cron_cmdstr_user(self):
'''
Passes if a user is added to crontab command
'''
self.assertEqual('crontab -u root /tmp', cron._get_cron_cmdstr(STUB_PATH, STUB_USER))
def test__date_time_match(self):
'''
Passes if a match is found on all elements. Note the conversions to strings here!