]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
pytest: tests for conditional ACEs with security tokens
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>
Thu, 24 Aug 2023 01:44:17 +0000 (13:44 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 26 Sep 2023 23:45:35 +0000 (23:45 +0000)
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/tests/conditional_ace_claims.py [new file with mode: 0644]
selftest/knownfail.d/conditional_ace_claims [new file with mode: 0644]
selftest/tests.py

diff --git a/python/samba/tests/conditional_ace_claims.py b/python/samba/tests/conditional_ace_claims.py
new file mode 100644 (file)
index 0000000..c385864
--- /dev/null
@@ -0,0 +1,338 @@
+# Unix SMB/CIFS implementation.
+# Copyright © Catalyst IT 2023
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""Tests for Conditional ACEs, claims, and security tokens."""
+
+from samba.dcerpc import security
+from samba.security import access_check
+from samba.tests.token_factory import token as Token
+from samba.tests import TestCase, DynamicTestCase, get_env_dir
+from samba.colour import c_RED, c_GREEN
+import os
+from samba import NTSTATUSError
+from samba.ntstatus import NT_STATUS_ACCESS_DENIED
+
+DEFAULT_ACCESS = security.SEC_FILE_ALL
+DEFAULT_ACCESS2 = (security.SEC_STD_READ_CONTROL |
+                   security.SEC_ADS_LIST |
+                   security.SEC_ADS_READ_PROP)
+
+
+class ConditionalAceClaimsBase(TestCase):
+    maxDiff = 0
+
+    @classmethod
+    def setUpDynamicTestCases(cls):
+        cls.domain_sid = security.dom_sid("S-1-22-333-4444")
+        seen = set()
+
+        for i, row in enumerate(cls.data):
+            token, sddl, access_desired = row
+            name = f'{i+1:03}-{token}-{sddl}-{access_desired}'
+            if len(name) > 150:
+                name = f"{name[:125]}+{len(name) - 125}-more-characters"
+
+            if name in seen:
+                print(f"seen {row} after {len(seen)}")
+            seen.add(name)
+
+            if cls.allow:
+                cls.generate_dynamic_test('test_allow',
+                                          name, token, sddl, access_desired)
+            else:
+                cls.generate_dynamic_test('test_deny',
+                                          name, token, sddl, access_desired)
+
+        fuzz_seed_dir = get_env_dir('SAMBA_WRITE_FUZZ_STRINGS_DIR')
+        if fuzz_seed_dir is not None:
+            cls._write_sddl_strings_for_fuzz_seeds(fuzz_seed_dir)
+
+    @classmethod
+    def _write_sddl_strings_for_fuzz_seeds(cls, fuzz_seed_dir):
+        """write all the SDDL strings we have into a directory as individual
+        files, using a naming convention beloved of fuzzing engines.
+
+        To run this set an environment variable; see
+        cls.setUpDynamicTestCases(), below.
+
+        Note this will only run in subclasses annotated with @DynamicTestCase.
+        """
+        from hashlib import md5
+        for _, sddl, _ in cls.data:
+            name = md5(sddl.encode()).hexdigest()
+            with open(os.path.join(fuzz_seed_dir, name), 'w') as f:
+                f.write(sddl)
+
+    def _test_allow_with_args(self, _token, sddl, access_desired):
+        if isinstance(_token, dict):
+            token = Token(**_token)
+        else:
+            token = _token
+        sd = security.descriptor.from_sddl(sddl, self.domain_sid)
+        try:
+            granted = access_check(sd, token, access_desired)
+        except NTSTATUSError as e:
+            print(c_RED(sddl))
+            print(c_RED(_token))
+            if e.args[0] != NT_STATUS_ACCESS_DENIED:
+                raise
+            self.fail("access was denied")
+
+        self.assertEqual(granted, access_desired)
+
+    def _test_deny_with_args(self, token, sddl, access_desired):
+        if isinstance(token, dict):
+            token = Token(**token)
+        sd = security.descriptor.from_sddl(sddl, self.domain_sid)
+        try:
+            granted = access_check(sd, token, access_desired)
+        except NTSTATUSError as e:
+            if e.args[0] == NT_STATUS_ACCESS_DENIED:
+                return
+            self.fail(f"failed with {e}, not access denied")
+
+        self.fail("access allowed")
+
+
+@DynamicTestCase
+class AllowTests(ConditionalAceClaimsBase):
+    name = "allow"
+    allow = True
+    data = [
+        (  # device_claims
+            {'sids': ['WD', 'AA'],
+             'device_claims': {"colour":["orange", "blue"]}},
+            ('D:(XA;;0x1f;;;AA;'
+             '(@Device.colour == {"orange", "blue"}))'),
+            0x10),
+        (  # device_claims, int >=
+            {'sids': ['WD', 'AA'],
+             'device_claims': {"legs": 4}},
+            ('D:(XA;;0x1f;;;AA;(@Device.legs >= 1))'),
+            0x10),
+        (  # device_claims, int
+            {'sids': ['WD', 'AA'],
+             'device_claims': {"legs": 1}},
+            ('D:(XA;;0x1f;;;AA;(@Device.legs == 1))'),
+            0x10),
+        (  # device_member_of && member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            ("D:(XA;;0x1f;;;AA;"
+             "(Device_Member_of{SID(BA)} && Member_of{SID(WD)}))"),
+            0x10),
+        (  # device_member_of || member_of, both true
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            ("D:(XA;;0x1f;;;AA;"
+             "(Device_Member_of{SID(AA)} || Member_of{SID(WD)}))"),
+            0x10),
+        (  # device_member_of || member_of, second true
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            ("D:(XA;;0x1f;;;AA;"
+             "(Device_Member_of{SID(AA)} || Member_of{SID(WD)}))"),
+            0x10),
+        (  # device_member_of || member_of, first true
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            ("D:(XA;;0x1f;;;AA;"
+             "(Device_Member_of{SID(BG)} || Member_of{SID(WR)}))"),
+            0x10),
+        (  # single SID, Member_of_Any
+            {'sids': ['S-1-222-333']},
+            ("D:(XA;;0x1ff;;;S-1-222-333;(Member_of_Any{SID(S-1-222-333)}))"),
+            0x1),
+        ({'sids': ['S-1-1-0']}, "O:S-1-1-0D:(A;;0x1ff;;;WD)", DEFAULT_ACCESS),
+        ({'sids': ['S-1-1-0']},
+         "O:S-1-1-0D:(XA;;0x1ff;;;WD;(Member_of{SID(S-1-1-0)}))",
+         DEFAULT_ACCESS),
+        ({'sids': ['S-1-1-0', 'S-1-222-333']},
+         "O:S-1-1-0D:(XA;;0x1ff;;;WD;(Member_of{SID(S-1-1-0)}))",
+         DEFAULT_ACCESS),
+        ({'sids': ['WD', 'S-1-222-333']},
+         "O:S-1-1-0D:(XA;;0x1ff;;;WD;(Member_of{SID(S-1-1-0)}))",
+         DEFAULT_ACCESS),
+        (  # a single SID, not a composite
+            {'sids': ['S-1-1-0', 'S-1-222-333']},
+            "O:S-1-1-0D:(XA;;0x1ff;;;WD;(Member_of SID(S-1-1-0)))",
+            DEFAULT_ACCESS),
+        (  # a single SID, not a composite, without space after Member_of
+            {'sids': ['S-1-1-0', 'S-1-222-333']},
+            "O:S-1-1-0D:(XA;;0x1ff;;;WD;(Member_of\nSID(S-1-1-0)))",
+            DEFAULT_ACCESS),
+        (  # a single SID, not a composite, Member_of_Any
+            {'sids': ['S-1-1-0', 'S-1-222-333']},
+            "O:S-1-1-0D:(XA;;0x1ff;;;WD;(Member_of_Any SID(S-1-1-0)))",
+            DEFAULT_ACCESS),
+        (  # Member_of_Any
+            {'sids': ['S-1-1-0', 'S-1-222-333']},
+            "O:S-1-1-0D:(XA;;0x1;;;WD;(Member_of_Any{SID(AS),SID(WD)}))",
+            0x1),
+        ({'sids': ['S-1-1-0', 'S-1-222-333']},
+         ("O:S-1-1-0D:"
+          "(XA;;0x1ff;;;WD;(Member_of_Any{SID(S-1-1-0), SID(S-1-222-333)}))"),
+         DEFAULT_ACCESS),
+        ({'sids': ['S-1-1-0', 'S-1-222-333']},
+         ("O:S-1-1-0D:"
+          "(XA;;0x1ff;;;WD;(Member_of_Any{SID(S-1-1-334), SID(S-1-222-333)}))"),
+         DEFAULT_ACCESS),
+        ({'sids': ['S-1-1-0', 'S-1-222-333']},
+         ("D:(XA;;0x1ff;;;WD;(Member_of_Any{SID(S-1-222-333)}))"),
+         DEFAULT_ACCESS),
+        ({'sids': ['S-1-77-88-99', 'AA']},
+         "D:(XA;;0x1f;;;AA;(Member_of{SID(S-1-77-88-99)}))",
+         0x10),
+        (  # device_member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(Device_Member_of{SID(BA)}))",
+            0x10),
+        (  # device_member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(Device_Member_of{SID(BA)}))",
+            0x10),
+        (  # not (!) member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(! (Member_of{SID(BA)})))",
+            0x10),
+        (  # not not (!!) member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(!(! (Member_of{SID(AA)}))))",
+            0x10),
+        (  # not * 8 (!!!! !!!!) member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(!(!(!(!(!(!(!(!( Member_of{SID(AA)}))))))))))",
+            0x10),
+        (  # not * 9 (!!! !!! !!!) member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(!(!(!( !(!(!( !(!(!(Member_of{SID(BA)})))))))))))",
+            0x10),
+        (  # not * 9 (!!! !!! !!!) Not_Member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            ("D:(XA;;0x1f;;;AA;"
+             "(!(!(!(  !(!(!(  !(!(!(  Not_Member_of{SID(AA)})))))))))))"),
+            0x10),
+        (  #resource ACE
+            {'sids': ['WD', 'AA'],
+             'device_claims': {"colour": ["blue"]}},
+            ('D:(XA;;0x1f;;;AA;(@Device.colour Contains @Resource.colour))'
+             'S:(RA;;;;;WD;("colour",TS,0,"blue"))'),
+            0x10),
+        (  #resource ACE ==
+            {'sids': ['WD', 'AA'],
+             'device_claims': {"colour": ["blue"]}},
+            ('D:(XA;;0x1f;;;AA;(@Device.colour == @Resource.colour))'
+             'S:(RA;;;;;WD;("colour",TS,0,"blue"))'),
+            0x10),
+        (  # device_claims, comparing single to single
+            {'sids': ['WD', 'AA'],
+             'device_claims': {"colour": "blue"}},
+            ('D:(XA;;0x1f;;;AA;(@Device.colour == "blue"))'),
+            0x10),
+        (  # device_claims == user_claims
+            {'sids': ['WD', 'AA'],
+             'user_claims': {"colour": "blue"},
+             'device_claims': {"colour": "blue"}},
+            ('D:(XA;;0x1f;;;AA;(@User.colour == @Device.colour))'),
+            0x10),
+        (  #resource ACE multi
+            {'sids': ['WD', 'AA'],
+             'device_claims': {"colour": ["blue", "red"]}},
+            ('D:(XA;;0x1f;;;AA;(@Device.colour Contains @Resource.colour))'
+             'S:(RA;;;;;WD;("colour",TS,0,"blue", "red"))'),
+            0x10),
+    ]
+
+
+@DynamicTestCase
+class DenyTests(ConditionalAceClaimsBase):
+    name = "allow"
+    allow = False
+    data = [
+        ({}, "", DEFAULT_ACCESS),
+        ({'sids': ['S-1-1-0']}, "O:S-1-1-0D:(A;;0x1fe;;;WD)", DEFAULT_ACCESS),
+        ({}, "O:WDD:(A;;GACR;;;CO)", DEFAULT_ACCESS),
+        ({'sids': ['S-1-1-0', 'S-1-222-444']},
+         ("D:(XA;;0x1ff;;;WD;(Member_of_Any{SID(S-1-222-333)}))"),
+         0x1),
+        (  # Without explicit 'everyone' SID in list of SIDs, this is
+           # denied because the ACE SID 'WD' doesn't match.
+            {'sids': ['S-1-222-333']},
+         ("D:(XA;;0x1ff;;;WD;(Member_of_Any{SID(S-1-222-333)}))"),
+         0x1),
+        (  # device_member_of && member_of, both false
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            ("D:(XA;;0x1f;;;AA;"
+             "(Device_Member_of{SID(AA)} && Member_of{SID(WR)}))"),
+            0x10),
+        (  # device_member_of && member_of, first false
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            ("D:(XA;;0x1f;;;AA;"
+             "(Device_Member_of{SID(AA)} && Member_of{SID(WD)}))"),
+            0x10),
+        (  # device_member_of && member_of, second false
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            ("D:(XA;;0x1f;;;AA;"
+             "(Device_Member_of{SID(BA)} && Member_of{SID(BA)}))"),
+            0x10),
+        (  # device_member_of || member_of, both false
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            ("D:(XA;;0x1f;;;AA;"
+             "(Device_Member_of{SID(AA)} || Member_of{SID(WR)}))"),
+            0x10),
+        (  # device_claims, comparing composite to single
+            {'sids': ['WD', 'AA'],
+             'device_claims': {"colour": ["orange", "blue"]}},
+            ('D:(XA;;0x1f;;;AA;(@Device.colour == "blue"))'),
+            0x10),
+        (  # not (!) member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(! (Member_of{SID(AA)})))",
+            0x10),
+        (  # not not (!!) member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(!(!( Member_of{SID(BA)}))))",
+            0x10),
+        (  # not * 8 (!!!! !!!!) member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(!(!( !(!( !(!( !(!(Member_of{SID(BA)}))))))))))",
+            0x10),
+        (  # not * 3 (!!!) member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(!(!(!(Member_of{SID(AA)})))))",
+            0x10),
+        (  # not * 3 (!!!) Not_Member_of
+            {'sids': ['WD', 'AA'],
+             'device_sids': ['BA', 'BG']},
+            "D:(XA;;0x1f;;;AA;(!(!(!(Not_Member_of{SID(BA)})))))",
+            0x10),
+    ]
diff --git a/selftest/knownfail.d/conditional_ace_claims b/selftest/knownfail.d/conditional_ace_claims
new file mode 100644 (file)
index 0000000..11e96d7
--- /dev/null
@@ -0,0 +1,32 @@
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_001-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_002-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_003-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_004-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_005-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_006-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_007-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_008-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_010-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_011-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_012-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_013-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_014-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_015-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_016-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_017-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_018-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_019-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_020-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_021-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_022-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_023-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_024-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_025-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_026-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_027-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_028-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_029-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_030-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_031-
+^samba.tests.+conditional_ace_claims.AllowTests.test_allow_032-
+
index 6faa22a717b3110919951f92a8b42222f9ae7d15..89af15a075c0f3ae2d9dae5b85407012ff588952 100644 (file)
@@ -95,6 +95,7 @@ planpythontestsuite("none", "samba.tests.samba3sam")
 planpythontestsuite("none", "samba.tests.dsdb_api")
 planpythontestsuite("none", "samba.tests.smbconf")
 planpythontestsuite("none", "samba.tests.logfiles")
+planpythontestsuite("none", "samba.tests.conditional_ace_claims")
 planpythontestsuite(
     "none", "wafsamba.tests.test_suite",
     extra_path=[os.path.join(samba4srcdir, "..", "buildtools"),