diff options
-rw-r--r-- | test/conftest.py | 4 | ||||
-rw-r--r-- | test/test_go_isolation.py | 24 | ||||
-rw-r--r-- | test/test_php_isolation.py | 17 | ||||
-rw-r--r-- | test/test_python_isolation.py | 17 | ||||
-rw-r--r-- | test/test_python_isolation_chroot.py | 1 | ||||
-rw-r--r-- | test/test_ruby_isolation.py | 17 | ||||
-rw-r--r-- | test/unit/check/isolation.py | 158 | ||||
-rw-r--r-- | test/unit/feature/isolation.py | 160 | ||||
-rw-r--r-- | test/unit/main.py | 46 | ||||
-rw-r--r-- | test/unit/utils.py | 12 |
10 files changed, 197 insertions, 259 deletions
diff --git a/test/conftest.py b/test/conftest.py index 6783f16d..07d5f059 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -17,6 +17,7 @@ import pytest from unit.check.go import check_go from unit.check.node import check_node from unit.check.tls import check_openssl +from unit.check.isolation import check_isolation from unit.option import option from unit.utils import public_dir from unit.utils import waitforfiles @@ -123,6 +124,7 @@ def pytest_sessionstart(session): option.available = {'modules': {}, 'features': {}} unit = unit_run() + option.temp_dir = unit['temp_dir'] # read unit.log @@ -161,6 +163,8 @@ def pytest_sessionstart(session): k: v for k, v in option.available['modules'].items() if v is not None } + check_isolation() + unit_stop() _check_alerts() diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py index ac12c8ca..48c1b80c 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -5,31 +5,13 @@ import shutil import pytest -from conftest import unit_run -from conftest import unit_stop from unit.applications.lang.go import TestApplicationGo -from unit.feature.isolation import TestFeatureIsolation from unit.option import option +from unit.utils import getns class TestGoIsolation(TestApplicationGo): prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']} - isolation = TestFeatureIsolation() - - @classmethod - def setup_class(cls, complete_check=True): - check = super().setup_class(complete_check=False) - - unit = unit_run() - option.temp_dir = unit['temp_dir'] - - TestFeatureIsolation().check(option.available, unit['temp_dir']) - - assert unit_stop() is None - shutil.rmtree(unit['temp_dir']) - - return check if not complete_check else check() - def unpriv_creds(self): nobody_uid = pwd.getpwnam('nobody').pw_uid @@ -219,8 +201,8 @@ class TestGoIsolation(TestApplicationGo): == option.available['features']['isolation'][ns] ), ('%s match' % ns) - assert obj['NS']['MNT'] != self.isolation.getns('mnt'), 'mnt set' - assert obj['NS']['USER'] != self.isolation.getns('user'), 'user set' + assert obj['NS']['MNT'] != getns('mnt'), 'mnt set' + assert obj['NS']['USER'] != getns('user'), 'user set' def test_isolation_pid(self, is_su): if not self.isolation_key('pid'): diff --git a/test/test_php_isolation.py b/test/test_php_isolation.py index 1d0b0614..b0fea383 100644 --- a/test/test_php_isolation.py +++ b/test/test_php_isolation.py @@ -2,30 +2,13 @@ import shutil import pytest -from conftest import unit_run -from conftest import unit_stop from unit.applications.lang.php import TestApplicationPHP -from unit.feature.isolation import TestFeatureIsolation from unit.option import option class TestPHPIsolation(TestApplicationPHP): prerequisites = {'modules': {'php': 'any'}, 'features': ['isolation']} - @classmethod - def setup_class(cls, complete_check=True): - check = super().setup_class(complete_check=False) - - unit = unit_run() - option.temp_dir = unit['temp_dir'] - - TestFeatureIsolation().check(option.available, unit['temp_dir']) - - assert unit_stop() is None - shutil.rmtree(unit['temp_dir']) - - return check if not complete_check else check() - def test_php_isolation_rootfs(self, is_su, temp_dir): isolation_features = option.available['features']['isolation'].keys() diff --git a/test/test_python_isolation.py b/test/test_python_isolation.py index 66ff2f16..ad830269 100644 --- a/test/test_python_isolation.py +++ b/test/test_python_isolation.py @@ -2,30 +2,13 @@ import shutil import pytest -from conftest import unit_run -from conftest import unit_stop from unit.applications.lang.python import TestApplicationPython -from unit.feature.isolation import TestFeatureIsolation from unit.option import option class TestPythonIsolation(TestApplicationPython): prerequisites = {'modules': {'python': 'any'}, 'features': ['isolation']} - @classmethod - def setup_class(cls, complete_check=True): - check = super().setup_class(complete_check=False) - - unit = unit_run() - option.temp_dir = unit['temp_dir'] - - TestFeatureIsolation().check(option.available, unit['temp_dir']) - - assert unit_stop() is None - shutil.rmtree(unit['temp_dir']) - - return check if not complete_check else check() - def test_python_isolation_rootfs(self, is_su, temp_dir): isolation_features = option.available['features']['isolation'].keys() diff --git a/test/test_python_isolation_chroot.py b/test/test_python_isolation_chroot.py index 8018d5b9..7f559bcc 100644 --- a/test/test_python_isolation_chroot.py +++ b/test/test_python_isolation_chroot.py @@ -1,7 +1,6 @@ import pytest from unit.applications.lang.python import TestApplicationPython -from unit.feature.isolation import TestFeatureIsolation class TestPythonIsolation(TestApplicationPython): diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py index 8c382e5a..fab428e9 100644 --- a/test/test_ruby_isolation.py +++ b/test/test_ruby_isolation.py @@ -3,30 +3,13 @@ import shutil import os import pytest -from conftest import unit_run -from conftest import unit_stop from unit.applications.lang.ruby import TestApplicationRuby -from unit.feature.isolation import TestFeatureIsolation from unit.option import option class TestRubyIsolation(TestApplicationRuby): prerequisites = {'modules': {'ruby': 'any'}, 'features': ['isolation']} - @classmethod - def setup_class(cls, complete_check=True): - check = super().setup_class(complete_check=False) - - unit = unit_run() - option.temp_dir = unit['temp_dir'] - - TestFeatureIsolation().check(option.available, unit['temp_dir']) - - assert unit_stop() is None - shutil.rmtree(unit['temp_dir']) - - return check if not complete_check else check() - def test_ruby_isolation_rootfs(self, is_su): isolation_features = option.available['features']['isolation'].keys() diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py new file mode 100644 index 00000000..bb8feed1 --- /dev/null +++ b/test/unit/check/isolation.py @@ -0,0 +1,158 @@ +import json +import os + +from unit.applications.lang.go import TestApplicationGo +from unit.applications.lang.java import TestApplicationJava +from unit.applications.lang.node import TestApplicationNode +from unit.applications.proto import TestApplicationProto +from unit.http import TestHTTP +from unit.option import option +from unit.utils import getns + +allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net'] +http = TestHTTP() + +def check_isolation(): + test_conf = {"namespaces": {"credential": True}} + available = option.available + + conf = '' + if 'go' in available['modules']: + TestApplicationGo().prepare_env('empty', 'app') + + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "external", + "processes": {"spare": 0}, + "working_directory": option.test_dir + "/go/empty", + "executable": option.temp_dir + "/go/app", + "isolation": {"namespaces": {"credential": True}}, + }, + }, + } + + elif 'python' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": option.test_dir + "/python/empty", + "working_directory": option.test_dir + "/python/empty", + "module": "wsgi", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'php' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/phpinfo"}}, + "applications": { + "phpinfo": { + "type": "php", + "processes": {"spare": 0}, + "root": option.test_dir + "/php/phpinfo", + "working_directory": option.test_dir + "/php/phpinfo", + "index": "index.php", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'ruby' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "ruby", + "processes": {"spare": 0}, + "working_directory": option.test_dir + "/ruby/empty", + "script": option.test_dir + "/ruby/empty/config.ru", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'java' in available['modules']: + TestApplicationJava().prepare_env('empty') + + conf = { + "listeners": {"*:7080": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "unit_jars": option.current_dir + "/build", + "type": "java", + "processes": {"spare": 0}, + "working_directory": option.test_dir + "/java/empty/", + "webapp": option.temp_dir + "/java", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'node' in available['modules']: + TestApplicationNode().prepare_env('basic') + + conf = { + "listeners": {"*:7080": {"pass": "applications/basic"}}, + "applications": { + "basic": { + "type": "external", + "processes": {"spare": 0}, + "working_directory": option.temp_dir + "/node", + "executable": "app.js", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + elif 'perl' in available['modules']: + conf = { + "listeners": {"*:7080": {"pass": "applications/body_empty"}}, + "applications": { + "body_empty": { + "type": "perl", + "processes": {"spare": 0}, + "working_directory": option.test_dir + + "/perl/body_empty", + "script": option.test_dir + "/perl/body_empty/psgi.pl", + "isolation": {"namespaces": {"credential": True}}, + } + }, + } + + else: + return + + resp = http.put( + url='/config', + sock_type='unix', + addr=option.temp_dir + '/control.unit.sock', + body=json.dumps(conf), + ) + + if 'success' not in resp: + return + + userns = getns('user') + if not userns: + return + + available['features']['isolation'] = {'user': userns} + + unp_clone_path = '/proc/sys/kernel/unprivileged_userns_clone' + if os.path.exists(unp_clone_path): + with open(unp_clone_path, 'r') as f: + if str(f.read()).rstrip() == '1': + available['features']['isolation'][ + 'unprivileged_userns_clone' + ] = True + + for ns in allns: + ns_value = getns(ns) + if ns_value: + available['features']['isolation'][ns] = ns_value diff --git a/test/unit/feature/isolation.py b/test/unit/feature/isolation.py deleted file mode 100644 index d8f68919..00000000 --- a/test/unit/feature/isolation.py +++ /dev/null @@ -1,160 +0,0 @@ -import os - -from unit.applications.lang.go import TestApplicationGo -from unit.applications.lang.java import TestApplicationJava -from unit.applications.lang.node import TestApplicationNode -from unit.applications.proto import TestApplicationProto -from unit.option import option - - -class TestFeatureIsolation(TestApplicationProto): - allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net'] - - def check(self, available, temp_dir): - test_conf = {"namespaces": {"credential": True}} - - conf = '' - if 'go' in available['modules']: - TestApplicationGo().prepare_env('empty', 'app') - - conf = { - "listeners": {"*:7080": {"pass": "applications/empty"}}, - "applications": { - "empty": { - "type": "external", - "processes": {"spare": 0}, - "working_directory": option.test_dir + "/go/empty", - "executable": option.temp_dir + "/go/app", - "isolation": {"namespaces": {"credential": True}}, - }, - }, - } - - elif 'python' in available['modules']: - conf = { - "listeners": {"*:7080": {"pass": "applications/empty"}}, - "applications": { - "empty": { - "type": "python", - "processes": {"spare": 0}, - "path": option.test_dir + "/python/empty", - "working_directory": option.test_dir + "/python/empty", - "module": "wsgi", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'php' in available['modules']: - conf = { - "listeners": {"*:7080": {"pass": "applications/phpinfo"}}, - "applications": { - "phpinfo": { - "type": "php", - "processes": {"spare": 0}, - "root": option.test_dir + "/php/phpinfo", - "working_directory": option.test_dir + "/php/phpinfo", - "index": "index.php", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'ruby' in available['modules']: - conf = { - "listeners": {"*:7080": {"pass": "applications/empty"}}, - "applications": { - "empty": { - "type": "ruby", - "processes": {"spare": 0}, - "working_directory": option.test_dir + "/ruby/empty", - "script": option.test_dir + "/ruby/empty/config.ru", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'java' in available['modules']: - TestApplicationJava().prepare_env('empty') - - conf = { - "listeners": {"*:7080": {"pass": "applications/empty"}}, - "applications": { - "empty": { - "unit_jars": option.current_dir + "/build", - "type": "java", - "processes": {"spare": 0}, - "working_directory": option.test_dir + "/java/empty/", - "webapp": option.temp_dir + "/java", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'node' in available['modules']: - TestApplicationNode().prepare_env('basic') - - conf = { - "listeners": {"*:7080": {"pass": "applications/basic"}}, - "applications": { - "basic": { - "type": "external", - "processes": {"spare": 0}, - "working_directory": option.temp_dir + "/node", - "executable": "app.js", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - elif 'perl' in available['modules']: - conf = { - "listeners": {"*:7080": {"pass": "applications/body_empty"}}, - "applications": { - "body_empty": { - "type": "perl", - "processes": {"spare": 0}, - "working_directory": option.test_dir - + "/perl/body_empty", - "script": option.test_dir + "/perl/body_empty/psgi.pl", - "isolation": {"namespaces": {"credential": True}}, - } - }, - } - - else: - return - - if 'success' not in self.conf(conf): - return - - userns = self.getns('user') - if not userns: - return - - available['features']['isolation'] = {'user': userns} - - unp_clone_path = '/proc/sys/kernel/unprivileged_userns_clone' - if os.path.exists(unp_clone_path): - with open(unp_clone_path, 'r') as f: - if str(f.read()).rstrip() == '1': - available['features']['isolation'][ - 'unprivileged_userns_clone' - ] = True - - for ns in self.allns: - ns_value = self.getns(ns) - if ns_value: - available['features']['isolation'][ns] = ns_value - - def getns(self, nstype): - # read namespace id from symlink file: - # it points to: '<nstype>:[<ns id>]' - # # eg.: 'pid:[4026531836]' - nspath = '/proc/self/ns/' + nstype - data = None - - if os.path.exists(nspath): - data = int(os.readlink(nspath)[len(nstype) + 2 : -1]) - - return data diff --git a/test/unit/main.py b/test/unit/main.py index fce6a322..749ff3ab 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -4,39 +4,33 @@ from unit.option import option class TestUnit(): @classmethod - def setup_class(cls, complete_check=True): - def check(): - missed = [] + def setup_class(cls): + missed = [] - # check modules + # check modules - if 'modules' in cls.prerequisites: - available_modules = list(option.available['modules'].keys()) + if 'modules' in cls.prerequisites: + available_modules = list(option.available['modules'].keys()) - for module in cls.prerequisites['modules']: - if module in available_modules: - continue + for module in cls.prerequisites['modules']: + if module in available_modules: + continue - missed.append(module) + missed.append(module) - if missed: - pytest.skip('Unit has no ' + ', '.join(missed) + ' module(s)') + if missed: + pytest.skip('Unit has no ' + ', '.join(missed) + ' module(s)') - # check features + # check features - if 'features' in cls.prerequisites: - available_features = list(option.available['features'].keys()) + if 'features' in cls.prerequisites: + available_features = list(option.available['features'].keys()) - for feature in cls.prerequisites['features']: - if feature in available_features: - continue + for feature in cls.prerequisites['features']: + if feature in available_features: + continue - missed.append(feature) + missed.append(feature) - if missed: - pytest.skip(', '.join(missed) + ' feature(s) not supported') - - if complete_check: - check() - else: - return check + if missed: + pytest.skip(', '.join(missed) + ' feature(s) not supported') diff --git a/test/unit/utils.py b/test/unit/utils.py index f24e9728..1307a4f6 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -48,3 +48,15 @@ def waitforsocket(port): pytest.fail('Can\'t connect to the 127.0.0.1:' + port) + +def getns(nstype): + # read namespace id from symlink file: + # it points to: '<nstype>:[<ns id>]' + # # eg.: 'pid:[4026531836]' + nspath = '/proc/self/ns/' + nstype + data = None + + if os.path.exists(nspath): + data = int(os.readlink(nspath)[len(nstype) + 2 : -1]) + + return data |