Add support for dropping privileges

Drop privileges to a regular user prior to executing any command
defined by the snap.

If the user and group don't already exist, they will be created.
Additionally, file permissios and ownership of setup files are
adjusted to limit access from other users.

Change-Id: I8563abce55b2b20936eb4e1d55a9016b97e8f6e0
This commit is contained in:
Corey Bryant 2017-03-28 21:33:11 +00:00
parent 110b773d98
commit e51cf60780
4 changed files with 125 additions and 1 deletions

View File

@ -60,6 +60,7 @@ class OpenStackSnap(object):
utils = SnapUtils()
LOG.debug(setup)
utils.ensure_key('install', setup.keys())
install = setup['install']
if install == 'classic':
root_dir = '/'
@ -70,11 +71,28 @@ class OpenStackSnap(object):
LOG.error(_msg)
raise ValueError(_msg)
utils.ensure_key('users', setup.keys())
for user, groups in setup['users'].items():
home = os.path.join(root_dir, "/var/lib/", user)
utils.add_user(user, groups, home)
utils.ensure_key('default_owner', setup.keys())
default_user = setup['default_owner'].split(':')[0]
default_group = setup['default_owner'].split(':')[1]
utils.ensure_key('default_dir_mode', setup.keys())
default_dir_mode = setup['default_dir_mode']
utils.ensure_key('default_file_mode', setup.keys())
default_file_mode = setup['default_file_mode']
if 'dirs' in setup.keys():
for directory in setup['dirs']:
directory = os.path.join(root_dir, directory)
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']:
@ -84,8 +102,9 @@ class OpenStackSnap(object):
utils.ensure_dir(target_file, is_file=True)
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():
@ -99,6 +118,28 @@ 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 'chmod' in setup.keys():
for target in setup['chmod']:
target_path = target.format(**utils.snap_env)
mode = setup['chmod'][target]
utils.chmod(target_path, mode)
if 'chown' in setup.keys():
for target in setup['chown']:
target_path = target.format(**utils.snap_env)
user = setup['chown'][target].split(':')[0]
group = setup['chown'][target].split(':')[1]
utils.chown(target_path, user, 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)
def execute(self, argv):
'''Execute snap command building out configuration and log options'''
@ -110,6 +151,9 @@ class OpenStackSnap(object):
LOG.error(_msg)
raise ValueError(_msg)
utils.ensure_key('run_as', entry_point)
user, groups = list(entry_point['run_as'].items())[0]
other_args = argv[2:]
LOG.debug(entry_point)
@ -174,5 +218,7 @@ class OpenStackSnap(object):
LOG.debug('Configuration file {} not found'
', skipping'.format(cfile))
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,10 +23,16 @@ entry_points:
config-dirs:
- "/etc/nova/conf.d"
log-file: "/var/log/nova/scheduler.log"
run_as:
nova: [nova]
keystone-api:
type: uwsgi
uwsgi-dir: "/etc/uwsgi"
log-file: "/var/log/uwsgi/keystone.log"
run_as:
keystone: [keystone]
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.return_value = 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,69 @@ class SnapUtils(object):
if not os.path.exists(dir_name):
LOG.info('Creating directory {}'.format(dir_name))
os.makedirs(dir_name, 0o750)
def ensure_key(self, key, keys):
'''Ensure key exists and raise ValueError if it doesn't'''
if key not in keys:
_msg = '{} key is required'.format(key)
LOG.error(_msg)
raise ValueError(_msg)
def add_user(self, user, groups, home):
'''Add user to the system as a member of one ore more groups'''
for group in groups:
LOG.debug('Adding group {} to system'.format(group))
cmd = ['addgroup', '--system', group]
subprocess.check_call(cmd)
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)