Add support for dropping privileges
Add support 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:
parent
920715bd4f
commit
ed986bac6d
@ -41,6 +41,10 @@ DEFAULT_UWSGI_ARGS = ["--master",
|
||||
DEFAULT_NGINX_ARGS = ["-g",
|
||||
"daemon on; master_process on;"]
|
||||
|
||||
DEFAULT_OWNER = "root:root"
|
||||
DEFAULT_DIR_MODE = 0o750
|
||||
DEFAULT_FILE_MODE = 0o640
|
||||
|
||||
|
||||
class OpenStackSnap(object):
|
||||
'''Main executor class for snap-openstack'''
|
||||
@ -61,13 +65,24 @@ class OpenStackSnap(object):
|
||||
utils = SnapUtils()
|
||||
LOG.debug(setup)
|
||||
|
||||
if 'dirs' in setup.keys():
|
||||
for directory in setup['dirs']:
|
||||
dir_name = directory.format(**utils.snap_env)
|
||||
utils.ensure_dir(dir_name)
|
||||
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 'templates' in setup.keys():
|
||||
for template in setup['templates']:
|
||||
default_owner = setup.get('default-owner', DEFAULT_OWNER)
|
||||
default_user, default_group = default_owner.split(':')
|
||||
default_dir_mode = setup.get('default-dir-mode', DEFAULT_DIR_MODE)
|
||||
default_file_mode = setup.get('default-file-mode', DEFAULT_FILE_MODE)
|
||||
|
||||
for directory in setup.get('dirs', []):
|
||||
dir_name = directory.format(**utils.snap_env)
|
||||
utils.ensure_dir(dir_name, perms=default_dir_mode)
|
||||
utils.rchmod(dir_name, default_dir_mode, default_file_mode)
|
||||
utils.rchown(dir_name, default_user, default_group)
|
||||
|
||||
for template in setup.get('templates', []):
|
||||
target = setup['templates'][template]
|
||||
target_file = target.format(**utils.snap_env)
|
||||
utils.ensure_dir(target_file, is_file=True)
|
||||
@ -75,8 +90,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 +105,29 @@ 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)
|
||||
|
||||
for target in setup.get('rchown', []):
|
||||
target_path = target.format(**utils.snap_env)
|
||||
user, group = setup['rchown'][target].split(':')
|
||||
utils.rchown(target_path, user, group)
|
||||
|
||||
for target in setup.get('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))
|
||||
|
||||
for target in setup.get('chown', []):
|
||||
target_path = target.format(**utils.snap_env)
|
||||
if os.path.exists(target_path):
|
||||
user, group = setup['chown'][target].split(':')
|
||||
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'''
|
||||
@ -165,5 +204,9 @@ class OpenStackSnap(object):
|
||||
LOG.debug('Configuration file {} not found'
|
||||
', skipping'.format(cfile))
|
||||
|
||||
if 'run-as' in entry_point.keys():
|
||||
user, groups = list(entry_point['run-as'].items())[0]
|
||||
utils.drop_privileges(user, groups)
|
||||
|
||||
LOG.debug('Executing command {}'.format(' '.join(cmd)))
|
||||
os.execvp(cmd[0], cmd)
|
||||
|
@ -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,6 +23,8 @@ 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"
|
||||
@ -28,6 +32,10 @@ entry_points:
|
||||
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]
|
||||
|
@ -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')
|
||||
|
@ -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__)
|
||||
|
||||
@ -57,7 +60,7 @@ class SnapUtils(object):
|
||||
'''
|
||||
return self._snap_env
|
||||
|
||||
def ensure_dir(self, path, is_file=False):
|
||||
def ensure_dir(self, path, is_file=False, perms=0o750):
|
||||
'''Ensure a directory exists
|
||||
|
||||
Ensure that the directory structure to support the provided file or
|
||||
@ -71,4 +74,69 @@ class SnapUtils(object):
|
||||
dir_name = os.path.dirname(path)
|
||||
if not os.path.exists(dir_name):
|
||||
LOG.info('Creating directory {}'.format(dir_name))
|
||||
os.makedirs(dir_name, 0o750)
|
||||
os.makedirs(dir_name, perms)
|
||||
|
||||
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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user