xref: /unit/test/test_go_isolation.py (revision 2073:bc6ad31ce286)
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