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