xref: /unit/test/test_go_isolation.py (revision 1635)
11477Szelenkov@nginx.comimport grp
21596Szelenkov@nginx.comimport os
31293St.nateldemoura@f5.comimport pwd
4*1635Szelenkov@nginx.com
51596Szelenkov@nginx.comimport pytest
61477Szelenkov@nginx.com
71182St.nateldemoura@f5.comfrom unit.applications.lang.go import TestApplicationGo
81182St.nateldemoura@f5.comfrom unit.feature.isolation import TestFeatureIsolation
91182St.nateldemoura@f5.com
101182St.nateldemoura@f5.comclass TestGoIsolation(TestApplicationGo):
111467Szelenkov@nginx.com    prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']}
121182St.nateldemoura@f5.com
131182St.nateldemoura@f5.com    isolation = TestFeatureIsolation()
141182St.nateldemoura@f5.com
151182St.nateldemoura@f5.com    @classmethod
161596Szelenkov@nginx.com    def setup_class(cls, complete_check=True):
171596Szelenkov@nginx.com        unit = super().setup_class(complete_check=False)
181182St.nateldemoura@f5.com
191596Szelenkov@nginx.com        TestFeatureIsolation().check(cls.available, unit.temp_dir)
201182St.nateldemoura@f5.com
211182St.nateldemoura@f5.com        return unit if not complete_check else unit.complete()
221182St.nateldemoura@f5.com
231307St.nateldemoura@f5.com    def unpriv_creds(self):
241307St.nateldemoura@f5.com        nobody_uid = pwd.getpwnam('nobody').pw_uid
251307St.nateldemoura@f5.com
261307St.nateldemoura@f5.com        try:
271307St.nateldemoura@f5.com            nogroup_gid = grp.getgrnam('nogroup').gr_gid
281307St.nateldemoura@f5.com            nogroup = 'nogroup'
291307St.nateldemoura@f5.com        except:
301307St.nateldemoura@f5.com            nogroup_gid = grp.getgrnam('nobody').gr_gid
311307St.nateldemoura@f5.com            nogroup = 'nobody'
321307St.nateldemoura@f5.com
331307St.nateldemoura@f5.com        return (nobody_uid, nogroup_gid, nogroup)
341307St.nateldemoura@f5.com
351182St.nateldemoura@f5.com    def isolation_key(self, key):
361182St.nateldemoura@f5.com        return key in self.available['features']['isolation'].keys()
371182St.nateldemoura@f5.com
381182St.nateldemoura@f5.com    def test_isolation_values(self):
391182St.nateldemoura@f5.com        self.load('ns_inspect')
401182St.nateldemoura@f5.com
411296St.nateldemoura@f5.com        obj = self.getjson()['body']
421182St.nateldemoura@f5.com
431182St.nateldemoura@f5.com        for ns, ns_value in self.available['features']['isolation'].items():
441182St.nateldemoura@f5.com            if ns.upper() in obj['NS']:
451596Szelenkov@nginx.com                assert obj['NS'][ns.upper()] == ns_value, '%s match' % ns
461182St.nateldemoura@f5.com
471596Szelenkov@nginx.com    def test_isolation_unpriv_user(self, is_su):
481182St.nateldemoura@f5.com        if not self.isolation_key('unprivileged_userns_clone'):
491596Szelenkov@nginx.com            pytest.skip('unprivileged clone is not available')
501182St.nateldemoura@f5.com
511596Szelenkov@nginx.com        if is_su:
521596Szelenkov@nginx.com            pytest.skip('privileged tests, skip this')
531307St.nateldemoura@f5.com
541307St.nateldemoura@f5.com        self.load('ns_inspect')
551307St.nateldemoura@f5.com        obj = self.getjson()['body']
561307St.nateldemoura@f5.com
571596Szelenkov@nginx.com        assert obj['UID'] == os.geteuid(), 'uid match'
581596Szelenkov@nginx.com        assert obj['GID'] == os.getegid(), 'gid match'
591307St.nateldemoura@f5.com
601307St.nateldemoura@f5.com        self.load('ns_inspect', isolation={'namespaces': {'credential': True}})
611307St.nateldemoura@f5.com
621307St.nateldemoura@f5.com        obj = self.getjson()['body']
631307St.nateldemoura@f5.com
641307St.nateldemoura@f5.com        nobody_uid, nogroup_gid, nogroup = self.unpriv_creds()
651307St.nateldemoura@f5.com
661307St.nateldemoura@f5.com        # unprivileged unit map itself to nobody in the container by default
671596Szelenkov@nginx.com        assert obj['UID'] == nobody_uid, 'uid of nobody'
681596Szelenkov@nginx.com        assert obj['GID'] == nogroup_gid, 'gid of %s' % nogroup
691307St.nateldemoura@f5.com
701307St.nateldemoura@f5.com        self.load(
711307St.nateldemoura@f5.com            'ns_inspect',
721307St.nateldemoura@f5.com            user='root',
731307St.nateldemoura@f5.com            isolation={'namespaces': {'credential': True}},
741307St.nateldemoura@f5.com        )
751307St.nateldemoura@f5.com
761307St.nateldemoura@f5.com        obj = self.getjson()['body']
771307St.nateldemoura@f5.com
781596Szelenkov@nginx.com        assert obj['UID'] == 0, 'uid match user=root'
791596Szelenkov@nginx.com        assert obj['GID'] == 0, 'gid match user=root'
801307St.nateldemoura@f5.com
811307St.nateldemoura@f5.com        self.load(
821307St.nateldemoura@f5.com            'ns_inspect',
831307St.nateldemoura@f5.com            user='root',
841307St.nateldemoura@f5.com            group=nogroup,
851307St.nateldemoura@f5.com            isolation={'namespaces': {'credential': True}},
861307St.nateldemoura@f5.com        )
871307St.nateldemoura@f5.com
881307St.nateldemoura@f5.com        obj = self.getjson()['body']
891307St.nateldemoura@f5.com
901596Szelenkov@nginx.com        assert obj['UID'] == 0, 'uid match user=root group=nogroup'
911596Szelenkov@nginx.com        assert obj['GID'] == nogroup_gid, 'gid match user=root group=nogroup'
921307St.nateldemoura@f5.com
931307St.nateldemoura@f5.com        self.load(
941307St.nateldemoura@f5.com            'ns_inspect',
951307St.nateldemoura@f5.com            user='root',
961307St.nateldemoura@f5.com            group='root',
971307St.nateldemoura@f5.com            isolation={
981307St.nateldemoura@f5.com                'namespaces': {'credential': True},
991596Szelenkov@nginx.com                'uidmap': [{'container': 0, 'host': os.geteuid(), 'size': 1}],
1001596Szelenkov@nginx.com                'gidmap': [{'container': 0, 'host': os.getegid(), 'size': 1}],
1011307St.nateldemoura@f5.com            },
1021307St.nateldemoura@f5.com        )
1031307St.nateldemoura@f5.com
1041307St.nateldemoura@f5.com        obj = self.getjson()['body']
1051307St.nateldemoura@f5.com
1061596Szelenkov@nginx.com        assert obj['UID'] == 0, 'uid match uidmap'
1071596Szelenkov@nginx.com        assert obj['GID'] == 0, 'gid match gidmap'
1081307St.nateldemoura@f5.com
1091596Szelenkov@nginx.com    def test_isolation_priv_user(self, is_su):
1101596Szelenkov@nginx.com        if not is_su:
1111596Szelenkov@nginx.com            pytest.skip('unprivileged tests, skip this')
1121307St.nateldemoura@f5.com
1131182St.nateldemoura@f5.com        self.load('ns_inspect')
1141293St.nateldemoura@f5.com
1151307St.nateldemoura@f5.com        nobody_uid, nogroup_gid, nogroup = self.unpriv_creds()
1161307St.nateldemoura@f5.com
1171307St.nateldemoura@f5.com        obj = self.getjson()['body']
1181293St.nateldemoura@f5.com
1191596Szelenkov@nginx.com        assert obj['UID'] == nobody_uid, 'uid match'
1201596Szelenkov@nginx.com        assert obj['GID'] == nogroup_gid, 'gid match'
1211307St.nateldemoura@f5.com
1221307St.nateldemoura@f5.com        self.load('ns_inspect', isolation={'namespaces': {'credential': True}})
1231293St.nateldemoura@f5.com
1241296St.nateldemoura@f5.com        obj = self.getjson()['body']
1251182St.nateldemoura@f5.com
1261307St.nateldemoura@f5.com        # privileged unit map app creds in the container by default
1271596Szelenkov@nginx.com        assert obj['UID'] == nobody_uid, 'uid nobody'
1281596Szelenkov@nginx.com        assert obj['GID'] == nogroup_gid, 'gid nobody'
1291293St.nateldemoura@f5.com
1301307St.nateldemoura@f5.com        self.load(
1311307St.nateldemoura@f5.com            'ns_inspect',
1321307St.nateldemoura@f5.com            user='root',
1331307St.nateldemoura@f5.com            isolation={'namespaces': {'credential': True}},
1341307St.nateldemoura@f5.com        )
1351182St.nateldemoura@f5.com
1361296St.nateldemoura@f5.com        obj = self.getjson()['body']
1371182St.nateldemoura@f5.com
1381596Szelenkov@nginx.com        assert obj['UID'] == 0, 'uid nobody user=root'
1391596Szelenkov@nginx.com        assert obj['GID'] == 0, 'gid nobody user=root'
1401182St.nateldemoura@f5.com
1411307St.nateldemoura@f5.com        self.load(
1421307St.nateldemoura@f5.com            'ns_inspect',
1431307St.nateldemoura@f5.com            user='root',
1441307St.nateldemoura@f5.com            group=nogroup,
1451307St.nateldemoura@f5.com            isolation={'namespaces': {'credential': True}},
1461182St.nateldemoura@f5.com        )
1471182St.nateldemoura@f5.com
1481296St.nateldemoura@f5.com        obj = self.getjson()['body']
1491182St.nateldemoura@f5.com
1501596Szelenkov@nginx.com        assert obj['UID'] == 0, 'uid match user=root group=nogroup'
1511596Szelenkov@nginx.com        assert obj['GID'] == nogroup_gid, 'gid match user=root group=nogroup'
1521307St.nateldemoura@f5.com
1531307St.nateldemoura@f5.com        self.load(
1541307St.nateldemoura@f5.com            'ns_inspect',
1551307St.nateldemoura@f5.com            user='root',
1561307St.nateldemoura@f5.com            group='root',
1571307St.nateldemoura@f5.com            isolation={
1581307St.nateldemoura@f5.com                'namespaces': {'credential': True},
1591307St.nateldemoura@f5.com                'uidmap': [{'container': 0, 'host': 0, 'size': 1}],
1601307St.nateldemoura@f5.com                'gidmap': [{'container': 0, 'host': 0, 'size': 1}],
1611307St.nateldemoura@f5.com            },
1621307St.nateldemoura@f5.com        )
1631307St.nateldemoura@f5.com
1641307St.nateldemoura@f5.com        obj = self.getjson()['body']
1651307St.nateldemoura@f5.com
1661596Szelenkov@nginx.com        assert obj['UID'] == 0, 'uid match uidmap user=root'
1671596Szelenkov@nginx.com        assert obj['GID'] == 0, 'gid match gidmap user=root'
1681307St.nateldemoura@f5.com
1691307St.nateldemoura@f5.com        # map 65535 uids
1701307St.nateldemoura@f5.com        self.load(
1711307St.nateldemoura@f5.com            'ns_inspect',
1721307St.nateldemoura@f5.com            user='nobody',
1731307St.nateldemoura@f5.com            isolation={
1741307St.nateldemoura@f5.com                'namespaces': {'credential': True},
1751307St.nateldemoura@f5.com                'uidmap': [
1761307St.nateldemoura@f5.com                    {'container': 0, 'host': 0, 'size': nobody_uid + 1}
1771307St.nateldemoura@f5.com                ],
1781307St.nateldemoura@f5.com            },
1791307St.nateldemoura@f5.com        )
1801307St.nateldemoura@f5.com
1811307St.nateldemoura@f5.com        obj = self.getjson()['body']
1821307St.nateldemoura@f5.com
1831596Szelenkov@nginx.com        assert obj['UID'] == nobody_uid, 'uid match uidmap user=nobody'
1841596Szelenkov@nginx.com        assert obj['GID'] == nogroup_gid, 'gid match uidmap user=nobody'
1851182St.nateldemoura@f5.com
1861182St.nateldemoura@f5.com    def test_isolation_mnt(self):
1871182St.nateldemoura@f5.com        if not self.isolation_key('mnt'):
1881596Szelenkov@nginx.com            pytest.skip('mnt namespace is not supported')
1891182St.nateldemoura@f5.com
1901182St.nateldemoura@f5.com        if not self.isolation_key('unprivileged_userns_clone'):
1911596Szelenkov@nginx.com            pytest.skip('unprivileged clone is not available')
1921182St.nateldemoura@f5.com
1931307St.nateldemoura@f5.com        self.load(
1941307St.nateldemoura@f5.com            'ns_inspect',
1951307St.nateldemoura@f5.com            isolation={'namespaces': {'mount': True, 'credential': True}},
1961182St.nateldemoura@f5.com        )
1971182St.nateldemoura@f5.com
1981296St.nateldemoura@f5.com        obj = self.getjson()['body']
1991182St.nateldemoura@f5.com
2001182St.nateldemoura@f5.com        # all but user and mnt
2011182St.nateldemoura@f5.com        allns = list(self.available['features']['isolation'].keys())
2021182St.nateldemoura@f5.com        allns.remove('user')
2031182St.nateldemoura@f5.com        allns.remove('mnt')
2041182St.nateldemoura@f5.com
2051182St.nateldemoura@f5.com        for ns in allns:
2061182St.nateldemoura@f5.com            if ns.upper() in obj['NS']:
2071596Szelenkov@nginx.com                assert (
2081596Szelenkov@nginx.com                    obj['NS'][ns.upper()]
2091596Szelenkov@nginx.com                    == self.available['features']['isolation'][ns]
2101596Szelenkov@nginx.com                ), ('%s match' % ns)
2111182St.nateldemoura@f5.com
2121596Szelenkov@nginx.com        assert obj['NS']['MNT'] != self.isolation.getns('mnt'), 'mnt set'
2131596Szelenkov@nginx.com        assert obj['NS']['USER'] != self.isolation.getns('user'), 'user set'
2141182St.nateldemoura@f5.com
2151596Szelenkov@nginx.com    def test_isolation_pid(self, is_su):
2161182St.nateldemoura@f5.com        if not self.isolation_key('pid'):
2171596Szelenkov@nginx.com            pytest.skip('pid namespace is not supported')
2181182St.nateldemoura@f5.com
2191596Szelenkov@nginx.com        if not (is_su or self.isolation_key('unprivileged_userns_clone')):
2201596Szelenkov@nginx.com            pytest.skip('requires root or unprivileged_userns_clone')
2211182St.nateldemoura@f5.com
2221307St.nateldemoura@f5.com        self.load(
2231307St.nateldemoura@f5.com            'ns_inspect',
2241307St.nateldemoura@f5.com            isolation={'namespaces': {'pid': True, 'credential': True}},
2251307St.nateldemoura@f5.com        )
2261182St.nateldemoura@f5.com
2271296St.nateldemoura@f5.com        obj = self.getjson()['body']
2281182St.nateldemoura@f5.com
2291596Szelenkov@nginx.com        assert obj['PID'] == 1, 'pid of container is 1'
2301182St.nateldemoura@f5.com
2311236St.nateldemoura@f5.com    def test_isolation_namespace_false(self):
2321236St.nateldemoura@f5.com        self.load('ns_inspect')
2331236St.nateldemoura@f5.com        allns = list(self.available['features']['isolation'].keys())
2341236St.nateldemoura@f5.com
2351236St.nateldemoura@f5.com        remove_list = ['unprivileged_userns_clone', 'ipc', 'cgroup']
2361236St.nateldemoura@f5.com        allns = [ns for ns in allns if ns not in remove_list]
2371236St.nateldemoura@f5.com
2381236St.nateldemoura@f5.com        namespaces = {}
2391236St.nateldemoura@f5.com        for ns in allns:
2401236St.nateldemoura@f5.com            if ns == 'user':
2411236St.nateldemoura@f5.com                namespaces['credential'] = False
2421236St.nateldemoura@f5.com            elif ns == 'mnt':
2431236St.nateldemoura@f5.com                namespaces['mount'] = False
2441236St.nateldemoura@f5.com            elif ns == 'net':
2451236St.nateldemoura@f5.com                namespaces['network'] = False
2461236St.nateldemoura@f5.com            elif ns == 'uts':
2471236St.nateldemoura@f5.com                namespaces['uname'] = False
2481236St.nateldemoura@f5.com            else:
2491236St.nateldemoura@f5.com                namespaces[ns] = False
2501236St.nateldemoura@f5.com
2511307St.nateldemoura@f5.com        self.load('ns_inspect', isolation={'namespaces': namespaces})
2521236St.nateldemoura@f5.com
2531296St.nateldemoura@f5.com        obj = self.getjson()['body']
2541236St.nateldemoura@f5.com
2551236St.nateldemoura@f5.com        for ns in allns:
2561236St.nateldemoura@f5.com            if ns.upper() in obj['NS']:
2571596Szelenkov@nginx.com                assert (
2581596Szelenkov@nginx.com                    obj['NS'][ns.upper()]
2591596Szelenkov@nginx.com                    == self.available['features']['isolation'][ns]
2601596Szelenkov@nginx.com                ), ('%s match' % ns)
2611236St.nateldemoura@f5.com
2621490St.nateldemoura@f5.com    def test_go_isolation_rootfs_container(self):
2631490St.nateldemoura@f5.com        if not self.isolation_key('unprivileged_userns_clone'):
2641596Szelenkov@nginx.com            pytest.skip('unprivileged clone is not available')
2651490St.nateldemoura@f5.com
2661490St.nateldemoura@f5.com        if not self.isolation_key('mnt'):
2671596Szelenkov@nginx.com            pytest.skip('mnt namespace is not supported')
2681490St.nateldemoura@f5.com
2691490St.nateldemoura@f5.com        isolation = {
2701490St.nateldemoura@f5.com            'namespaces': {'mount': True, 'credential': True},
2711596Szelenkov@nginx.com            'rootfs': self.temp_dir,
2721490St.nateldemoura@f5.com        }
2731490St.nateldemoura@f5.com
2741490St.nateldemoura@f5.com        self.load('ns_inspect', isolation=isolation)
2751490St.nateldemoura@f5.com
2761490St.nateldemoura@f5.com        obj = self.getjson(url='/?file=/go/app')['body']
2771490St.nateldemoura@f5.com
2781596Szelenkov@nginx.com        assert obj['FileExists'] == True, 'app relative to rootfs'
2791490St.nateldemoura@f5.com
2801490St.nateldemoura@f5.com        obj = self.getjson(url='/?file=/bin/sh')['body']
2811596Szelenkov@nginx.com        assert obj['FileExists'] == False, 'file should not exists'
2821490St.nateldemoura@f5.com
2831596Szelenkov@nginx.com    def test_go_isolation_rootfs_container_priv(self, is_su):
2841596Szelenkov@nginx.com        if not is_su:
2851596Szelenkov@nginx.com            pytest.skip('requires root')
2861490St.nateldemoura@f5.com
2871490St.nateldemoura@f5.com        if not self.isolation_key('mnt'):
2881596Szelenkov@nginx.com            pytest.skip('mnt namespace is not supported')
2891490St.nateldemoura@f5.com
2901490St.nateldemoura@f5.com        isolation = {
2911490St.nateldemoura@f5.com            'namespaces': {'mount': True},
2921596Szelenkov@nginx.com            'rootfs': self.temp_dir,
2931490St.nateldemoura@f5.com        }
2941490St.nateldemoura@f5.com
2951490St.nateldemoura@f5.com        self.load('ns_inspect', isolation=isolation)
2961490St.nateldemoura@f5.com
2971490St.nateldemoura@f5.com        obj = self.getjson(url='/?file=/go/app')['body']
2981490St.nateldemoura@f5.com
2991596Szelenkov@nginx.com        assert obj['FileExists'] == True, 'app relative to rootfs'
3001490St.nateldemoura@f5.com
3011490St.nateldemoura@f5.com        obj = self.getjson(url='/?file=/bin/sh')['body']
3021596Szelenkov@nginx.com        assert obj['FileExists'] == False, 'file should not exists'
3031490St.nateldemoura@f5.com
3041581St.nateldemoura@f5.com    def test_go_isolation_rootfs_default_tmpfs(self):
3051581St.nateldemoura@f5.com        if not self.isolation_key('unprivileged_userns_clone'):
3061596Szelenkov@nginx.com            pytest.skip('unprivileged clone is not available')
3071581St.nateldemoura@f5.com
3081581St.nateldemoura@f5.com        if not self.isolation_key('mnt'):
3091596Szelenkov@nginx.com            pytest.skip('mnt namespace is not supported')
3101581St.nateldemoura@f5.com
3111581St.nateldemoura@f5.com        isolation = {
3121581St.nateldemoura@f5.com            'namespaces': {'mount': True, 'credential': True},
3131596Szelenkov@nginx.com            'rootfs': self.temp_dir,
3141581St.nateldemoura@f5.com        }
3151581St.nateldemoura@f5.com
3161581St.nateldemoura@f5.com        self.load('ns_inspect', isolation=isolation)
3171581St.nateldemoura@f5.com
3181581St.nateldemoura@f5.com        obj = self.getjson(url='/?file=/tmp')['body']
3191581St.nateldemoura@f5.com
3201596Szelenkov@nginx.com        assert obj['FileExists'] == True, 'app has /tmp'
321