Migrate rootwrap to privsep
As a part of the community goal [1], we should replace rootwrap in favor of privsep. Although the latest codes don't have a dependency on rootwrap for now, but it might be happened to introduce a task with root privilege after. In addition, there are some complex mechanism embeded in Tacker such as in setuptools or configs under `etc/`. It's hard to drop and restore them again. So, keep the mechanism active. In this update, two methods used for tests are implemented in `tacker/privileged/linux_cmd.py`, but can be used for general purpose. For the test, it's also including rootwrap for backward compatibility which will be removed in a future update. It also updates required libs as bellow for oslo.privsep 2.4.0. - eventlet>=0.30.1 - msgpack>=0.6.0 - oslo.service>=2.5.0 [1] https://governance.openstack.org/tc/goals/selected/migrate-to-privsep.html Partially-Implements: bp privsep-migration Signed-off-by: Yasufumi Ogawa <yasufum.o@gmail.com> Change-Id: Id8de4c2bae91718d6ba45ed523edc103f0b21718
This commit is contained in:
parent
4d11475f3a
commit
3d6d849faa
@ -7,4 +7,8 @@
|
|||||||
# cmd-name: filter-name, raw-command, user, args
|
# cmd-name: filter-name, raw-command, user, args
|
||||||
|
|
||||||
[Filters]
|
[Filters]
|
||||||
|
privsep-rootwrap-sys_admin: RegExpFilter, privsep-helper, root, privsep-helper, --config-file, /etc/(?!\.\.).*, --privsep_context, tacker.privileged.default, --privsep_sock_path, /tmp/.*
|
||||||
|
|
||||||
|
# This definition is for test purpose. It's used in
|
||||||
|
# 'tacker.tests.unit.test_rootwrap_exec.RootwrapTestExec.test_rootwrap'
|
||||||
|
pwd: CommandFilter, pwd, root
|
||||||
|
@ -24,7 +24,7 @@ docutils==0.14
|
|||||||
dogpile.cache==0.6.5
|
dogpile.cache==0.6.5
|
||||||
dulwich==0.19.0
|
dulwich==0.19.0
|
||||||
enum-compat==0.0.2
|
enum-compat==0.0.2
|
||||||
eventlet==0.18.2
|
eventlet==0.30.1
|
||||||
extras==1.0.0
|
extras==1.0.0
|
||||||
fasteners==0.14.1
|
fasteners==0.14.1
|
||||||
fixtures==3.0.0
|
fixtures==3.0.0
|
||||||
@ -52,7 +52,7 @@ Mako==1.0.7
|
|||||||
MarkupSafe==1.1
|
MarkupSafe==1.1
|
||||||
monotonic==1.4
|
monotonic==1.4
|
||||||
mox3==0.25.0
|
mox3==0.25.0
|
||||||
msgpack==0.5.6
|
msgpack==0.6.0
|
||||||
munch==2.2.0
|
munch==2.2.0
|
||||||
netaddr==0.7.18
|
netaddr==0.7.18
|
||||||
netifaces==0.10.6
|
netifaces==0.10.6
|
||||||
@ -72,10 +72,11 @@ oslo.log==3.36.0
|
|||||||
oslo.messaging==9.3.0
|
oslo.messaging==9.3.0
|
||||||
oslo.middleware==3.31.0
|
oslo.middleware==3.31.0
|
||||||
oslo.policy==3.6.0
|
oslo.policy==3.6.0
|
||||||
|
oslo.privsep==2.4.0
|
||||||
oslo.reports==1.18.0
|
oslo.reports==1.18.0
|
||||||
oslo.rootwrap==5.8.0
|
oslo.rootwrap==5.8.0
|
||||||
oslo.serialization==2.18.0
|
oslo.serialization==2.18.0
|
||||||
oslo.service==1.24.0
|
oslo.service==2.5.0
|
||||||
oslo.upgradecheck==1.3.0
|
oslo.upgradecheck==1.3.0
|
||||||
oslo.utils==4.8.0
|
oslo.utils==4.8.0
|
||||||
oslo.versionedobjects==1.33.3
|
oslo.versionedobjects==1.33.3
|
||||||
|
@ -7,7 +7,7 @@ Paste>=2.0.2 # MIT
|
|||||||
PasteDeploy>=1.5.0 # MIT
|
PasteDeploy>=1.5.0 # MIT
|
||||||
Routes>=2.3.1 # MIT
|
Routes>=2.3.1 # MIT
|
||||||
amqp>=2.4.0
|
amqp>=2.4.0
|
||||||
eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
|
eventlet>=0.30.1 # MIT
|
||||||
requests>=2.25.1 # Apache-2.0
|
requests>=2.25.1 # Apache-2.0
|
||||||
jsonschema>=3.2.0 # MIT
|
jsonschema>=3.2.0 # MIT
|
||||||
keystonemiddleware>=4.17.0 # Apache-2.0
|
keystonemiddleware>=4.17.0 # Apache-2.0
|
||||||
@ -27,10 +27,11 @@ oslo.log>=3.36.0 # Apache-2.0
|
|||||||
oslo.messaging>=9.3.0 # Apache-2.0
|
oslo.messaging>=9.3.0 # Apache-2.0
|
||||||
oslo.middleware>=3.31.0 # Apache-2.0
|
oslo.middleware>=3.31.0 # Apache-2.0
|
||||||
oslo.policy>=3.6.0 # Apache-2.0
|
oslo.policy>=3.6.0 # Apache-2.0
|
||||||
|
oslo.privsep>=2.4.0 # Apache-2.0
|
||||||
oslo.reports>=1.18.0 # Apache-2.0
|
oslo.reports>=1.18.0 # Apache-2.0
|
||||||
oslo.rootwrap>=5.8.0 # Apache-2.0
|
oslo.rootwrap>=5.8.0 # Apache-2.0
|
||||||
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
|
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
|
||||||
oslo.service!=1.28.1,>=1.24.0 # Apache-2.0
|
oslo.service>=2.5.0 # Apache-2.0
|
||||||
oslo.upgradecheck>=1.3.0 # Apache-2.0
|
oslo.upgradecheck>=1.3.0 # Apache-2.0
|
||||||
oslo.utils>=4.8.0 # Apache-2.0
|
oslo.utils>=4.8.0 # Apache-2.0
|
||||||
oslo.versionedobjects>=1.33.3 # Apache-2.0
|
oslo.versionedobjects>=1.33.3 # Apache-2.0
|
||||||
|
@ -43,7 +43,7 @@ console_scripts =
|
|||||||
tacker-db-manage = tacker.db.migration.cli:main
|
tacker-db-manage = tacker.db.migration.cli:main
|
||||||
tacker-server = tacker.cmd.eventlet.tacker_server:main
|
tacker-server = tacker.cmd.eventlet.tacker_server:main
|
||||||
tacker-conductor = tacker.cmd.eventlet.conductor:main
|
tacker-conductor = tacker.cmd.eventlet.conductor:main
|
||||||
tacker-rootwrap = oslo.rootwrap.cmd:main
|
tacker-rootwrap = oslo_rootwrap.cmd:main
|
||||||
tacker-status = tacker.cmd.status:main
|
tacker-status = tacker.cmd.status:main
|
||||||
tacker.service_plugins =
|
tacker.service_plugins =
|
||||||
dummy = tacker.tests.unit.dummy_plugin:DummyServicePlugin
|
dummy = tacker.tests.unit.dummy_plugin:DummyServicePlugin
|
||||||
|
31
tacker/privileged/__init__.py
Normal file
31
tacker/privileged/__init__.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""Setup privsep decorator."""
|
||||||
|
|
||||||
|
from oslo_privsep import capabilities as caps
|
||||||
|
from oslo_privsep import priv_context
|
||||||
|
|
||||||
|
default = priv_context.PrivContext(
|
||||||
|
__name__,
|
||||||
|
cfg_section='privsep',
|
||||||
|
pypath="f{__name__}.default",
|
||||||
|
capabilities=[caps.CAP_SYS_ADMIN,
|
||||||
|
caps.CAP_NET_ADMIN,
|
||||||
|
caps.CAP_DAC_OVERRIDE,
|
||||||
|
caps.CAP_DAC_READ_SEARCH,
|
||||||
|
caps.CAP_SYS_PTRACE],
|
||||||
|
)
|
33
tacker/privileged/linux_cmd.py
Normal file
33
tacker/privileged/linux_cmd.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""Basic Linux commands intented to be used in unittests"""
|
||||||
|
|
||||||
|
from oslo_concurrency import processutils
|
||||||
|
|
||||||
|
import tacker.privileged
|
||||||
|
|
||||||
|
|
||||||
|
@tacker.privileged.default.entrypoint
|
||||||
|
def pwd():
|
||||||
|
pwd = processutils.execute('pwd')
|
||||||
|
return pwd
|
||||||
|
|
||||||
|
|
||||||
|
@tacker.privileged.default.entrypoint
|
||||||
|
def ls():
|
||||||
|
ls = processutils.execute('ls')
|
||||||
|
return ls
|
@ -1,12 +0,0 @@
|
|||||||
# tacker-rootwrap command filters for the unit test
|
|
||||||
|
|
||||||
# this file goes with tacker/tests/unit/_test_rootwrap_exec.py.
|
|
||||||
# See the comments there about how to run that unit tests
|
|
||||||
|
|
||||||
# format seems to be
|
|
||||||
# cmd-name: filter-name, raw-command, user, args
|
|
||||||
|
|
||||||
[Filters]
|
|
||||||
|
|
||||||
# a test filter for the RootwrapTest unit test
|
|
||||||
bash: CommandFilter, /usr/bin/bash, root
|
|
@ -21,4 +21,3 @@ lock_path = $state_path/lock
|
|||||||
|
|
||||||
[database]
|
[database]
|
||||||
connection = 'sqlite://'
|
connection = 'sqlite://'
|
||||||
|
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
# Copyright (c) 2012 OpenStack Foundation
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import fixtures
|
|
||||||
from oslo_log import log as logging
|
|
||||||
|
|
||||||
from tacker.agent.linux import utils
|
|
||||||
from tacker.tests import base
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class RootwrapTestExec(base.BaseTestCase):
|
|
||||||
"""Simple unit test to test the basic rootwrap mechanism
|
|
||||||
|
|
||||||
Essentially hello-world. Just run a command as root and check that
|
|
||||||
it actually *did* run as root, and generated the right output.
|
|
||||||
|
|
||||||
NB that this is named _test_rootwrap so as not to get run by default
|
|
||||||
from scripts like tox. That's because it actually executes a sudo'ed
|
|
||||||
command, and that won't work in the automated test environment, at
|
|
||||||
least as it stands today. To run this, rename it to
|
|
||||||
test_rootwrap.py, or run it by hand.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(RootwrapTestExec, self).setUp()
|
|
||||||
self.cwd = os.getcwd() + "/../../.."
|
|
||||||
# stuff a stupid bash script into /tmp, so that the next
|
|
||||||
# method can execute it.
|
|
||||||
self.test_file = self.useFixture(
|
|
||||||
fixtures.TempDir()).join("rootwrap-test.sh")
|
|
||||||
with open(self.test_file, 'w') as f:
|
|
||||||
f.write('#!/bin/bash\n')
|
|
||||||
f.write('ID=`id | sed \'s/uid=//\' | sed \'s/(.*//\' `\n')
|
|
||||||
f.write("echo $ID $1\
|
|
||||||
\" Now is the time for all good men to come \
|
|
||||||
to the aid of their party.\"\n")
|
|
||||||
# we need a temporary conf file, pointing into pwd for the filter
|
|
||||||
# specs. there's probably a better way to do this, but I couldn't
|
|
||||||
# figure it out. 08/15/12 -- jrd
|
|
||||||
self.conf_file = self.useFixture(
|
|
||||||
fixtures.TempDir()).join("rootwrap.conf")
|
|
||||||
with open(self.conf_file, 'w') as f:
|
|
||||||
f.write("# temporary conf file for rootwrap-test, " +
|
|
||||||
"generated by test_rootwrap.py\n")
|
|
||||||
f.write("[DEFAULT]\n")
|
|
||||||
f.write("filters_path=" + self.cwd +
|
|
||||||
"/tacker/tests/etc/rootwrap.d/")
|
|
||||||
# now set the root helper to sudo our rootwrap script,
|
|
||||||
# with the new conf
|
|
||||||
self.root_helper = "sudo " + self.cwd + "/bin/tacker-rootwrap "
|
|
||||||
self.root_helper += self.conf_file
|
|
||||||
|
|
||||||
def runTest(self):
|
|
||||||
try:
|
|
||||||
result = utils.execute(["bash", self.test_file, 'arg'],
|
|
||||||
self.root_helper)
|
|
||||||
self.assertEqual("0 arg Now is the time for all good men to \
|
|
||||||
come to the aid of their party.", result)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception("Losing in rootwrap test")
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
os.remove(self.test_file)
|
|
||||||
os.remove(self.conf_file)
|
|
||||||
super(RootwrapTestExec, self).tearDown()
|
|
85
tacker/tests/unit/test_privsep.py
Normal file
85
tacker/tests/unit/test_privsep.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Copyright (c) 2012 OpenStack Foundation
|
||||||
|
# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
from oslo_log import log as logging
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from tacker.agent.linux import utils
|
||||||
|
import tacker.privileged.linux_cmd
|
||||||
|
from tacker.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Use env 'PWD' to check if tests are run on zuul because we cannot run the
|
||||||
|
# tests require root privileges. Skip them on zuul, but still run on
|
||||||
|
# localhost to test privsep features.
|
||||||
|
_PWD = os.environ['PWD']
|
||||||
|
_PWD_ZUUL = "/home/zuul/src/opendev.org/openstack/tacker"
|
||||||
|
|
||||||
|
|
||||||
|
class PrivsepTest(base.BaseTestCase):
|
||||||
|
"""Simple unit test to test the basic privsep mechanism
|
||||||
|
|
||||||
|
Essentially hello-world. Just run a command as root and check that
|
||||||
|
it actually *did* run as root.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(PrivsepTest, self).setUp()
|
||||||
|
|
||||||
|
@unittest.skipIf(_PWD == _PWD_ZUUL, "Failed on zuul for root priv")
|
||||||
|
def test_privsep_ls(self):
|
||||||
|
"""Run ls with root privilege
|
||||||
|
|
||||||
|
This ls command is expected to be run on `/`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ls = tacker.privileged.linux_cmd.ls()
|
||||||
|
# The result is a series of dirs on '/' and separated with '\n' like
|
||||||
|
# as 'bin\nboot\ndev\netc\n...'.
|
||||||
|
res = ls[0].split('\n')
|
||||||
|
|
||||||
|
# 'boot' dir must be under '/'.
|
||||||
|
self.assertIn('boot', res)
|
||||||
|
|
||||||
|
@unittest.skipIf(_PWD == _PWD_ZUUL, "Failed on zuul for root priv")
|
||||||
|
def test_privsep_pwd(self):
|
||||||
|
"""Run pwd with root privilege
|
||||||
|
|
||||||
|
This ls command is expected to be run on `/`.
|
||||||
|
"""
|
||||||
|
res = tacker.privileged.linux_cmd.pwd()[0]
|
||||||
|
self.assertEqual('/\n', res)
|
||||||
|
|
||||||
|
@unittest.skipIf(_PWD == _PWD_ZUUL, "Failed on zuul for root priv")
|
||||||
|
def test_rootwrap(self):
|
||||||
|
"""Confirm a command can be run with tacker-rootwrap
|
||||||
|
|
||||||
|
pwd is used as a harmless command in this test and defined in
|
||||||
|
'/etc/tacker/rootwrap.d/tacker.filters' as a CommandFilter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
root_helper = ["sudo", "tacker-rootwrap",
|
||||||
|
"/etc/tacker/rootwrap.conf"]
|
||||||
|
cmd = "pwd"
|
||||||
|
|
||||||
|
actual = utils.execute(root_helper + [cmd])
|
||||||
|
expected = utils.execute([cmd])
|
||||||
|
|
||||||
|
self.assertEqual(expected, actual)
|
Loading…
Reference in New Issue
Block a user