11477Szelenkov@nginx.comimport grp 21596Szelenkov@nginx.comimport os 31293St.nateldemoura@f5.comimport pwd 41654Szelenkov@nginx.comimport shutil 51635Szelenkov@nginx.com 61596Szelenkov@nginx.comimport pytest 71477Szelenkov@nginx.com 81654Szelenkov@nginx.comfrom conftest import option 91654Szelenkov@nginx.comfrom conftest import unit_run 101654Szelenkov@nginx.comfrom conftest import unit_stop 111182St.nateldemoura@f5.comfrom unit.applications.lang.go import TestApplicationGo 121182St.nateldemoura@f5.comfrom unit.feature.isolation import TestFeatureIsolation 131182St.nateldemoura@f5.com 141182St.nateldemoura@f5.comclass TestGoIsolation(TestApplicationGo): 151467Szelenkov@nginx.com prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']} 161182St.nateldemoura@f5.com 171182St.nateldemoura@f5.com isolation = TestFeatureIsolation() 181182St.nateldemoura@f5.com 191182St.nateldemoura@f5.com @classmethod 201596Szelenkov@nginx.com def setup_class(cls, complete_check=True): 211654Szelenkov@nginx.com check = super().setup_class(complete_check=False) 221654Szelenkov@nginx.com 231654Szelenkov@nginx.com unit = unit_run() 241654Szelenkov@nginx.com option.temp_dir = unit['temp_dir'] 251182St.nateldemoura@f5.com 261654Szelenkov@nginx.com TestFeatureIsolation().check(option.available, unit['temp_dir']) 271182St.nateldemoura@f5.com 281654Szelenkov@nginx.com assert unit_stop() is None 291654Szelenkov@nginx.com shutil.rmtree(unit['temp_dir']) 301654Szelenkov@nginx.com 311654Szelenkov@nginx.com return check if not complete_check else check() 321182St.nateldemoura@f5.com 331307St.nateldemoura@f5.com def unpriv_creds(self): 341307St.nateldemoura@f5.com nobody_uid = pwd.getpwnam('nobody').pw_uid 351307St.nateldemoura@f5.com 361307St.nateldemoura@f5.com try: 371307St.nateldemoura@f5.com nogroup_gid = grp.getgrnam('nogroup').gr_gid 381307St.nateldemoura@f5.com nogroup = 'nogroup' 39*1706Smax.romanov@nginx.com except KeyError: 401307St.nateldemoura@f5.com nogroup_gid = grp.getgrnam('nobody').gr_gid 411307St.nateldemoura@f5.com nogroup = 'nobody' 421307St.nateldemoura@f5.com 431307St.nateldemoura@f5.com return (nobody_uid, nogroup_gid, nogroup) 441307St.nateldemoura@f5.com 451182St.nateldemoura@f5.com def isolation_key(self, key): 461654Szelenkov@nginx.com return key in option.available['features']['isolation'].keys() 471182St.nateldemoura@f5.com 481182St.nateldemoura@f5.com def test_isolation_values(self): 491182St.nateldemoura@f5.com self.load('ns_inspect') 501182St.nateldemoura@f5.com 511296St.nateldemoura@f5.com obj = self.getjson()['body'] 521182St.nateldemoura@f5.com 531654Szelenkov@nginx.com for ns, ns_value in option.available['features']['isolation'].items(): 541182St.nateldemoura@f5.com if ns.upper() in obj['NS']: 551596Szelenkov@nginx.com assert obj['NS'][ns.upper()] == ns_value, '%s match' % ns 561182St.nateldemoura@f5.com 571596Szelenkov@nginx.com def test_isolation_unpriv_user(self, is_su): 581182St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 591596Szelenkov@nginx.com pytest.skip('unprivileged clone is not available') 601182St.nateldemoura@f5.com 611596Szelenkov@nginx.com if is_su: 621596Szelenkov@nginx.com pytest.skip('privileged tests, skip this') 631307St.nateldemoura@f5.com 641307St.nateldemoura@f5.com self.load('ns_inspect') 651307St.nateldemoura@f5.com obj = self.getjson()['body'] 661307St.nateldemoura@f5.com 671596Szelenkov@nginx.com assert obj['UID'] == os.geteuid(), 'uid match' 681596Szelenkov@nginx.com assert obj['GID'] == os.getegid(), 'gid match' 691307St.nateldemoura@f5.com 701307St.nateldemoura@f5.com self.load('ns_inspect', isolation={'namespaces': {'credential': True}}) 711307St.nateldemoura@f5.com 721307St.nateldemoura@f5.com obj = self.getjson()['body'] 731307St.nateldemoura@f5.com 741307St.nateldemoura@f5.com nobody_uid, nogroup_gid, nogroup = self.unpriv_creds() 751307St.nateldemoura@f5.com 761307St.nateldemoura@f5.com # unprivileged unit map itself to nobody in the container by default 771596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'uid of nobody' 781596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid of %s' % nogroup 791307St.nateldemoura@f5.com 801307St.nateldemoura@f5.com self.load( 811307St.nateldemoura@f5.com 'ns_inspect', 821307St.nateldemoura@f5.com user='root', 831307St.nateldemoura@f5.com isolation={'namespaces': {'credential': True}}, 841307St.nateldemoura@f5.com ) 851307St.nateldemoura@f5.com 861307St.nateldemoura@f5.com obj = self.getjson()['body'] 871307St.nateldemoura@f5.com 881596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match user=root' 891596Szelenkov@nginx.com assert obj['GID'] == 0, 'gid match user=root' 901307St.nateldemoura@f5.com 911307St.nateldemoura@f5.com self.load( 921307St.nateldemoura@f5.com 'ns_inspect', 931307St.nateldemoura@f5.com user='root', 941307St.nateldemoura@f5.com group=nogroup, 951307St.nateldemoura@f5.com isolation={'namespaces': {'credential': True}}, 961307St.nateldemoura@f5.com ) 971307St.nateldemoura@f5.com 981307St.nateldemoura@f5.com obj = self.getjson()['body'] 991307St.nateldemoura@f5.com 1001596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match user=root group=nogroup' 1011596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid match user=root group=nogroup' 1021307St.nateldemoura@f5.com 1031307St.nateldemoura@f5.com self.load( 1041307St.nateldemoura@f5.com 'ns_inspect', 1051307St.nateldemoura@f5.com user='root', 1061307St.nateldemoura@f5.com group='root', 1071307St.nateldemoura@f5.com isolation={ 1081307St.nateldemoura@f5.com 'namespaces': {'credential': True}, 1091596Szelenkov@nginx.com 'uidmap': [{'container': 0, 'host': os.geteuid(), 'size': 1}], 1101596Szelenkov@nginx.com 'gidmap': [{'container': 0, 'host': os.getegid(), 'size': 1}], 1111307St.nateldemoura@f5.com }, 1121307St.nateldemoura@f5.com ) 1131307St.nateldemoura@f5.com 1141307St.nateldemoura@f5.com obj = self.getjson()['body'] 1151307St.nateldemoura@f5.com 1161596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match uidmap' 1171596Szelenkov@nginx.com assert obj['GID'] == 0, 'gid match gidmap' 1181307St.nateldemoura@f5.com 1191596Szelenkov@nginx.com def test_isolation_priv_user(self, is_su): 1201596Szelenkov@nginx.com if not is_su: 1211596Szelenkov@nginx.com pytest.skip('unprivileged tests, skip this') 1221307St.nateldemoura@f5.com 1231182St.nateldemoura@f5.com self.load('ns_inspect') 1241293St.nateldemoura@f5.com 1251307St.nateldemoura@f5.com nobody_uid, nogroup_gid, nogroup = self.unpriv_creds() 1261307St.nateldemoura@f5.com 1271307St.nateldemoura@f5.com obj = self.getjson()['body'] 1281293St.nateldemoura@f5.com 1291596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'uid match' 1301596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid match' 1311307St.nateldemoura@f5.com 1321307St.nateldemoura@f5.com self.load('ns_inspect', isolation={'namespaces': {'credential': True}}) 1331293St.nateldemoura@f5.com 1341296St.nateldemoura@f5.com obj = self.getjson()['body'] 1351182St.nateldemoura@f5.com 1361307St.nateldemoura@f5.com # privileged unit map app creds in the container by default 1371596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'uid nobody' 1381596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid nobody' 1391293St.nateldemoura@f5.com 1401307St.nateldemoura@f5.com self.load( 1411307St.nateldemoura@f5.com 'ns_inspect', 1421307St.nateldemoura@f5.com user='root', 1431307St.nateldemoura@f5.com isolation={'namespaces': {'credential': True}}, 1441307St.nateldemoura@f5.com ) 1451182St.nateldemoura@f5.com 1461296St.nateldemoura@f5.com obj = self.getjson()['body'] 1471182St.nateldemoura@f5.com 1481596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid nobody user=root' 1491596Szelenkov@nginx.com assert obj['GID'] == 0, 'gid nobody user=root' 1501182St.nateldemoura@f5.com 1511307St.nateldemoura@f5.com self.load( 1521307St.nateldemoura@f5.com 'ns_inspect', 1531307St.nateldemoura@f5.com user='root', 1541307St.nateldemoura@f5.com group=nogroup, 1551307St.nateldemoura@f5.com isolation={'namespaces': {'credential': True}}, 1561182St.nateldemoura@f5.com ) 1571182St.nateldemoura@f5.com 1581296St.nateldemoura@f5.com obj = self.getjson()['body'] 1591182St.nateldemoura@f5.com 1601596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match user=root group=nogroup' 1611596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid match user=root group=nogroup' 1621307St.nateldemoura@f5.com 1631307St.nateldemoura@f5.com self.load( 1641307St.nateldemoura@f5.com 'ns_inspect', 1651307St.nateldemoura@f5.com user='root', 1661307St.nateldemoura@f5.com group='root', 1671307St.nateldemoura@f5.com isolation={ 1681307St.nateldemoura@f5.com 'namespaces': {'credential': True}, 1691307St.nateldemoura@f5.com 'uidmap': [{'container': 0, 'host': 0, 'size': 1}], 1701307St.nateldemoura@f5.com 'gidmap': [{'container': 0, 'host': 0, 'size': 1}], 1711307St.nateldemoura@f5.com }, 1721307St.nateldemoura@f5.com ) 1731307St.nateldemoura@f5.com 1741307St.nateldemoura@f5.com obj = self.getjson()['body'] 1751307St.nateldemoura@f5.com 1761596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match uidmap user=root' 1771596Szelenkov@nginx.com assert obj['GID'] == 0, 'gid match gidmap user=root' 1781307St.nateldemoura@f5.com 1791307St.nateldemoura@f5.com # map 65535 uids 1801307St.nateldemoura@f5.com self.load( 1811307St.nateldemoura@f5.com 'ns_inspect', 1821307St.nateldemoura@f5.com user='nobody', 1831307St.nateldemoura@f5.com isolation={ 1841307St.nateldemoura@f5.com 'namespaces': {'credential': True}, 1851307St.nateldemoura@f5.com 'uidmap': [ 1861307St.nateldemoura@f5.com {'container': 0, 'host': 0, 'size': nobody_uid + 1} 1871307St.nateldemoura@f5.com ], 1881307St.nateldemoura@f5.com }, 1891307St.nateldemoura@f5.com ) 1901307St.nateldemoura@f5.com 1911307St.nateldemoura@f5.com obj = self.getjson()['body'] 1921307St.nateldemoura@f5.com 1931596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'uid match uidmap user=nobody' 1941596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid match uidmap user=nobody' 1951182St.nateldemoura@f5.com 1961182St.nateldemoura@f5.com def test_isolation_mnt(self): 1971182St.nateldemoura@f5.com if not self.isolation_key('mnt'): 1981596Szelenkov@nginx.com pytest.skip('mnt namespace is not supported') 1991182St.nateldemoura@f5.com 2001182St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 2011596Szelenkov@nginx.com pytest.skip('unprivileged clone is not available') 2021182St.nateldemoura@f5.com 2031307St.nateldemoura@f5.com self.load( 2041307St.nateldemoura@f5.com 'ns_inspect', 2051307St.nateldemoura@f5.com isolation={'namespaces': {'mount': True, 'credential': True}}, 2061182St.nateldemoura@f5.com ) 2071182St.nateldemoura@f5.com 2081296St.nateldemoura@f5.com obj = self.getjson()['body'] 2091182St.nateldemoura@f5.com 2101182St.nateldemoura@f5.com # all but user and mnt 2111654Szelenkov@nginx.com allns = list(option.available['features']['isolation'].keys()) 2121182St.nateldemoura@f5.com allns.remove('user') 2131182St.nateldemoura@f5.com allns.remove('mnt') 2141182St.nateldemoura@f5.com 2151182St.nateldemoura@f5.com for ns in allns: 2161182St.nateldemoura@f5.com if ns.upper() in obj['NS']: 2171596Szelenkov@nginx.com assert ( 2181596Szelenkov@nginx.com obj['NS'][ns.upper()] 2191654Szelenkov@nginx.com == option.available['features']['isolation'][ns] 2201596Szelenkov@nginx.com ), ('%s match' % ns) 2211182St.nateldemoura@f5.com 2221596Szelenkov@nginx.com assert obj['NS']['MNT'] != self.isolation.getns('mnt'), 'mnt set' 2231596Szelenkov@nginx.com assert obj['NS']['USER'] != self.isolation.getns('user'), 'user set' 2241182St.nateldemoura@f5.com 2251596Szelenkov@nginx.com def test_isolation_pid(self, is_su): 2261182St.nateldemoura@f5.com if not self.isolation_key('pid'): 2271596Szelenkov@nginx.com pytest.skip('pid namespace is not supported') 2281182St.nateldemoura@f5.com 2291673St.nateldemoura@f5.com if not is_su: 2301673St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 2311673St.nateldemoura@f5.com pytest.skip('unprivileged clone is not available') 2321673St.nateldemoura@f5.com 2331673St.nateldemoura@f5.com if not self.isolation_key('user'): 2341673St.nateldemoura@f5.com pytest.skip('user namespace is not supported') 2351182St.nateldemoura@f5.com 2361673St.nateldemoura@f5.com if not self.isolation_key('mnt'): 2371673St.nateldemoura@f5.com pytest.skip('mnt namespace is not supported') 2381673St.nateldemoura@f5.com 2391673St.nateldemoura@f5.com isolation = {'namespaces': {'pid': True}} 2401673St.nateldemoura@f5.com 2411673St.nateldemoura@f5.com if not is_su: 2421673St.nateldemoura@f5.com isolation['namespaces']['mount'] = True 2431673St.nateldemoura@f5.com isolation['namespaces']['credential'] = True 2441673St.nateldemoura@f5.com 2451673St.nateldemoura@f5.com self.load('ns_inspect', isolation=isolation) 2461182St.nateldemoura@f5.com 2471296St.nateldemoura@f5.com obj = self.getjson()['body'] 2481182St.nateldemoura@f5.com 2491596Szelenkov@nginx.com assert obj['PID'] == 1, 'pid of container is 1' 2501182St.nateldemoura@f5.com 2511236St.nateldemoura@f5.com def test_isolation_namespace_false(self): 2521236St.nateldemoura@f5.com self.load('ns_inspect') 2531654Szelenkov@nginx.com allns = list(option.available['features']['isolation'].keys()) 2541236St.nateldemoura@f5.com 2551236St.nateldemoura@f5.com remove_list = ['unprivileged_userns_clone', 'ipc', 'cgroup'] 2561236St.nateldemoura@f5.com allns = [ns for ns in allns if ns not in remove_list] 2571236St.nateldemoura@f5.com 2581236St.nateldemoura@f5.com namespaces = {} 2591236St.nateldemoura@f5.com for ns in allns: 2601236St.nateldemoura@f5.com if ns == 'user': 2611236St.nateldemoura@f5.com namespaces['credential'] = False 2621236St.nateldemoura@f5.com elif ns == 'mnt': 2631236St.nateldemoura@f5.com namespaces['mount'] = False 2641236St.nateldemoura@f5.com elif ns == 'net': 2651236St.nateldemoura@f5.com namespaces['network'] = False 2661236St.nateldemoura@f5.com elif ns == 'uts': 2671236St.nateldemoura@f5.com namespaces['uname'] = False 2681236St.nateldemoura@f5.com else: 2691236St.nateldemoura@f5.com namespaces[ns] = False 2701236St.nateldemoura@f5.com 2711307St.nateldemoura@f5.com self.load('ns_inspect', isolation={'namespaces': namespaces}) 2721236St.nateldemoura@f5.com 2731296St.nateldemoura@f5.com obj = self.getjson()['body'] 2741236St.nateldemoura@f5.com 2751236St.nateldemoura@f5.com for ns in allns: 2761236St.nateldemoura@f5.com if ns.upper() in obj['NS']: 2771596Szelenkov@nginx.com assert ( 2781596Szelenkov@nginx.com obj['NS'][ns.upper()] 2791654Szelenkov@nginx.com == option.available['features']['isolation'][ns] 2801596Szelenkov@nginx.com ), ('%s match' % ns) 2811236St.nateldemoura@f5.com 2821673St.nateldemoura@f5.com def test_go_isolation_rootfs_container(self, is_su, temp_dir): 2831673St.nateldemoura@f5.com if not is_su: 2841673St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 2851673St.nateldemoura@f5.com pytest.skip('unprivileged clone is not available') 2861673St.nateldemoura@f5.com 2871673St.nateldemoura@f5.com if not self.isolation_key('user'): 2881673St.nateldemoura@f5.com pytest.skip('user namespace is not supported') 2891673St.nateldemoura@f5.com 2901673St.nateldemoura@f5.com if not self.isolation_key('mnt'): 2911673St.nateldemoura@f5.com pytest.skip('mnt namespace is not supported') 2921490St.nateldemoura@f5.com 2931673St.nateldemoura@f5.com if not self.isolation_key('pid'): 2941673St.nateldemoura@f5.com pytest.skip('pid namespace is not supported') 2951673St.nateldemoura@f5.com 2961673St.nateldemoura@f5.com isolation = {'rootfs': temp_dir} 2971490St.nateldemoura@f5.com 2981673St.nateldemoura@f5.com if not is_su: 2991673St.nateldemoura@f5.com isolation['namespaces'] = { 3001673St.nateldemoura@f5.com 'mount': True, 3011673St.nateldemoura@f5.com 'credential': True, 3021673St.nateldemoura@f5.com 'pid': True 3031673St.nateldemoura@f5.com } 3041490St.nateldemoura@f5.com 3051490St.nateldemoura@f5.com self.load('ns_inspect', isolation=isolation) 3061490St.nateldemoura@f5.com 3071490St.nateldemoura@f5.com obj = self.getjson(url='/?file=/go/app')['body'] 3081490St.nateldemoura@f5.com 3091596Szelenkov@nginx.com assert obj['FileExists'] == True, 'app relative to rootfs' 3101490St.nateldemoura@f5.com 3111490St.nateldemoura@f5.com obj = self.getjson(url='/?file=/bin/sh')['body'] 3121596Szelenkov@nginx.com assert obj['FileExists'] == False, 'file should not exists' 3131490St.nateldemoura@f5.com 3141654Szelenkov@nginx.com def test_go_isolation_rootfs_container_priv(self, is_su, temp_dir): 3151596Szelenkov@nginx.com if not is_su: 3161596Szelenkov@nginx.com pytest.skip('requires root') 3171490St.nateldemoura@f5.com 3181490St.nateldemoura@f5.com if not self.isolation_key('mnt'): 3191596Szelenkov@nginx.com pytest.skip('mnt namespace is not supported') 3201490St.nateldemoura@f5.com 3211490St.nateldemoura@f5.com isolation = { 3221490St.nateldemoura@f5.com 'namespaces': {'mount': True}, 3231654Szelenkov@nginx.com 'rootfs': temp_dir, 3241490St.nateldemoura@f5.com } 3251490St.nateldemoura@f5.com 3261490St.nateldemoura@f5.com self.load('ns_inspect', isolation=isolation) 3271490St.nateldemoura@f5.com 3281490St.nateldemoura@f5.com obj = self.getjson(url='/?file=/go/app')['body'] 3291490St.nateldemoura@f5.com 3301596Szelenkov@nginx.com assert obj['FileExists'] == True, 'app relative to rootfs' 3311490St.nateldemoura@f5.com 3321490St.nateldemoura@f5.com obj = self.getjson(url='/?file=/bin/sh')['body'] 3331596Szelenkov@nginx.com assert obj['FileExists'] == False, 'file should not exists' 3341490St.nateldemoura@f5.com 3351673St.nateldemoura@f5.com def test_go_isolation_rootfs_default_tmpfs(self, is_su, temp_dir): 3361673St.nateldemoura@f5.com if not is_su: 3371673St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 3381673St.nateldemoura@f5.com pytest.skip('unprivileged clone is not available') 3391673St.nateldemoura@f5.com 3401673St.nateldemoura@f5.com if not self.isolation_key('user'): 3411673St.nateldemoura@f5.com pytest.skip('user namespace is not supported') 3421673St.nateldemoura@f5.com 3431673St.nateldemoura@f5.com if not self.isolation_key('mnt'): 3441673St.nateldemoura@f5.com pytest.skip('mnt namespace is not supported') 3451581St.nateldemoura@f5.com 3461673St.nateldemoura@f5.com if not self.isolation_key('pid'): 3471673St.nateldemoura@f5.com pytest.skip('pid namespace is not supported') 3481673St.nateldemoura@f5.com 3491673St.nateldemoura@f5.com isolation = {'rootfs': temp_dir} 3501581St.nateldemoura@f5.com 3511673St.nateldemoura@f5.com if not is_su: 3521673St.nateldemoura@f5.com isolation['namespaces'] = { 3531673St.nateldemoura@f5.com 'mount': True, 3541673St.nateldemoura@f5.com 'credential': True, 3551673St.nateldemoura@f5.com 'pid': True 3561673St.nateldemoura@f5.com } 3571581St.nateldemoura@f5.com 3581581St.nateldemoura@f5.com self.load('ns_inspect', isolation=isolation) 3591581St.nateldemoura@f5.com 3601581St.nateldemoura@f5.com obj = self.getjson(url='/?file=/tmp')['body'] 3611581St.nateldemoura@f5.com 3621596Szelenkov@nginx.com assert obj['FileExists'] == True, 'app has /tmp' 363