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
|
||||
|
||||
[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
|
||||
dulwich==0.19.0
|
||||
enum-compat==0.0.2
|
||||
eventlet==0.18.2
|
||||
eventlet==0.30.1
|
||||
extras==1.0.0
|
||||
fasteners==0.14.1
|
||||
fixtures==3.0.0
|
||||
@ -52,7 +52,7 @@ Mako==1.0.7
|
||||
MarkupSafe==1.1
|
||||
monotonic==1.4
|
||||
mox3==0.25.0
|
||||
msgpack==0.5.6
|
||||
msgpack==0.6.0
|
||||
munch==2.2.0
|
||||
netaddr==0.7.18
|
||||
netifaces==0.10.6
|
||||
@ -72,10 +72,11 @@ oslo.log==3.36.0
|
||||
oslo.messaging==9.3.0
|
||||
oslo.middleware==3.31.0
|
||||
oslo.policy==3.6.0
|
||||
oslo.privsep==2.4.0
|
||||
oslo.reports==1.18.0
|
||||
oslo.rootwrap==5.8.0
|
||||
oslo.serialization==2.18.0
|
||||
oslo.service==1.24.0
|
||||
oslo.service==2.5.0
|
||||
oslo.upgradecheck==1.3.0
|
||||
oslo.utils==4.8.0
|
||||
oslo.versionedobjects==1.33.3
|
||||
|
@ -7,7 +7,7 @@ Paste>=2.0.2 # MIT
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
Routes>=2.3.1 # MIT
|
||||
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
|
||||
jsonschema>=3.2.0 # MIT
|
||||
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.middleware>=3.31.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.rootwrap>=5.8.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.utils>=4.8.0 # 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-server = tacker.cmd.eventlet.tacker_server: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.service_plugins =
|
||||
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]
|
||||
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