11477Szelenkov@nginx.comimport grp 21596Szelenkov@nginx.comimport os 31293St.nateldemoura@f5.comimport pwd 41635Szelenkov@nginx.com 51596Szelenkov@nginx.comimport pytest 61182St.nateldemoura@f5.comfrom unit.applications.lang.go import TestApplicationGo 71730Szelenkov@nginx.comfrom unit.option import option 81740Szelenkov@nginx.comfrom unit.utils import getns 91182St.nateldemoura@f5.com 101848Szelenkov@nginx.com 111182St.nateldemoura@f5.comclass TestGoIsolation(TestApplicationGo): 121467Szelenkov@nginx.com prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']} 131182St.nateldemoura@f5.com 141999Smax.romanov@nginx.com @pytest.fixture(autouse=True) 151999Smax.romanov@nginx.com def setup_method_fixture(self, request, skip_alert): 161999Smax.romanov@nginx.com skip_alert(r'\[unit\] close\(\d+\) failed: Bad file descriptor') 171999Smax.romanov@nginx.com 181307St.nateldemoura@f5.com def unpriv_creds(self): 191307St.nateldemoura@f5.com nobody_uid = pwd.getpwnam('nobody').pw_uid 201307St.nateldemoura@f5.com 211307St.nateldemoura@f5.com try: 221307St.nateldemoura@f5.com nogroup_gid = grp.getgrnam('nogroup').gr_gid 231307St.nateldemoura@f5.com nogroup = 'nogroup' 241706Smax.romanov@nginx.com except KeyError: 251307St.nateldemoura@f5.com nogroup_gid = grp.getgrnam('nobody').gr_gid 261307St.nateldemoura@f5.com nogroup = 'nobody' 271307St.nateldemoura@f5.com 281307St.nateldemoura@f5.com return (nobody_uid, nogroup_gid, nogroup) 291307St.nateldemoura@f5.com 301182St.nateldemoura@f5.com def isolation_key(self, key): 311654Szelenkov@nginx.com return key in option.available['features']['isolation'].keys() 321182St.nateldemoura@f5.com 331182St.nateldemoura@f5.com def test_isolation_values(self): 341182St.nateldemoura@f5.com self.load('ns_inspect') 351182St.nateldemoura@f5.com 361296St.nateldemoura@f5.com obj = self.getjson()['body'] 371182St.nateldemoura@f5.com 381654Szelenkov@nginx.com for ns, ns_value in option.available['features']['isolation'].items(): 391182St.nateldemoura@f5.com if ns.upper() in obj['NS']: 401596Szelenkov@nginx.com assert obj['NS'][ns.upper()] == ns_value, '%s match' % ns 411182St.nateldemoura@f5.com 421596Szelenkov@nginx.com def test_isolation_unpriv_user(self, is_su): 431182St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 441596Szelenkov@nginx.com pytest.skip('unprivileged clone is not available') 451182St.nateldemoura@f5.com 461596Szelenkov@nginx.com if is_su: 471596Szelenkov@nginx.com pytest.skip('privileged tests, skip this') 481307St.nateldemoura@f5.com 491307St.nateldemoura@f5.com self.load('ns_inspect') 501307St.nateldemoura@f5.com obj = self.getjson()['body'] 511307St.nateldemoura@f5.com 521596Szelenkov@nginx.com assert obj['UID'] == os.geteuid(), 'uid match' 531596Szelenkov@nginx.com assert obj['GID'] == os.getegid(), 'gid match' 541307St.nateldemoura@f5.com 551307St.nateldemoura@f5.com self.load('ns_inspect', isolation={'namespaces': {'credential': True}}) 561307St.nateldemoura@f5.com 571307St.nateldemoura@f5.com obj = self.getjson()['body'] 581307St.nateldemoura@f5.com 591307St.nateldemoura@f5.com nobody_uid, nogroup_gid, nogroup = self.unpriv_creds() 601307St.nateldemoura@f5.com 611307St.nateldemoura@f5.com # unprivileged unit map itself to nobody in the container by default 621596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'uid of nobody' 631596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid of %s' % nogroup 641307St.nateldemoura@f5.com 651307St.nateldemoura@f5.com self.load( 661307St.nateldemoura@f5.com 'ns_inspect', 671307St.nateldemoura@f5.com user='root', 681307St.nateldemoura@f5.com isolation={'namespaces': {'credential': True}}, 691307St.nateldemoura@f5.com ) 701307St.nateldemoura@f5.com 711307St.nateldemoura@f5.com obj = self.getjson()['body'] 721307St.nateldemoura@f5.com 731596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match user=root' 741596Szelenkov@nginx.com assert obj['GID'] == 0, 'gid match user=root' 751307St.nateldemoura@f5.com 761307St.nateldemoura@f5.com self.load( 771307St.nateldemoura@f5.com 'ns_inspect', 781307St.nateldemoura@f5.com user='root', 791307St.nateldemoura@f5.com group=nogroup, 801307St.nateldemoura@f5.com isolation={'namespaces': {'credential': True}}, 811307St.nateldemoura@f5.com ) 821307St.nateldemoura@f5.com 831307St.nateldemoura@f5.com obj = self.getjson()['body'] 841307St.nateldemoura@f5.com 851596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match user=root group=nogroup' 861596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid match user=root group=nogroup' 871307St.nateldemoura@f5.com 881307St.nateldemoura@f5.com self.load( 891307St.nateldemoura@f5.com 'ns_inspect', 901307St.nateldemoura@f5.com user='root', 911307St.nateldemoura@f5.com group='root', 921307St.nateldemoura@f5.com isolation={ 931307St.nateldemoura@f5.com 'namespaces': {'credential': True}, 941596Szelenkov@nginx.com 'uidmap': [{'container': 0, 'host': os.geteuid(), 'size': 1}], 951596Szelenkov@nginx.com 'gidmap': [{'container': 0, 'host': os.getegid(), 'size': 1}], 961307St.nateldemoura@f5.com }, 971307St.nateldemoura@f5.com ) 981307St.nateldemoura@f5.com 991307St.nateldemoura@f5.com obj = self.getjson()['body'] 1001307St.nateldemoura@f5.com 1011596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match uidmap' 1021596Szelenkov@nginx.com assert obj['GID'] == 0, 'gid match gidmap' 1031307St.nateldemoura@f5.com 1041596Szelenkov@nginx.com def test_isolation_priv_user(self, is_su): 1051596Szelenkov@nginx.com if not is_su: 1061596Szelenkov@nginx.com pytest.skip('unprivileged tests, skip this') 1071307St.nateldemoura@f5.com 1081182St.nateldemoura@f5.com self.load('ns_inspect') 1091293St.nateldemoura@f5.com 1101307St.nateldemoura@f5.com nobody_uid, nogroup_gid, nogroup = self.unpriv_creds() 1111307St.nateldemoura@f5.com 1121307St.nateldemoura@f5.com obj = self.getjson()['body'] 1131293St.nateldemoura@f5.com 1141596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'uid match' 1151596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid match' 1161307St.nateldemoura@f5.com 1171307St.nateldemoura@f5.com self.load('ns_inspect', isolation={'namespaces': {'credential': True}}) 1181293St.nateldemoura@f5.com 1191296St.nateldemoura@f5.com obj = self.getjson()['body'] 1201182St.nateldemoura@f5.com 1211307St.nateldemoura@f5.com # privileged unit map app creds in the container by default 1221596Szelenkov@nginx.com assert obj['UID'] == nobody_uid, 'uid nobody' 1231596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid nobody' 1241293St.nateldemoura@f5.com 1251307St.nateldemoura@f5.com self.load( 1261307St.nateldemoura@f5.com 'ns_inspect', 1271307St.nateldemoura@f5.com user='root', 1281307St.nateldemoura@f5.com isolation={'namespaces': {'credential': True}}, 1291307St.nateldemoura@f5.com ) 1301182St.nateldemoura@f5.com 1311296St.nateldemoura@f5.com obj = self.getjson()['body'] 1321182St.nateldemoura@f5.com 1331596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid nobody user=root' 1341596Szelenkov@nginx.com assert obj['GID'] == 0, 'gid nobody user=root' 1351182St.nateldemoura@f5.com 1361307St.nateldemoura@f5.com self.load( 1371307St.nateldemoura@f5.com 'ns_inspect', 1381307St.nateldemoura@f5.com user='root', 1391307St.nateldemoura@f5.com group=nogroup, 1401307St.nateldemoura@f5.com isolation={'namespaces': {'credential': True}}, 1411182St.nateldemoura@f5.com ) 1421182St.nateldemoura@f5.com 1431296St.nateldemoura@f5.com obj = self.getjson()['body'] 1441182St.nateldemoura@f5.com 1451596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match user=root group=nogroup' 1461596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid match user=root group=nogroup' 1471307St.nateldemoura@f5.com 1481307St.nateldemoura@f5.com self.load( 1491307St.nateldemoura@f5.com 'ns_inspect', 1501307St.nateldemoura@f5.com user='root', 1511307St.nateldemoura@f5.com group='root', 1521307St.nateldemoura@f5.com isolation={ 1531307St.nateldemoura@f5.com 'namespaces': {'credential': True}, 1541307St.nateldemoura@f5.com 'uidmap': [{'container': 0, 'host': 0, 'size': 1}], 1551307St.nateldemoura@f5.com 'gidmap': [{'container': 0, 'host': 0, 'size': 1}], 1561307St.nateldemoura@f5.com }, 1571307St.nateldemoura@f5.com ) 1581307St.nateldemoura@f5.com 1591307St.nateldemoura@f5.com obj = self.getjson()['body'] 1601307St.nateldemoura@f5.com 1611596Szelenkov@nginx.com assert obj['UID'] == 0, 'uid match uidmap user=root' 1621596Szelenkov@nginx.com assert obj['GID'] == 0, 'gid match gidmap user=root' 1631307St.nateldemoura@f5.com 1641307St.nateldemoura@f5.com # map 65535 uids 1651307St.nateldemoura@f5.com self.load( 1661307St.nateldemoura@f5.com 'ns_inspect', 1671307St.nateldemoura@f5.com user='nobody', 1681307St.nateldemoura@f5.com isolation={ 1691307St.nateldemoura@f5.com 'namespaces': {'credential': True}, 170*2073Szelenkov@nginx.com 'uidmap': [{'container': 0, 'host': 0, 'size': nobody_uid + 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'] == nobody_uid, 'uid match uidmap user=nobody' 1771596Szelenkov@nginx.com assert obj['GID'] == nogroup_gid, 'gid match uidmap user=nobody' 1781182St.nateldemoura@f5.com 1791182St.nateldemoura@f5.com def test_isolation_mnt(self): 1801182St.nateldemoura@f5.com if not self.isolation_key('mnt'): 1811596Szelenkov@nginx.com pytest.skip('mnt namespace is not supported') 1821182St.nateldemoura@f5.com 1831182St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 1841596Szelenkov@nginx.com pytest.skip('unprivileged clone is not available') 1851182St.nateldemoura@f5.com 1861307St.nateldemoura@f5.com self.load( 1871307St.nateldemoura@f5.com 'ns_inspect', 1881307St.nateldemoura@f5.com isolation={'namespaces': {'mount': True, 'credential': True}}, 1891182St.nateldemoura@f5.com ) 1901182St.nateldemoura@f5.com 1911296St.nateldemoura@f5.com obj = self.getjson()['body'] 1921182St.nateldemoura@f5.com 1931182St.nateldemoura@f5.com # all but user and mnt 1941654Szelenkov@nginx.com allns = list(option.available['features']['isolation'].keys()) 1951182St.nateldemoura@f5.com allns.remove('user') 1961182St.nateldemoura@f5.com allns.remove('mnt') 1971182St.nateldemoura@f5.com 1981182St.nateldemoura@f5.com for ns in allns: 1991182St.nateldemoura@f5.com if ns.upper() in obj['NS']: 2001596Szelenkov@nginx.com assert ( 2011596Szelenkov@nginx.com obj['NS'][ns.upper()] 2021654Szelenkov@nginx.com == option.available['features']['isolation'][ns] 2031596Szelenkov@nginx.com ), ('%s match' % ns) 2041182St.nateldemoura@f5.com 2051740Szelenkov@nginx.com assert obj['NS']['MNT'] != getns('mnt'), 'mnt set' 2061740Szelenkov@nginx.com assert obj['NS']['USER'] != getns('user'), 'user set' 2071182St.nateldemoura@f5.com 2081596Szelenkov@nginx.com def test_isolation_pid(self, is_su): 2091182St.nateldemoura@f5.com if not self.isolation_key('pid'): 2101596Szelenkov@nginx.com pytest.skip('pid namespace is not supported') 2111182St.nateldemoura@f5.com 2121673St.nateldemoura@f5.com if not is_su: 2131673St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 2141673St.nateldemoura@f5.com pytest.skip('unprivileged clone is not available') 2151673St.nateldemoura@f5.com 2161673St.nateldemoura@f5.com if not self.isolation_key('user'): 2171673St.nateldemoura@f5.com pytest.skip('user namespace is not supported') 2181182St.nateldemoura@f5.com 2191673St.nateldemoura@f5.com if not self.isolation_key('mnt'): 2201673St.nateldemoura@f5.com pytest.skip('mnt namespace is not supported') 2211673St.nateldemoura@f5.com 2221673St.nateldemoura@f5.com isolation = {'namespaces': {'pid': True}} 2231673St.nateldemoura@f5.com 2241673St.nateldemoura@f5.com if not is_su: 2251673St.nateldemoura@f5.com isolation['namespaces']['mount'] = True 2261673St.nateldemoura@f5.com isolation['namespaces']['credential'] = True 2271673St.nateldemoura@f5.com 2281673St.nateldemoura@f5.com self.load('ns_inspect', isolation=isolation) 2291182St.nateldemoura@f5.com 2301296St.nateldemoura@f5.com obj = self.getjson()['body'] 2311182St.nateldemoura@f5.com 2321999Smax.romanov@nginx.com assert obj['PID'] == 2, 'pid of container is 2' 2331182St.nateldemoura@f5.com 2341236St.nateldemoura@f5.com def test_isolation_namespace_false(self): 2351236St.nateldemoura@f5.com self.load('ns_inspect') 2361654Szelenkov@nginx.com allns = list(option.available['features']['isolation'].keys()) 2371236St.nateldemoura@f5.com 2381236St.nateldemoura@f5.com remove_list = ['unprivileged_userns_clone', 'ipc', 'cgroup'] 2391236St.nateldemoura@f5.com allns = [ns for ns in allns if ns not in remove_list] 2401236St.nateldemoura@f5.com 2411236St.nateldemoura@f5.com namespaces = {} 2421236St.nateldemoura@f5.com for ns in allns: 2431236St.nateldemoura@f5.com if ns == 'user': 2441236St.nateldemoura@f5.com namespaces['credential'] = False 2451236St.nateldemoura@f5.com elif ns == 'mnt': 2461236St.nateldemoura@f5.com namespaces['mount'] = False 2471236St.nateldemoura@f5.com elif ns == 'net': 2481236St.nateldemoura@f5.com namespaces['network'] = False 2491236St.nateldemoura@f5.com elif ns == 'uts': 2501236St.nateldemoura@f5.com namespaces['uname'] = False 2511236St.nateldemoura@f5.com else: 2521236St.nateldemoura@f5.com namespaces[ns] = False 2531236St.nateldemoura@f5.com 2541307St.nateldemoura@f5.com self.load('ns_inspect', isolation={'namespaces': namespaces}) 2551236St.nateldemoura@f5.com 2561296St.nateldemoura@f5.com obj = self.getjson()['body'] 2571236St.nateldemoura@f5.com 2581236St.nateldemoura@f5.com for ns in allns: 2591236St.nateldemoura@f5.com if ns.upper() in obj['NS']: 2601596Szelenkov@nginx.com assert ( 2611596Szelenkov@nginx.com obj['NS'][ns.upper()] 2621654Szelenkov@nginx.com == option.available['features']['isolation'][ns] 2631596Szelenkov@nginx.com ), ('%s match' % ns) 2641236St.nateldemoura@f5.com 2651673St.nateldemoura@f5.com def test_go_isolation_rootfs_container(self, is_su, temp_dir): 2661673St.nateldemoura@f5.com if not is_su: 2671673St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 2681673St.nateldemoura@f5.com pytest.skip('unprivileged clone is not available') 2691673St.nateldemoura@f5.com 2701673St.nateldemoura@f5.com if not self.isolation_key('user'): 2711673St.nateldemoura@f5.com pytest.skip('user namespace is not supported') 2721673St.nateldemoura@f5.com 2731673St.nateldemoura@f5.com if not self.isolation_key('mnt'): 2741673St.nateldemoura@f5.com pytest.skip('mnt namespace is not supported') 2751490St.nateldemoura@f5.com 2761673St.nateldemoura@f5.com if not self.isolation_key('pid'): 2771673St.nateldemoura@f5.com pytest.skip('pid namespace is not supported') 2781673St.nateldemoura@f5.com 2791673St.nateldemoura@f5.com isolation = {'rootfs': temp_dir} 2801490St.nateldemoura@f5.com 2811673St.nateldemoura@f5.com if not is_su: 2821673St.nateldemoura@f5.com isolation['namespaces'] = { 2831673St.nateldemoura@f5.com 'mount': True, 2841673St.nateldemoura@f5.com 'credential': True, 2851848Szelenkov@nginx.com 'pid': True, 2861673St.nateldemoura@f5.com } 2871490St.nateldemoura@f5.com 2881490St.nateldemoura@f5.com self.load('ns_inspect', isolation=isolation) 2891490St.nateldemoura@f5.com 2901490St.nateldemoura@f5.com obj = self.getjson(url='/?file=/go/app')['body'] 2911490St.nateldemoura@f5.com 2921596Szelenkov@nginx.com assert obj['FileExists'] == True, 'app relative to rootfs' 2931490St.nateldemoura@f5.com 2941490St.nateldemoura@f5.com obj = self.getjson(url='/?file=/bin/sh')['body'] 2951596Szelenkov@nginx.com assert obj['FileExists'] == False, 'file should not exists' 2961490St.nateldemoura@f5.com 2971654Szelenkov@nginx.com def test_go_isolation_rootfs_container_priv(self, is_su, temp_dir): 2981596Szelenkov@nginx.com if not is_su: 2991596Szelenkov@nginx.com pytest.skip('requires root') 3001490St.nateldemoura@f5.com 3011490St.nateldemoura@f5.com if not self.isolation_key('mnt'): 3021596Szelenkov@nginx.com pytest.skip('mnt namespace is not supported') 3031490St.nateldemoura@f5.com 3041490St.nateldemoura@f5.com isolation = { 3051490St.nateldemoura@f5.com 'namespaces': {'mount': True}, 3061654Szelenkov@nginx.com 'rootfs': temp_dir, 3071490St.nateldemoura@f5.com } 3081490St.nateldemoura@f5.com 3091490St.nateldemoura@f5.com self.load('ns_inspect', isolation=isolation) 3101490St.nateldemoura@f5.com 3111490St.nateldemoura@f5.com obj = self.getjson(url='/?file=/go/app')['body'] 3121490St.nateldemoura@f5.com 3131596Szelenkov@nginx.com assert obj['FileExists'] == True, 'app relative to rootfs' 3141490St.nateldemoura@f5.com 3151490St.nateldemoura@f5.com obj = self.getjson(url='/?file=/bin/sh')['body'] 3161596Szelenkov@nginx.com assert obj['FileExists'] == False, 'file should not exists' 3171490St.nateldemoura@f5.com 3181707St.nateldemoura@f5.com def test_go_isolation_rootfs_automount_tmpfs(self, is_su, temp_dir): 3191707St.nateldemoura@f5.com try: 3201707St.nateldemoura@f5.com open("/proc/self/mountinfo") 3211707St.nateldemoura@f5.com except: 3221707St.nateldemoura@f5.com pytest.skip('The system lacks /proc/self/mountinfo file') 3231707St.nateldemoura@f5.com 3241673St.nateldemoura@f5.com if not is_su: 3251673St.nateldemoura@f5.com if not self.isolation_key('unprivileged_userns_clone'): 3261673St.nateldemoura@f5.com pytest.skip('unprivileged clone is not available') 3271673St.nateldemoura@f5.com 3281673St.nateldemoura@f5.com if not self.isolation_key('user'): 3291673St.nateldemoura@f5.com pytest.skip('user namespace is not supported') 3301673St.nateldemoura@f5.com 3311673St.nateldemoura@f5.com if not self.isolation_key('mnt'): 3321673St.nateldemoura@f5.com pytest.skip('mnt namespace is not supported') 3331581St.nateldemoura@f5.com 3341673St.nateldemoura@f5.com if not self.isolation_key('pid'): 3351673St.nateldemoura@f5.com pytest.skip('pid namespace is not supported') 3361673St.nateldemoura@f5.com 3371673St.nateldemoura@f5.com isolation = {'rootfs': temp_dir} 3381581St.nateldemoura@f5.com 3391673St.nateldemoura@f5.com if not is_su: 3401673St.nateldemoura@f5.com isolation['namespaces'] = { 3411673St.nateldemoura@f5.com 'mount': True, 3421673St.nateldemoura@f5.com 'credential': True, 3431848Szelenkov@nginx.com 'pid': True, 3441673St.nateldemoura@f5.com } 3451581St.nateldemoura@f5.com 3461848Szelenkov@nginx.com isolation['automount'] = {'tmpfs': False} 3471707St.nateldemoura@f5.com 3481707St.nateldemoura@f5.com self.load('ns_inspect', isolation=isolation) 3491707St.nateldemoura@f5.com 3501707St.nateldemoura@f5.com obj = self.getjson(url='/?mounts=true')['body'] 3511707St.nateldemoura@f5.com 3521707St.nateldemoura@f5.com assert ( 3531707St.nateldemoura@f5.com "/ /tmp" not in obj['Mounts'] and "tmpfs" not in obj['Mounts'] 3541707St.nateldemoura@f5.com ), 'app has no /tmp mounted' 3551768St.nateldemoura@f5.com 3561848Szelenkov@nginx.com isolation['automount'] = {'tmpfs': True} 3571768St.nateldemoura@f5.com 3581768St.nateldemoura@f5.com self.load('ns_inspect', isolation=isolation) 3591768St.nateldemoura@f5.com 3601768St.nateldemoura@f5.com obj = self.getjson(url='/?mounts=true')['body'] 3611768St.nateldemoura@f5.com 3621768St.nateldemoura@f5.com assert ( 3631768St.nateldemoura@f5.com "/ /tmp" in obj['Mounts'] and "tmpfs" in obj['Mounts'] 3641768St.nateldemoura@f5.com ), 'app has /tmp mounted on /' 365