Add support for dropping privileges

Adds suport for running commands, defined by a snap, as a specific
user/group.

Additionally, file permissions and ownership of setup files can
be adjusted to limit access from other users.

Change-Id: I8563abce55b2b20936eb4e1d55a9016b97e8f6e0
This commit is contained in:
Corey Bryant 2017-05-15 14:39:17 +00:00
parent 920715bd4f
commit 50451ab404
4 changed files with 138 additions and 1 deletions

View File

@ -61,10 +61,33 @@ class OpenStackSnap(object):
utils = SnapUtils()
LOG.debug(setup)
default_user = "root"
default_group = "root"
default_dir_mode = 0750
default_file_mode = 0640
if 'users' in setup.keys():
for user, groups in setup['users'].items():
home = os.path.join("{snap_common}".format(**utils.snap_env),
"lib", user)
utils.add_user(user, groups, home)
if 'default_owner' in setup.keys():
default_user = setup['default_owner'].split(':')[0]
default_group = setup['default_owner'].split(':')[1]
if 'default_dir_mode' in setup.keys():
default_dir_mode = setup['default_dir_mode']
if 'default_file_mode' in setup.keys():
default_file_mode = setup['default_file_mode']
if 'dirs' in setup.keys():
for directory in setup['dirs']:
dir_name = directory.format(**utils.snap_env)
utils.ensure_dir(dir_name)
utils.rchmod(dir_name, default_dir_mode, default_file_mode)
utils.rchown(dir_name, default_user, default_group)
if 'templates' in setup.keys():
for template in setup['templates']:
@ -75,8 +98,9 @@ class OpenStackSnap(object):
LOG.debug('Rendering {} to {}'.format(template,
target_file))
with open(target_file, 'w') as tf:
os.fchmod(tf.fileno(), 0o640)
tf.write(renderer.render(template, utils.snap_env))
utils.chmod(target_file, default_file_mode)
utils.chown(target_file, default_user, default_group)
if 'copyfiles' in setup.keys():
for source, target in setup['copyfiles'].items():
@ -89,6 +113,34 @@ class OpenStackSnap(object):
continue
LOG.debug('Copying file {} to {}'.format(s_file, d_file))
shutil.copy2(s_file, d_file)
utils.chmod(d_file, default_file_mode)
utils.chown(d_file, default_user, default_group)
if 'rchown' in setup.keys():
for target in setup['rchown']:
target_path = target.format(**utils.snap_env)
user = setup['rchown'][target].split(':')[0]
group = setup['rchown'][target].split(':')[1]
utils.rchown(target_path, user, group)
if 'chmod' in setup.keys():
for target in setup['chmod']:
target_path = target.format(**utils.snap_env)
if os.path.exists(target_path):
mode = setup['chmod'][target]
utils.chmod(target_path, mode)
else:
LOG.debug('Path not found: {}'.format(target_path))
if 'chown' in setup.keys():
for target in setup['chown']:
target_path = target.format(**utils.snap_env)
if os.path.exists(target_path):
user = setup['chown'][target].split(':')[0]
group = setup['chown'][target].split(':')[1]
utils.chown(target_path, user, group)
else:
LOG.debug('Path not found: {}'.format(target_path))
def execute(self, argv):
'''Execute snap command building out configuration and log options'''
@ -100,6 +152,9 @@ class OpenStackSnap(object):
LOG.error(_msg)
raise ValueError(_msg)
if 'run-as' in entry_point.keys():
user, groups = list(entry_point['run-as'].items())[0]
other_args = argv[2:]
LOG.debug(entry_point)
@ -165,5 +220,8 @@ class OpenStackSnap(object):
LOG.debug('Configuration file {} not found'
', skipping'.format(cfile))
if 'run-as' in entry_point.keys():
utils.drop_privileges(user, groups)
LOG.debug('Executing command {}'.format(' '.join(cmd)))
os.execvp(cmd[0], cmd)

View File

@ -13,6 +13,8 @@ entry_points:
- "/etc/nova/nova.conf"
config-dirs:
- "/etc/nova/conf.d"
run_as:
nova: [nova]
nova-scheduler:
type: simple
binary: nova-scheduler
@ -21,13 +23,21 @@ entry_points:
config-dirs:
- "/etc/nova/conf.d"
log-file: "/var/log/nova/scheduler.log"
run_as:
nova: [nova]
keystone-uwsgi:
type: uwsgi
uwsgi-dir: "/etc/uwsgi"
log-file: "/var/log/uwsgi/keystone.log"
run_as:
keystone: [keystone]
keystone-nginx:
type: nginx
config-file: "/etc/nginx/keystone/nginx.conf"
run_as:
nova: [nova]
nova-broken:
type: unknown
binary: nova-broken
run_as:
nova: [nova]

View File

@ -49,6 +49,7 @@ class TestOpenStackSnapExecute(test_base.TestCase):
def mock_snap_utils(self, mock_utils):
snap_utils = mock_utils.return_value
snap_utils.snap_env = MOCK_SNAP_ENV
snap_utils.drop_privileges.return_value = None
@patch('snap_openstack.base.SnapUtils')
@patch.object(base, 'os')

View File

@ -14,8 +14,11 @@
# License for the specific language governing permissions and limitations
# under the License.
import grp
import logging
import os
import pwd
import subprocess
LOG = logging.getLogger(__name__)
@ -72,3 +75,68 @@ class SnapUtils(object):
if not os.path.exists(dir_name):
LOG.info('Creating directory {}'.format(dir_name))
os.makedirs(dir_name, 0o750)
def add_user(self, user, groups, home):
'''Add user to the system as a member of one ore more groups'''
for group in groups:
try:
grp.getgrnam(group)
except KeyError:
LOG.debug('Adding group {} to system'.format(group))
cmd = ['addgroup', '--system', group]
subprocess.check_call(cmd)
try:
pwd.getpwnam(user)
except KeyError:
self.ensure_dir(home)
LOG.debug('Adding user {} to system'.format(user))
cmd = ['adduser', '--quiet', '--system', '--home', home,
'--no-create-home', '--shell', '/bin/false', user]
subprocess.check_call(cmd)
for group in groups:
LOG.debug('Adding user {} to group {}'.format(user, group))
cmd = ['adduser', user, group]
subprocess.check_call(cmd)
def chown(self, path, user, group):
'''Change the owner of the specified file'''
LOG.debug('Changing owner of {} to {}:{}'.format(path, user, group))
uid = pwd.getpwnam(user).pw_uid
gid = grp.getgrnam(group).gr_gid
os.chown(path, uid, gid)
def chmod(self, path, mode):
'''Change the file mode bits of the specified file'''
LOG.debug('Changing file mode of {} to {}'.format(path, oct(mode)))
os.chmod(path, mode)
def rchown(self, root_dir, user, group):
'''Recursively change owner starting at the specified directory'''
self.chown(root_dir, user, group)
for dirpath, dirnames, filenames in os.walk(root_dir):
for d in dirnames:
self.chown(os.path.join(dirpath, d), user, group)
for f in filenames:
self.chown(os.path.join(dirpath, f), user, group)
def rchmod(self, root_dir, dir_mode, file_mode):
'''Recursively change mode bits starting at the specified directory'''
self.chmod(root_dir, dir_mode)
for dirpath, dirnames, filenames in os.walk(root_dir):
for d in dirnames:
self.chmod(os.path.join(dirpath, d), dir_mode)
for f in filenames:
self.chmod(os.path.join(dirpath, f), file_mode)
def drop_privileges(self, user, groups):
'''Drop privileges to the specified user and group(s)'''
LOG.debug('Dropping privileges to {}:{}'.format(user, groups))
uid = pwd.getpwnam(user).pw_uid
gid = grp.getgrnam(groups[0]).gr_gid
gids = [grp.getgrnam(g).gr_gid for g in groups]
os.setgroups([])
os.setgroups(gids)
os.setgid(gid)
os.setuid(uid)