summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-06-14 08:04:18 +0000
committerGerrit Code Review <review@openstack.org>2018-06-14 08:04:18 +0000
commite1cef0ea518c29d7375dfc9487d1591f1176e6ad (patch)
treed7d8733e6c13e074d731edbf977cd33a35b9ef93
parent4335b3e5442eb7cf91bb95b3aacdcc55f07b1f8b (diff)
parent861646d1ba53f6becea59bc50306229e162f0c6c (diff)
Merge "Implement privsep boilerplate in cinder."
-rw-r--r--cinder/privsep/__init__.py32
-rw-r--r--cinder/privsep/cgroup.py35
-rw-r--r--cinder/test.py3
-rw-r--r--cinder/tests/fixtures.py29
-rw-r--r--cinder/tests/unit/test_volume_throttling.py53
-rw-r--r--cinder/volume/throttling.py7
-rw-r--r--etc/cinder/rootwrap.d/volume.filters6
-rw-r--r--releasenotes/notes/privsep-rocky-35bdfe70ed62a826.yaml14
8 files changed, 141 insertions, 38 deletions
diff --git a/cinder/privsep/__init__.py b/cinder/privsep/__init__.py
new file mode 100644
index 0000000..7f826a8
--- /dev/null
+++ b/cinder/privsep/__init__.py
@@ -0,0 +1,32 @@
1# Copyright 2016 Red Hat, Inc
2# Copyright 2017 Rackspace Australia
3# Copyright 2018 Michael Still and Aptira
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17"""Setup privsep decorator."""
18
19from oslo_privsep import capabilities
20from oslo_privsep import priv_context
21
22sys_admin_pctxt = priv_context.PrivContext(
23 'cinder',
24 cfg_section='cinder_sys_admin',
25 pypath=__name__ + '.sys_admin_pctxt',
26 capabilities=[capabilities.CAP_CHOWN,
27 capabilities.CAP_DAC_OVERRIDE,
28 capabilities.CAP_DAC_READ_SEARCH,
29 capabilities.CAP_FOWNER,
30 capabilities.CAP_NET_ADMIN,
31 capabilities.CAP_SYS_ADMIN],
32)
diff --git a/cinder/privsep/cgroup.py b/cinder/privsep/cgroup.py
new file mode 100644
index 0000000..15d47e0
--- /dev/null
+++ b/cinder/privsep/cgroup.py
@@ -0,0 +1,35 @@
1# Copyright 2016 Red Hat, Inc
2# Copyright 2017 Rackspace Australia
3# Copyright 2018 Michael Still and Aptira
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17"""
18Helpers for cgroup related routines.
19"""
20
21from oslo_concurrency import processutils
22
23import cinder.privsep
24
25
26@cinder.privsep.sys_admin_pctxt.entrypoint
27def cgroup_create(name):
28 processutils.execute('cgcreate', '-g', 'blkio:%s' % name)
29
30
31@cinder.privsep.sys_admin_pctxt.entrypoint
32def cgroup_limit(name, rw, dev, bps):
33 processutils.execute('cgset', '-r',
34 'blkio.throttle.%s_bps_device=%s %d' % (rw, dev, bps),
35 name)
diff --git a/cinder/test.py b/cinder/test.py
index 5c42140..85fb558 100644
--- a/cinder/test.py
+++ b/cinder/test.py
@@ -302,6 +302,9 @@ class TestCase(testtools.TestCase):
302 tpool.killall() 302 tpool.killall()
303 tpool._nthreads = 20 303 tpool._nthreads = 20
304 304
305 # NOTE(mikal): make sure we don't load a privsep helper accidentally
306 self.useFixture(cinder_fixtures.PrivsepNoHelperFixture())
307
305 def _restore_obj_registry(self): 308 def _restore_obj_registry(self):
306 objects_base.CinderObjectRegistry._registry._obj_classes = \ 309 objects_base.CinderObjectRegistry._registry._obj_classes = \
307 self._base_test_obj_backup 310 self._base_test_obj_backup
diff --git a/cinder/tests/fixtures.py b/cinder/tests/fixtures.py
index 6e275a7..79e0b73 100644
--- a/cinder/tests/fixtures.py
+++ b/cinder/tests/fixtures.py
@@ -1,4 +1,6 @@
1# Copyright 2016 IBM Corp. 1# Copyright 2016 IBM Corp.
2# Copyright 2017 Rackspace Australia
3# Copyright 2018 Michael Still and Aptira
2# 4#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain 6# not use this file except in compliance with the License. You may obtain
@@ -21,6 +23,7 @@ import os
21import warnings 23import warnings
22 24
23import fixtures 25import fixtures
26from oslo_privsep import daemon as privsep_daemon
24 27
25_TRUE_VALUES = ('True', 'true', '1', 'yes') 28_TRUE_VALUES = ('True', 'true', '1', 'yes')
26 29
@@ -131,3 +134,29 @@ class WarningsFixture(fixtures.Fixture):
131 ' This key is deprecated. Please update your policy ' 134 ' This key is deprecated. Please update your policy '
132 'file to use the standard policy values.') 135 'file to use the standard policy values.')
133 self.addCleanup(warnings.resetwarnings) 136 self.addCleanup(warnings.resetwarnings)
137
138
139class UnHelperfulClientChannel(privsep_daemon._ClientChannel):
140 def __init__(self, context):
141 raise Exception('You have attempted to start a privsep helper. '
142 'This is not allowed in the gate, and '
143 'indicates a failure to have mocked your tests.')
144
145
146class PrivsepNoHelperFixture(fixtures.Fixture):
147 """A fixture to catch failures to mock privsep's rootwrap helper.
148
149 If you fail to mock away a privsep'd method in a unit test, then
150 you may well end up accidentally running the privsep rootwrap
151 helper. This will fail in the gate, but it fails in a way which
152 doesn't identify which test is missing a mock. Instead, we
153 raise an exception so that you at least know where you've missed
154 something.
155 """
156
157 def setUp(self):
158 super(PrivsepNoHelperFixture, self).setUp()
159
160 self.useFixture(fixtures.MonkeyPatch(
161 'oslo_privsep.daemon.RootwrapClientChannel',
162 UnHelperfulClientChannel))
diff --git a/cinder/tests/unit/test_volume_throttling.py b/cinder/tests/unit/test_volume_throttling.py
index 82e2645..edbc2d9 100644
--- a/cinder/tests/unit/test_volume_throttling.py
+++ b/cinder/tests/unit/test_volume_throttling.py
@@ -29,7 +29,9 @@ class ThrottleTestCase(test.TestCase):
29 self.assertEqual([], cmd['prefix']) 29 self.assertEqual([], cmd['prefix'])
30 30
31 @mock.patch.object(utils, 'get_blkdev_major_minor') 31 @mock.patch.object(utils, 'get_blkdev_major_minor')
32 def test_BlkioCgroup(self, mock_major_minor): 32 @mock.patch('cinder.privsep.cgroup.cgroup_create')
33 @mock.patch('cinder.privsep.cgroup.cgroup_limit')
34 def test_BlkioCgroup(self, mock_limit, mock_create, mock_major_minor):
33 35
34 def fake_get_blkdev_major_minor(path): 36 def fake_get_blkdev_major_minor(path):
35 return {'src_volume1': "253:0", 'dst_volume1': "253:1", 37 return {'src_volume1': "253:0", 'dst_volume1': "253:1",
@@ -37,38 +39,25 @@ class ThrottleTestCase(test.TestCase):
37 39
38 mock_major_minor.side_effect = fake_get_blkdev_major_minor 40 mock_major_minor.side_effect = fake_get_blkdev_major_minor
39 41
40 self.exec_cnt = 0 42 throttle = throttling.BlkioCgroup(1024, 'fake_group')
43 with throttle.subcommand('src_volume1', 'dst_volume1') as cmd:
44 self.assertEqual(['cgexec', '-g', 'blkio:fake_group'],
45 cmd['prefix'])
41 46
42 def fake_execute(*cmd, **kwargs): 47 # a nested job
43 cmd_set = ['cgset', '-r', 48 with throttle.subcommand('src_volume2', 'dst_volume2') as cmd:
44 'blkio.throttle.%s_bps_device=%s %d', 'fake_group']
45 set_order = [None,
46 ('read', '253:0', 1024),
47 ('write', '253:1', 1024),
48 # a nested job starts; bps limit are set to the half
49 ('read', '253:0', 512),
50 ('read', '253:2', 512),
51 ('write', '253:1', 512),
52 ('write', '253:3', 512),
53 # a nested job ends; bps limit is resumed
54 ('read', '253:0', 1024),
55 ('write', '253:1', 1024)]
56
57 if set_order[self.exec_cnt] is None:
58 self.assertEqual(('cgcreate', '-g', 'blkio:fake_group'), cmd)
59 else:
60 cmd_set[2] %= set_order[self.exec_cnt]
61 self.assertEqual(tuple(cmd_set), cmd)
62
63 self.exec_cnt += 1
64
65 with mock.patch.object(utils, 'execute', side_effect=fake_execute):
66 throttle = throttling.BlkioCgroup(1024, 'fake_group')
67 with throttle.subcommand('src_volume1', 'dst_volume1') as cmd:
68 self.assertEqual(['cgexec', '-g', 'blkio:fake_group'], 49 self.assertEqual(['cgexec', '-g', 'blkio:fake_group'],
69 cmd['prefix']) 50 cmd['prefix'])
70 51
71 # a nested job 52 mock_create.assert_has_calls([mock.call('fake_group')])
72 with throttle.subcommand('src_volume2', 'dst_volume2') as cmd: 53 mock_limit.assert_has_calls([
73 self.assertEqual(['cgexec', '-g', 'blkio:fake_group'], 54 mock.call('fake_group', 'read', '253:0', 1024),
74 cmd['prefix']) 55 mock.call('fake_group', 'write', '253:1', 1024),
56 # a nested job starts; bps limit are set to the half
57 mock.call('fake_group', 'read', '253:0', 512),
58 mock.call('fake_group', 'read', '253:2', 512),
59 mock.call('fake_group', 'write', '253:1', 512),
60 mock.call('fake_group', 'write', '253:3', 512),
61 # a nested job ends; bps limit is resumed
62 mock.call('fake_group', 'read', '253:0', 1024),
63 mock.call('fake_group', 'write', '253:1', 1024)])
diff --git a/cinder/volume/throttling.py b/cinder/volume/throttling.py
index 39cbbeb..3c6ddaa 100644
--- a/cinder/volume/throttling.py
+++ b/cinder/volume/throttling.py
@@ -22,6 +22,7 @@ from oslo_concurrency import processutils
22from oslo_log import log as logging 22from oslo_log import log as logging
23 23
24from cinder import exception 24from cinder import exception
25import cinder.privsep.cgroup
25from cinder import utils 26from cinder import utils
26 27
27 28
@@ -65,8 +66,7 @@ class BlkioCgroup(Throttle):
65 self.dstdevs = {} 66 self.dstdevs = {}
66 67
67 try: 68 try:
68 utils.execute('cgcreate', '-g', 'blkio:%s' % self.cgroup, 69 cinder.privsep.cgroup.cgroup_create(self.cgroup)
69 run_as_root=True)
70 except processutils.ProcessExecutionError: 70 except processutils.ProcessExecutionError:
71 LOG.error('Failed to create blkio cgroup \'%(name)s\'.', 71 LOG.error('Failed to create blkio cgroup \'%(name)s\'.',
72 {'name': cgroup_name}) 72 {'name': cgroup_name})
@@ -81,8 +81,7 @@ class BlkioCgroup(Throttle):
81 81
82 def _limit_bps(self, rw, dev, bps): 82 def _limit_bps(self, rw, dev, bps):
83 try: 83 try:
84 utils.execute('cgset', '-r', 'blkio.throttle.%s_bps_device=%s %d' 84 cinder.privsep.cgroup.cgroup_limit(self.cgroup, rw, dev, bps)
85 % (rw, dev, bps), self.cgroup, run_as_root=True)
86 except processutils.ProcessExecutionError: 85 except processutils.ProcessExecutionError:
87 LOG.warning('Failed to setup blkio cgroup to throttle the ' 86 LOG.warning('Failed to setup blkio cgroup to throttle the '
88 'device \'%(device)s\'.', {'device': dev}) 87 'device \'%(device)s\'.', {'device': dev})
diff --git a/etc/cinder/rootwrap.d/volume.filters b/etc/cinder/rootwrap.d/volume.filters
index d66b89e..0c6351c 100644
--- a/etc/cinder/rootwrap.d/volume.filters
+++ b/etc/cinder/rootwrap.d/volume.filters
@@ -43,6 +43,10 @@ lvdisplay4: EnvFilter, env, root, LC_ALL=C, LVM_SYSTEM_DIR=, LVM_SUPPRESS_FD_WAR
43# This line ties the superuser privs with the config files, context name, 43# This line ties the superuser privs with the config files, context name,
44# and (implicitly) the actual python code invoked. 44# and (implicitly) the actual python code invoked.
45privsep-rootwrap: RegExpFilter, privsep-helper, root, privsep-helper, --config-file, /etc/(?!\.\.).*, --privsep_context, os_brick.privileged.default, --privsep_sock_path, /tmp/.* 45privsep-rootwrap: RegExpFilter, privsep-helper, root, privsep-helper, --config-file, /etc/(?!\.\.).*, --privsep_context, os_brick.privileged.default, --privsep_sock_path, /tmp/.*
46
47# Privsep calls within cinder iteself
48privsep-rootwrap-sys_admin: RegExpFilter, privsep-helper, root, privsep-helper, --config-file, /etc/(?!\.\.).*, --privsep_context, cinder.privsep.sys_admin_pctxt, --privsep_sock_path, /tmp/.*
49
46# The following and any cinder/brick/* entries should all be obsoleted 50# The following and any cinder/brick/* entries should all be obsoleted
47# by privsep, and may be removed once the os-brick version requirement 51# by privsep, and may be removed once the os-brick version requirement
48# is updated appropriately. 52# is updated appropriately.
@@ -93,8 +97,6 @@ ionice_1: ChainingRegExpFilter, ionice, root, ionice, -c[0-3], -n[0-7]
93ionice_2: ChainingRegExpFilter, ionice, root, ionice, -c[0-3] 97ionice_2: ChainingRegExpFilter, ionice, root, ionice, -c[0-3]
94 98
95# cinder/volume/utils.py: setup_blkio_cgroup() 99# cinder/volume/utils.py: setup_blkio_cgroup()
96cgcreate: CommandFilter, cgcreate, root
97cgset: CommandFilter, cgset, root
98cgexec: ChainingRegExpFilter, cgexec, root, cgexec, -g, blkio:\S+ 100cgexec: ChainingRegExpFilter, cgexec, root, cgexec, -g, blkio:\S+
99 101
100# cinder/volume/driver.py 102# cinder/volume/driver.py
diff --git a/releasenotes/notes/privsep-rocky-35bdfe70ed62a826.yaml b/releasenotes/notes/privsep-rocky-35bdfe70ed62a826.yaml
new file mode 100644
index 0000000..b3a4c34
--- /dev/null
+++ b/releasenotes/notes/privsep-rocky-35bdfe70ed62a826.yaml
@@ -0,0 +1,14 @@
1---
2security:
3 - |
4 Privsep transitions. Cinder is transitioning from using the older style
5 rootwrap privilege escalation path to the new style Oslo privsep path.
6 This should improve performance and security of Cinder in the long term.
7 - |
8 Privsep daemons are now started by Cinder when required. These daemons can
9 be started via rootwrap if required. rootwrap configs therefore need to
10 be updated to include new privsep daemon invocations.
11upgrade:
12 - |
13 The following commands are no longer required to be listed in your rootwrap
14 configuration: cgcreate; and cgset.