mirror of
https://github.com/samba-team/samba.git
synced 2024-12-25 23:21:54 +03:00
66b4597862
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
5036 lines
208 KiB
Python
Executable File
5036 lines
208 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
# Unix SMB/CIFS implementation.
|
||
# Copyright (C) Stefan Metzmacher 2020
|
||
# Copyright (C) Catalyst.Net Ltd 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/>.
|
||
#
|
||
|
||
import sys
|
||
import os
|
||
|
||
sys.path.insert(0, 'bin/python')
|
||
os.environ['PYTHONUNBUFFERED'] = '1'
|
||
|
||
from collections import OrderedDict
|
||
from functools import partial
|
||
import re
|
||
from string import Formatter
|
||
|
||
import ldb
|
||
|
||
from samba import dsdb, ntstatus
|
||
from samba.dcerpc import claims, krb5pac, security
|
||
from samba.ndr import ndr_pack, ndr_unpack
|
||
from samba.sd_utils import escaped_claim_id
|
||
|
||
from samba.tests import DynamicTestCase, env_get_var_value
|
||
from samba.tests.krb5.authn_policy_tests import (
|
||
AuditEvent,
|
||
AuditReason,
|
||
AuthnPolicyBaseTests,
|
||
)
|
||
from samba.tests.krb5.raw_testcase import RawKerberosTest
|
||
from samba.tests.krb5.rfc4120_constants import (
|
||
KDC_ERR_BADOPTION,
|
||
KDC_ERR_GENERIC,
|
||
KDC_ERR_MODIFIED,
|
||
KDC_ERR_POLICY,
|
||
NT_PRINCIPAL,
|
||
)
|
||
import samba.tests.krb5.rfc4120_pyasn1 as krb5_asn1
|
||
|
||
SidType = RawKerberosTest.SidType
|
||
|
||
global_asn1_print = False
|
||
global_hexdump = False
|
||
|
||
|
||
# When used as a test outcome, indicates that the test can cause a Windows
|
||
# server to crash, and is to be run with caution.
|
||
CRASHES_WINDOWS = object()
|
||
|
||
|
||
class ConditionalAceBaseTests(AuthnPolicyBaseTests):
|
||
# Constants for group SID attributes.
|
||
default_attrs = security.SE_GROUP_DEFAULT_FLAGS
|
||
resource_attrs = default_attrs | security.SE_GROUP_RESOURCE
|
||
|
||
aa_asserted_identity = (
|
||
security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY)
|
||
service_asserted_identity = security.SID_SERVICE_ASSERTED_IDENTITY
|
||
|
||
@classmethod
|
||
def setUpClass(cls):
|
||
super().setUpClass()
|
||
|
||
cls._setup = False
|
||
|
||
def setUp(self):
|
||
super().setUp()
|
||
self.do_asn1_print = global_asn1_print
|
||
self.do_hexdump = global_hexdump
|
||
|
||
if not self._setup:
|
||
samdb = self.get_samdb()
|
||
cls = type(self)
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
cls._mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER)
|
||
|
||
# Create some new groups.
|
||
|
||
group0_name = self.get_new_username()
|
||
group0_dn = self.create_group(samdb, group0_name)
|
||
cls._group0_sid = self.get_objectSid(samdb, group0_dn)
|
||
|
||
group1_name = self.get_new_username()
|
||
group1_dn = self.create_group(samdb, group1_name)
|
||
cls._group1_sid = self.get_objectSid(samdb, group1_dn)
|
||
|
||
# Create machine accounts with which to perform FAST that belong to
|
||
# various arrangements of the groups.
|
||
|
||
cls._member_of_both_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'member_of': (group0_dn, group1_dn)})
|
||
|
||
cls._member_of_one_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'member_of': (group1_dn,)})
|
||
|
||
# Create some authentication silos.
|
||
cls._unenforced_silo = self.create_authn_silo(enforced=False)
|
||
cls._enforced_silo = self.create_authn_silo(enforced=True)
|
||
|
||
# Create machine accounts with which to perform FAST that belong to
|
||
# the respective silos.
|
||
|
||
cls._member_of_unenforced_silo = self._get_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
assigned_silo=self._unenforced_silo,
|
||
cached=True)
|
||
self.add_to_group(str(self._member_of_unenforced_silo.get_dn()),
|
||
self._unenforced_silo.dn,
|
||
'msDS-AuthNPolicySiloMembers',
|
||
expect_attr=False)
|
||
|
||
cls._member_of_enforced_silo = self._get_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
assigned_silo=self._enforced_silo,
|
||
cached=True)
|
||
self.add_to_group(str(self._member_of_enforced_silo.get_dn()),
|
||
self._enforced_silo.dn,
|
||
'msDS-AuthNPolicySiloMembers',
|
||
expect_attr=False)
|
||
|
||
# Create a couple of multi‐valued string claims for testing claim
|
||
# value comparisons.
|
||
|
||
cls.claim0_attr = 'carLicense'
|
||
cls.claim0_id = self.get_new_username()
|
||
self.create_claim(cls.claim0_id,
|
||
enabled=True,
|
||
attribute=cls.claim0_attr,
|
||
single_valued=False,
|
||
source_type='AD',
|
||
for_classes=['computer', 'user'],
|
||
value_type=claims.CLAIM_TYPE_STRING)
|
||
|
||
cls.claim1_attr = 'departmentNumber'
|
||
cls.claim1_id = self.get_new_username()
|
||
self.create_claim(cls.claim1_id,
|
||
enabled=True,
|
||
attribute=cls.claim1_attr,
|
||
single_valued=False,
|
||
source_type='AD',
|
||
for_classes=['computer', 'user'],
|
||
value_type=claims.CLAIM_TYPE_STRING)
|
||
|
||
cls._setup = True
|
||
|
||
# For debugging purposes. Prints out the SDDL representation of
|
||
# authentication policy conditions set by the Windows GUI.
|
||
def _print_authn_policy_sddl(self, policy_id):
|
||
policy_dn = self.get_authn_policies_dn()
|
||
policy_dn.add_child(f'CN={policy_id}')
|
||
|
||
attrs = [
|
||
'msDS-ComputerAllowedToAuthenticateTo',
|
||
'msDS-ServiceAllowedToAuthenticateFrom',
|
||
'msDS-ServiceAllowedToAuthenticateTo',
|
||
'msDS-UserAllowedToAuthenticateFrom',
|
||
'msDS-UserAllowedToAuthenticateTo',
|
||
]
|
||
|
||
samdb = self.get_samdb()
|
||
res = samdb.search(policy_dn, scope=ldb.SCOPE_BASE, attrs=attrs)
|
||
self.assertEqual(1, len(res),
|
||
f'Authentication policy {policy_id} not found')
|
||
result = res[0]
|
||
|
||
def print_sddl(attr):
|
||
sd = result.get(attr, idx=0)
|
||
if sd is None:
|
||
return
|
||
|
||
sec_desc = ndr_unpack(security.descriptor, sd)
|
||
print(f'{attr}: {sec_desc.as_sddl()}')
|
||
|
||
for attr in attrs:
|
||
print_sddl(attr)
|
||
|
||
def sddl_array_from_sids(self, sids):
|
||
def sddl_from_sid_entry(sid_entry):
|
||
sid, _, _ = sid_entry
|
||
return f'SID({sid})'
|
||
|
||
return f"{{{', '.join(map(sddl_from_sid_entry, sids))}}}"
|
||
|
||
def allow_if(self, condition):
|
||
return f'O:SYD:(XA;;CR;;;WD;({condition}))'
|
||
|
||
|
||
@DynamicTestCase
|
||
class ConditionalAceTests(ConditionalAceBaseTests):
|
||
@classmethod
|
||
def setUpDynamicTestCases(cls):
|
||
FILTER = env_get_var_value('FILTER', allow_missing=True)
|
||
|
||
# These operators are arranged so that each operator precedes its own
|
||
# affixes.
|
||
op_names = OrderedDict([
|
||
('!=', 'does not equal'),
|
||
('!', 'not'),
|
||
('&&', 'and'),
|
||
('<=', 'is less than or equals'),
|
||
('<', 'is less than'),
|
||
('==', 'equals'),
|
||
('>=', 'exceeds or equals'),
|
||
('>', 'exceeds'),
|
||
('Not_Any_of', 'matches none of'),
|
||
('Any_of', 'matches any of'),
|
||
('Not_Contains', 'does not contain'),
|
||
('Contains', 'contains'),
|
||
('Not_Member_of_Any', 'the user belongs to none of'),
|
||
('Not_Device_Member_of_Any', 'the device belongs to none of'), # TODO: no test for this yet
|
||
('Device_Member_of_Any', 'the device belongs to any of'), # TODO: no test for this yet
|
||
('Not_Device_Member_of', 'the device does not belong to'), # TODO: no test for this yet
|
||
('Device_Member_of', 'the device belongs to'),
|
||
('Not_Exists', 'there does not exist'),
|
||
('Exists', 'there exists'),
|
||
('Member_of_Any', 'the user belongs to any of'),
|
||
('Not_Member_of', 'the user does not belong to'),
|
||
('Member_of', 'the user belongs to'),
|
||
('||', 'or'),
|
||
])
|
||
|
||
# This is a safety measure to ensure correct ordering of op_names
|
||
keys = list(op_names.keys())
|
||
for i in range(len(keys)):
|
||
for j in range(i + 1, len(keys)):
|
||
if keys[i] in keys[j]:
|
||
raise AssertionError((keys[i], keys[j]))
|
||
|
||
for case in cls.pac_claim_cases:
|
||
if len(case) == 3:
|
||
pac_claims, expression, outcome = case
|
||
claim_map = None
|
||
elif len(case) == 4:
|
||
pac_claims, expression, claim_map, outcome = case
|
||
else:
|
||
raise AssertionError(
|
||
f'found {len(case)} items in case, expected 3–4')
|
||
|
||
expression_name = expression
|
||
for op, op_name in op_names.items():
|
||
expression_name = expression_name.replace(op, op_name)
|
||
|
||
name = f'{pac_claims}_{expression_name}'
|
||
|
||
if claim_map is not None:
|
||
name += f'_{claim_map}'
|
||
|
||
name = re.sub(r'\W+', '_', name)
|
||
if len(name) > 150:
|
||
name = f'{name[:125]}+{len(name) - 125}‐more'
|
||
|
||
if FILTER and not re.search(FILTER, name):
|
||
continue
|
||
|
||
cls.generate_dynamic_test('test_pac_claim_cmp', name,
|
||
pac_claims, expression, claim_map,
|
||
outcome)
|
||
|
||
for case in cls.claim_against_claim_cases:
|
||
lhs, op, rhs, outcome = case
|
||
op_name = op_names[op]
|
||
|
||
name = f'{lhs}_{op_name}_{rhs}'
|
||
|
||
name = re.sub(r'\W+', '_', name)
|
||
if FILTER and not re.search(FILTER, name):
|
||
continue
|
||
|
||
cls.generate_dynamic_test('test_cmp', name,
|
||
lhs, op, rhs, outcome)
|
||
|
||
for case in cls.claim_against_literal_cases:
|
||
lhs, op, rhs, outcome = case
|
||
op_name = op_names[op]
|
||
|
||
name = f'{lhs}_{op_name}_literal_{rhs}'
|
||
|
||
name = re.sub(r'\W+', '_', name)
|
||
if FILTER and not re.search(FILTER, name):
|
||
continue
|
||
|
||
cls.generate_dynamic_test('test_cmp', name,
|
||
lhs, op, rhs, outcome, True)
|
||
|
||
def test_allowed_from_member_of_each(self):
|
||
# Create an authentication policy that allows accounts belonging to
|
||
# both groups.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XA;;CR;;;WD;(Member_of '
|
||
f'{{SID({self._group0_sid}), SID({self._group1_sid})}}))'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error if the machine account does not
|
||
# belong to both groups.
|
||
armor_tgt = self.get_tgt(self._member_of_one_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
# Otherwise, authentication should succeed.
|
||
armor_tgt = self.get_tgt(self._member_of_both_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_member_of_any(self):
|
||
# Create an authentication policy that allows accounts belonging to
|
||
# either group.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XA;;CR;;;WD;(Member_of_Any '
|
||
f'{{SID({self._group0_sid}), SID({self._group1_sid})}}))'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error if the machine account belongs to
|
||
# neither group.
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
# Otherwise, authentication should succeed.
|
||
armor_tgt = self.get_tgt(self._member_of_one_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_not_member_of_each(self):
|
||
# Create an authentication policy that allows accounts not belonging to
|
||
# both groups.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XA;;CR;;;WD;(Not_Member_of '
|
||
f'{{SID({self._group0_sid}), SID({self._group1_sid})}}))'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error if the machine account belongs to
|
||
# both groups.
|
||
armor_tgt = self.get_tgt(self._member_of_both_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
# Otherwise, authentication should succeed.
|
||
armor_tgt = self.get_tgt(self._member_of_one_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_not_member_of_any(self):
|
||
# Create an authentication policy that allows accounts belonging to
|
||
# neither group.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XA;;CR;;;WD;(Not_Member_of_Any '
|
||
f'{{SID({self._group0_sid}), SID({self._group1_sid})}}))'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error if the machine account belongs to one
|
||
# of the groups.
|
||
armor_tgt = self.get_tgt(self._member_of_one_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
# Otherwise, authentication should succeed.
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_member_of_each_deny(self):
|
||
# Create an authentication policy that denies accounts belonging to
|
||
# both groups, and allows other accounts.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XD;;CR;;;WD;(Member_of '
|
||
f'{{SID({self._group0_sid}), SID({self._group1_sid})}}))'
|
||
f'(A;;CR;;;WD)'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error if the machine account belongs to
|
||
# both groups.
|
||
armor_tgt = self.get_tgt(self._member_of_both_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
# Otherwise, authentication should succeed.
|
||
armor_tgt = self.get_tgt(self._member_of_one_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_member_of_any_deny(self):
|
||
# Create an authentication policy that denies accounts belonging to
|
||
# either group, and allows other accounts.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XD;;CR;;;WD;(Member_of_Any '
|
||
f'{{SID({self._group0_sid}), SID({self._group1_sid})}}))'
|
||
f'(A;;CR;;;WD)'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error if the machine account belongs to
|
||
# either group.
|
||
armor_tgt = self.get_tgt(self._member_of_one_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
# Otherwise, authentication should succeed.
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_not_member_of_each_deny(self):
|
||
# Create an authentication policy that denies accounts not belonging to
|
||
# both groups, and allows other accounts.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XD;;CR;;;WD;(Not_Member_of '
|
||
f'{{SID({self._group0_sid}), SID({self._group1_sid})}}))'
|
||
f'(A;;CR;;;WD)'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error if the machine account doesn’t belong
|
||
# to both groups.
|
||
armor_tgt = self.get_tgt(self._member_of_one_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
# Otherwise, authentication should succeed.
|
||
armor_tgt = self.get_tgt(self._member_of_both_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_not_member_of_any_deny(self):
|
||
# Create an authentication policy that denies accounts belonging to
|
||
# neither group, and allows other accounts.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XD;;CR;;;WD;(Not_Member_of_Any '
|
||
f'{{SID({self._group0_sid}), SID({self._group1_sid})}}))'
|
||
f'(A;;CR;;;WD)'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error if the machine account belongs to
|
||
# neither group.
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
# Otherwise, authentication should succeed.
|
||
armor_tgt = self.get_tgt(self._member_of_one_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_unenforced_silo_equals(self):
|
||
# Create an authentication policy that allows accounts belonging to the
|
||
# unenforced silo.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XA;;CR;;;WD;'
|
||
f'(@User.ad://ext/AuthenticationSilo == '
|
||
f'"{self._unenforced_silo}"))'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# As the policy is unenforced, the ‘ad://ext/AuthenticationSilo’ claim
|
||
# will not be present in the TGT, and the ACE will never allow access.
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_unenforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_enforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
def test_allowed_from_enforced_silo_equals(self):
|
||
# Create an authentication policy that allows accounts belonging to the
|
||
# enforced silo.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XA;;CR;;;WD;'
|
||
f'(@User.ad://ext/AuthenticationSilo == '
|
||
f'"{self._enforced_silo}"))'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error if the machine account does not
|
||
# belong to the silo.
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_unenforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
# Otherwise, authentication should succeed.
|
||
armor_tgt = self.get_tgt(self._member_of_enforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_unenforced_silo_not_equals(self):
|
||
# Create an authentication policy that allows accounts not belonging to
|
||
# the unenforced silo.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XA;;CR;;;WD;'
|
||
f'(@User.ad://ext/AuthenticationSilo != '
|
||
f'"{self._unenforced_silo}"))'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that authentication fails unless the account belongs to a silo
|
||
# other than the unenforced silo.
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_unenforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_enforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_enforced_silo_not_equals(self):
|
||
# Create an authentication policy that allows accounts not belonging to
|
||
# the enforced silo.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XA;;CR;;;WD;'
|
||
f'(@User.ad://ext/AuthenticationSilo != '
|
||
f'"{self._enforced_silo}"))'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that authentication always fails, as none of the machine
|
||
# accounts belong to a silo that is not the enforced one. (The
|
||
# unenforced silo doesn’t count, as it will never appear in a claim.)
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_unenforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_enforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
def test_allowed_from_unenforced_silo_equals_deny(self):
|
||
# Create an authentication policy that denies accounts belonging to the
|
||
# unenforced silo, and allows other accounts.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XD;;CR;;;WD;'
|
||
f'(@User.ad://ext/AuthenticationSilo == '
|
||
f'"{self._unenforced_silo}"))'
|
||
f'(A;;CR;;;WD)'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that authentication fails unless the account belongs to a silo
|
||
# other than the unenforced silo.
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_unenforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_enforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_enforced_silo_equals_deny(self):
|
||
# Create an authentication policy that denies accounts belonging to the
|
||
# enforced silo, and allows other accounts.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XD;;CR;;;WD;'
|
||
f'(@User.ad://ext/AuthenticationSilo == '
|
||
f'"{self._enforced_silo}"))'
|
||
f'(A;;CR;;;WD)'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that authentication always fails, as none of the machine
|
||
# accounts belong to a silo that is not the enforced one. (The
|
||
# unenforced silo doesn’t count, as it will never appear in a claim.)
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_unenforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_enforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
def test_allowed_from_unenforced_silo_not_equals_deny(self):
|
||
# Create an authentication policy that denies accounts not belonging to
|
||
# the unenforced silo, and allows other accounts.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XD;;CR;;;WD;'
|
||
f'(@User.ad://ext/AuthenticationSilo != '
|
||
f'"{self._unenforced_silo}"))'
|
||
f'(A;;CR;;;WD)'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that authentication always fails, as the unenforced silo will
|
||
# never appear in a claim.
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_unenforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_enforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
def test_allowed_from_enforced_silo_not_equals_deny(self):
|
||
# Create an authentication policy that denies accounts not belonging to
|
||
# the enforced silo, and allows other accounts.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XD;;CR;;;WD;'
|
||
f'(@User.ad://ext/AuthenticationSilo != '
|
||
f'"{self._enforced_silo}"))'
|
||
f'(A;;CR;;;WD)'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that authentication fails unless the account belongs to the
|
||
# enforced silo.
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_unenforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
armor_tgt = self.get_tgt(self._member_of_enforced_silo)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_from_claim_equals_claim(self):
|
||
# Create a couple of claim types.
|
||
|
||
claim0_id = self.get_new_username()
|
||
self.create_claim(claim0_id,
|
||
enabled=True,
|
||
attribute='carLicense',
|
||
single_valued=True,
|
||
source_type='AD',
|
||
for_classes=['computer'],
|
||
value_type=claims.CLAIM_TYPE_STRING)
|
||
|
||
claim1_id = self.get_new_username()
|
||
self.create_claim(claim1_id,
|
||
enabled=True,
|
||
attribute='comment',
|
||
single_valued=True,
|
||
source_type='AD',
|
||
for_classes=['computer'],
|
||
value_type=claims.CLAIM_TYPE_STRING)
|
||
|
||
# Create an authentication policy that allows accounts having the two
|
||
# claims be equal.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=(
|
||
f'O:SYD:(XA;;CR;;;WD;'
|
||
f'(@User.{claim0_id} == @User.{claim1_id}))'),
|
||
)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={
|
||
'additional_details': (
|
||
('carLicense', 'foo'),
|
||
('comment', 'foo'),
|
||
),
|
||
})
|
||
armor_tgt = self.get_tgt(
|
||
mach_creds,
|
||
expect_client_claims=True,
|
||
expected_client_claims={
|
||
claim0_id: {
|
||
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
||
'type': claims.CLAIM_TYPE_STRING,
|
||
'values': ('foo',),
|
||
},
|
||
claim1_id: {
|
||
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
||
'type': claims.CLAIM_TYPE_STRING,
|
||
'values': ('foo',),
|
||
},
|
||
})
|
||
self._get_tgt(client_creds, armor_tgt=armor_tgt,
|
||
expected_error=0)
|
||
|
||
def test_allowed_to_client_equals(self):
|
||
client_claim_attr = 'carLicense'
|
||
client_claim_value = 'foo bar'
|
||
client_claim_values = client_claim_value,
|
||
|
||
client_claim_id = self.get_new_username()
|
||
self.create_claim(client_claim_id,
|
||
enabled=True,
|
||
attribute=client_claim_attr,
|
||
single_valued=True,
|
||
source_type='AD',
|
||
for_classes=['user'],
|
||
value_type=claims.CLAIM_TYPE_STRING)
|
||
|
||
# Create an authentication policy that allows authorization if the
|
||
# client has a particular claim value.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
computer_allowed_to=(
|
||
f'O:SYD:(XA;;CR;;;WD;'
|
||
f'((@User.{client_claim_id} == "{client_claim_value}")))'),
|
||
)
|
||
|
||
# Create a computer account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=policy)
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
|
||
# Create a user account without the claim value.
|
||
client_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.USER)
|
||
tgt = self.get_tgt(client_creds)
|
||
# Show that obtaining a service ticket is denied.
|
||
self._tgs_req(
|
||
tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=armor_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
check_patypes=False)
|
||
|
||
# Create a user account with the claim value.
|
||
client_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.USER,
|
||
opts={
|
||
'additional_details': (
|
||
(client_claim_attr, client_claim_values),
|
||
),
|
||
})
|
||
tgt = self.get_tgt(
|
||
client_creds,
|
||
expect_client_claims=True,
|
||
expected_client_claims={
|
||
client_claim_id: {
|
||
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
||
'type': claims.CLAIM_TYPE_STRING,
|
||
'values': client_claim_values,
|
||
},
|
||
})
|
||
# Show that obtaining a service ticket is allowed.
|
||
self._tgs_req(tgt, 0, client_creds, target_creds,
|
||
armor_tgt=armor_tgt)
|
||
|
||
def test_allowed_to_device_equals(self):
|
||
device_claim_attr = 'carLicense'
|
||
device_claim_value = 'bar'
|
||
device_claim_values = device_claim_value,
|
||
|
||
device_claim_id = self.get_new_username()
|
||
self.create_claim(device_claim_id,
|
||
enabled=True,
|
||
attribute=device_claim_attr,
|
||
single_valued=True,
|
||
source_type='AD',
|
||
for_classes=['computer'],
|
||
value_type=claims.CLAIM_TYPE_STRING)
|
||
|
||
# Create a user account.
|
||
client_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.USER)
|
||
tgt = self.get_tgt(client_creds)
|
||
|
||
# Create an authentication policy that allows authorization if the
|
||
# device has a particular claim value.
|
||
policy = self.create_authn_policy(
|
||
enforced=True,
|
||
computer_allowed_to=(
|
||
f'O:SYD:(XA;;CR;;;WD;'
|
||
f'(@Device.{device_claim_id} == "{device_claim_value}"))'),
|
||
)
|
||
|
||
# Create a computer account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=policy)
|
||
|
||
armor_tgt = self.get_tgt(self._mach_creds)
|
||
# Show that obtaining a service ticket is denied when the claim value
|
||
# is not present.
|
||
self._tgs_req(
|
||
tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=armor_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
check_patypes=False)
|
||
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={
|
||
'additional_details': (
|
||
(device_claim_attr, device_claim_values),
|
||
),
|
||
})
|
||
armor_tgt = self.get_tgt(
|
||
mach_creds,
|
||
expect_client_claims=True,
|
||
expected_client_claims={
|
||
device_claim_id: {
|
||
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
||
'type': claims.CLAIM_TYPE_STRING,
|
||
'values': device_claim_values,
|
||
},
|
||
})
|
||
# Show that obtaining a service ticket is allowed when the claim value
|
||
# is present.
|
||
self._tgs_req(tgt, 0, client_creds, target_creds,
|
||
armor_tgt=armor_tgt)
|
||
|
||
claim_against_claim_cases = [
|
||
# If either side is missing, the result is unknown.
|
||
((), '==', (), None),
|
||
((), '!=', (), None),
|
||
('a', '==', (), None),
|
||
((), '==', 'b', None),
|
||
# Straightforward equality and inequality checks work.
|
||
('foo', '==', 'foo', True),
|
||
('foo', '==', 'bar', False),
|
||
('foo', '!=', 'foo', False),
|
||
('foo', '!=', 'bar', True),
|
||
# We can perform less‐than and greater‐than operations.
|
||
('cat', '<', 'dog', True),
|
||
('cat', '<=', 'dog', True),
|
||
('cat', '>', 'dog', False),
|
||
('cat', '>=', 'dog', False),
|
||
('foo', '<=', 'foo', True),
|
||
('foo', '>=', 'foo', True),
|
||
('foo', '<', 'foo bar', True),
|
||
('foo bar', '>', 'foo', True),
|
||
# String comparison is case‐sensitive.
|
||
('foo bar', '==', 'Foo BAR', True),
|
||
('foo bar', '==', 'FOO BAR', True),
|
||
('ćàț', '==', 'ĆÀȚ', True),
|
||
('ḽ', '==', 'Ḽ', True),
|
||
('ⅸ', '==', 'Ⅸ', True),
|
||
('ꙭ', '==', 'Ꙭ', True),
|
||
('ⱦ', '==', 'Ⱦ', True), # Lowercased variant added in Unicode 5.0.
|
||
('ԛԣ', '==', 'ԚԢ', True), # All added in Unicode 5.1.
|
||
('foo', '<', 'foo', True),
|
||
('ćàș', '<', 'ĆÀȚ', True),
|
||
('cat', '<', 'ćàț', True),
|
||
# This is done by converting to UPPER CASE. Hence, both ‘A’ (U+41) and
|
||
# ‘a’ (U+61) compare less than ‘_’ (U+5F).
|
||
('A', '<', '_', True),
|
||
('a', '<', '_', True),
|
||
# But not all uppercased/lowercased pairs are considered to be equal in
|
||
# this way.
|
||
('ß', '<', 'ẞ', True),
|
||
('ß', '>', 'SS', True),
|
||
('ⳬ', '>', 'Ⳬ', True), # Added in Unicode 5.2.
|
||
('ʞ', '<', 'Ʞ', True), # Uppercased variant added in Unicode 6.0.
|
||
('ʞ', '<', 'ʟ', True), # U+029E < U+029F < U+A7B0 (upper variant, Ʞ)
|
||
('ꞧ', '>', 'Ꞧ', True), # Added in Unicode 6.0.
|
||
('ɜ', '<', 'Ɜ', True), # Uppercased variant added in Unicode 7.0.
|
||
#
|
||
# Strings are compared as UTF‐16 code units, rather than as Unicode
|
||
# codepoints. So while you might expect ‘𐀀’ (U+10000) to compare
|
||
# greater than ‘豈’ (U+F900), it is actually considered to be the
|
||
# *smaller* of the pair. That is because it is encoded as a sequence of
|
||
# two code units, 0xd800 and 0xdc00, which combination compares less
|
||
# than the single code unit 0xf900.
|
||
('ퟻ', '<', '𐀀', True),
|
||
('𐀀', '<', '豈', True),
|
||
('ퟻ', '<', '豈', True),
|
||
# Composites can be compared.
|
||
(('foo', 'bar'), '==', ('foo', 'bar'), True),
|
||
(('foo', 'bar'), '==', ('foo', 'baz'), False),
|
||
# The individual components don’t have to match in case.
|
||
(('foo', 'bar'), '==', ('FOO', 'BAR'), True),
|
||
# Nor must they match in order.
|
||
(('foo', 'bar'), '==', ('bar', 'foo'), True),
|
||
# Composites of different lengths compare unequal.
|
||
(('foo', 'bar'), '!=', 'foo', True),
|
||
(('foo', 'bar'), '!=', ('foo', 'bar', 'baz'), True),
|
||
# But composites don’t have a defined ordering, and aren’t considered
|
||
# greater or lesser than one another.
|
||
(('foo', 'bar'), '<', ('foo', 'bar'), None),
|
||
(('foo', 'bar'), '<=', ('foo', 'bar'), None),
|
||
(('foo', 'bar'), '>', ('foo', 'bar', 'baz'), None),
|
||
(('foo', 'bar'), '>=', ('foo', 'bar', 'baz'), None),
|
||
# We can test for containment.
|
||
(('foo', 'bar'), 'Contains', ('FOO'), True),
|
||
(('foo', 'bar'), 'Contains', ('foo', 'bar'), True),
|
||
(('foo', 'bar'), 'Not_Contains', ('foo', 'bar'), False),
|
||
(('foo', 'bar'), 'Contains', ('foo', 'bar', 'baz'), False),
|
||
(('foo', 'bar'), 'Not_Contains', ('foo', 'bar', 'baz'), True),
|
||
# We can test whether the operands have any elements in common.
|
||
('foo', 'Any_of', 'foo', True),
|
||
(('foo', 'bar'), 'Any_of', 'BAR', True),
|
||
(('foo', 'bar'), 'Any_of', 'baz', False),
|
||
(('foo', 'bar'), 'Not_Any_of', 'baz', True),
|
||
(('foo', 'bar'), 'Any_of', ('bar', 'baz'), True),
|
||
(('foo', 'bar'), 'Not_Any_of', ('bar', 'baz'), False),
|
||
]
|
||
|
||
claim_against_literal_cases = [
|
||
# String comparisons also work against literals.
|
||
('foo bar', '==', '"foo bar"', True),
|
||
# Composites can be compared with literals.
|
||
('bar', '==', '{{"bar"}}', True),
|
||
(('apple', 'banana'), '==', '{{"APPLE", "BANANA"}}', True),
|
||
(('apple', 'banana'), '==', '{{"BANANA", "APPLE"}}', True),
|
||
(('apple', 'banana'), '==', '{{"apple", "banana", "apple"}}', False),
|
||
# We can test for containment.
|
||
((), 'Contains', '{{"foo"}}', None),
|
||
((), 'Not_Contains', '{{"foo", "bar"}}', None),
|
||
('bar', 'Contains', '{{"bar"}}', True),
|
||
(('foo', 'bar'), 'Contains', '{{"foo", "bar"}}', True),
|
||
(('foo', 'bar'), 'Contains', '{{"foo", "bar", "baz"}}', False),
|
||
# The right‐hand side of Contains or Not_Contains does not have to be a
|
||
# composite.
|
||
('foo', 'Contains', '"foo"', True),
|
||
(('foo', 'bar'), 'Not_Contains', '"foo"', False),
|
||
# It’s fine if the right‐hand side contains duplicate elements.
|
||
(('foo', 'bar'), 'Contains', '{{"foo", "bar", "bar"}}', True),
|
||
# We can test whether the operands have any elements in common.
|
||
('bar', 'Any_of', '{{"bar"}}', True),
|
||
(('foo', 'bar'), 'Any_of', '{{"bar", "baz"}}', True),
|
||
(('foo', 'bar'), 'Any_of', '{{"baz"}}', False),
|
||
# The right‐hand side of Any_of or Not_Any_of must be a composite.
|
||
('foo', 'Any_of', '"foo"', None),
|
||
(('foo', 'bar'), 'Not_Any_of', '"baz"', None),
|
||
# A string won’t compare equal to a numeric literal.
|
||
('42', '==', '"42"', True),
|
||
('42', '==', '42', None),
|
||
# Nor can composites that mismatch in type be compared.
|
||
(('123', '456'), '==', '{{"123", "456"}}', True),
|
||
(('654', '321'), '==', '{{654, 321}}', None),
|
||
(('foo', 'bar'), 'Contains', '{{1, 2, 3}}', None),
|
||
]
|
||
|
||
def _test_cmp_with_args(self, lhs, op, rhs, outcome, rhs_is_literal=False):
|
||
# Construct a conditional ACE expression that evaluates to True if the
|
||
# two claim values are equal.
|
||
if rhs_is_literal:
|
||
self.assertIsInstance(rhs, str)
|
||
rhs = rhs.format(self=self)
|
||
expression = f'(@User.{self.claim0_id} {op} {rhs})'
|
||
else:
|
||
expression = f'(@User.{self.claim0_id} {op} @User.{self.claim1_id})'
|
||
|
||
# Create an authentication policy that will allow authentication when
|
||
# the expression is true, and a second that will deny authentication in
|
||
# the same circumstance. By observing the results of authenticating
|
||
# against each of these policies in turn, we can determine whether the
|
||
# expression evaluates to a True, False, or Unknown value.
|
||
|
||
allowed_sddl = f'O:SYD:(XA;;CR;;;WD;{expression})'
|
||
denied_sddl = f'O:SYD:(XD;;CR;;;WD;{expression})(A;;CR;;;WD)'
|
||
|
||
allowed_policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=allowed_sddl)
|
||
denied_policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=denied_sddl)
|
||
|
||
# Create a user account assigned to each policy.
|
||
allowed_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=allowed_policy)
|
||
denied_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=denied_policy)
|
||
|
||
additional_details = ()
|
||
if lhs:
|
||
additional_details += ((self.claim0_attr, lhs),)
|
||
if rhs and not rhs_is_literal:
|
||
additional_details += ((self.claim1_attr, rhs),)
|
||
|
||
# Create a computer account with the provided attribute values.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'additional_details': additional_details})
|
||
|
||
def expected_values(val):
|
||
if isinstance(val, (str, bytes)):
|
||
return val,
|
||
|
||
return val
|
||
|
||
expected_client_claims = {}
|
||
if lhs:
|
||
expected_client_claims[self.claim0_id] = {
|
||
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
||
'type': claims.CLAIM_TYPE_STRING,
|
||
'values': expected_values(lhs),
|
||
}
|
||
if rhs and not rhs_is_literal:
|
||
expected_client_claims[self.claim1_id] = {
|
||
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
||
'type': claims.CLAIM_TYPE_STRING,
|
||
'values': expected_values(rhs),
|
||
}
|
||
|
||
# Fetch the computer account’s TGT, and ensure it contains the claims.
|
||
armor_tgt = self.get_tgt(
|
||
mach_creds,
|
||
expect_client_claims=bool(expected_client_claims) or None,
|
||
expected_client_claims=expected_client_claims)
|
||
|
||
# The first or the second authentication request is expected to succeed
|
||
# if the outcome is True or False, respectively. An Unknown outcome,
|
||
# represented by None, will result in a policy error in either case.
|
||
allowed_error = 0 if outcome is True else KDC_ERR_POLICY
|
||
denied_error = 0 if outcome is False else KDC_ERR_POLICY
|
||
|
||
# Attempt to authenticate and ensure that we observe the expected
|
||
# results.
|
||
self._get_tgt(allowed_creds, armor_tgt=armor_tgt,
|
||
expected_error=allowed_error)
|
||
self._get_tgt(denied_creds, armor_tgt=armor_tgt,
|
||
expected_error=denied_error)
|
||
|
||
pac_claim_cases = [
|
||
# Test a very simple expression with various claims.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{non_empty_string}', claims.CLAIM_TYPE_STRING, ['foo bar']),
|
||
]),
|
||
], '{non_empty_string}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{zero_uint}', claims.CLAIM_TYPE_UINT64, [0]),
|
||
]),
|
||
], '{zero_uint}', False),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{nonzero_uint}', claims.CLAIM_TYPE_UINT64, [1]),
|
||
]),
|
||
], '{nonzero_uint}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{zero_uints}', claims.CLAIM_TYPE_UINT64, [0, 0]),
|
||
]),
|
||
], '{zero_uints}', KDC_ERR_GENERIC),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{zero_and_one_uint}', claims.CLAIM_TYPE_UINT64, [0, 1]),
|
||
]),
|
||
], '{zero_and_one_uint}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{one_and_zero_uint}', claims.CLAIM_TYPE_UINT64, [1, 0]),
|
||
]),
|
||
], '{one_and_zero_uint}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{zero_int}', claims.CLAIM_TYPE_INT64, [0]),
|
||
]),
|
||
], '{zero_int}', False),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{nonzero_int}', claims.CLAIM_TYPE_INT64, [1]),
|
||
]),
|
||
], '{nonzero_int}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{zero_ints}', claims.CLAIM_TYPE_INT64, [0, 0]),
|
||
]),
|
||
], '{zero_ints}', KDC_ERR_GENERIC),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{zero_and_one_int}', claims.CLAIM_TYPE_INT64, [0, 1]),
|
||
]),
|
||
], '{zero_and_one_int}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{one_and_zero_int}', claims.CLAIM_TYPE_INT64, [1, 0]),
|
||
]),
|
||
], '{one_and_zero_int}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{false_boolean}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '{false_boolean}', False),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{true_boolean}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '{true_boolean}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{false_booleans}', claims.CLAIM_TYPE_BOOLEAN, [0, 0]),
|
||
]),
|
||
], '{false_booleans}', KDC_ERR_GENERIC),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{false_and_true_boolean}', claims.CLAIM_TYPE_BOOLEAN, [0, 1]),
|
||
]),
|
||
], '{false_and_true_boolean}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{true_and_false_boolean}', claims.CLAIM_TYPE_BOOLEAN, [1, 0]),
|
||
]),
|
||
], '{true_and_false_boolean}', True),
|
||
# Test a basic comparison against a literal.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['foo bar']),
|
||
]),
|
||
], '{a} == "foo bar"', True),
|
||
# Claims can be compared against one another.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['foo bar']),
|
||
('{b}', claims.CLAIM_TYPE_STRING, ['FOO BAR']),
|
||
]),
|
||
], '{a} == {b}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{b}', claims.CLAIM_TYPE_STRING, ['FOO', 'BAR', 'BAZ']),
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['foo', 'bar', 'baz']),
|
||
]),
|
||
], '{a} != {b}', False),
|
||
# Certificate claims are also valid.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_CERTIFICATE, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['foo']),
|
||
]),
|
||
], '{a} == "foo"', True),
|
||
# Other claim source types are ignored.
|
||
([
|
||
(0, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['foo']),
|
||
]),
|
||
], '{a} == "foo"', None),
|
||
([
|
||
(3, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['foo']),
|
||
]),
|
||
], '{a} == "foo"', None),
|
||
# If multiple claims have the same ID, the *last* one takes precedence.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['this is not the value…']),
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['…nor is this…']),
|
||
]),
|
||
(claims.CLAIMS_SOURCE_TYPE_CERTIFICATE, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['…and this isn’t either.']),
|
||
]),
|
||
(claims.CLAIMS_SOURCE_TYPE_CERTIFICATE, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['here’s the actual value!']),
|
||
]),
|
||
(3, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['this is a red herring.']),
|
||
]),
|
||
], '{a} == "here’s the actual value!"', True),
|
||
# Claim values can be empty.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{empty_claim_string}', claims.CLAIM_TYPE_STRING, []),
|
||
]),
|
||
], '{empty_claim_string} != "foo bar"', None),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{empty_claim_boolean}', claims.CLAIM_TYPE_BOOLEAN, []),
|
||
]),
|
||
], 'Exists {empty_claim_boolean}', None),
|
||
# Test unsigned integer equality.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_UINT64, [42]),
|
||
]),
|
||
], '{a} == 42', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_UINT64, [0]),
|
||
]),
|
||
], '{a} == 3', False),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_UINT64, [1, 2, 3]),
|
||
]),
|
||
], '{a} == {{1, 2, 3}}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_UINT64, [4, 5, 6]),
|
||
]),
|
||
], '{a} != {{1, 2, 3}}', True),
|
||
# Test unsigned integer comparison. Ensure we don’t run into any
|
||
# integer overflow issues.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_UINT64, [1 << 32]),
|
||
]),
|
||
], '{a} > 0', True),
|
||
# Test signed integer comparisons.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_INT64, [42]),
|
||
]),
|
||
], '{a} == 42', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_INT64, [42 << 32]),
|
||
]),
|
||
], f'{{a}} == {42 << 32}', True),
|
||
# Test boolean claims. Be careful! Windows will *crash* if you send it
|
||
# claims that aren’t real booleans (not 0 or 1). I doubt Microsoft will
|
||
# consider this a security issue though.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [2]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [3]),
|
||
]),
|
||
], '{a} == {b}', (None, CRASHES_WINDOWS)),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '{a} == {b}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '{a} == 42', None),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '{a} && {b}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '{a} && {b}', False),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '{a} && {b}', False),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '{a} || {b}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '{a} || {b}', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '{a} || {b}', False),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '!({a})', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '!(!(!(!({a}))))', False),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '!({a} && {a})', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '{a} && !({b} || {b})', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '!({a}) || !({a})', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [0]),
|
||
]),
|
||
], '{a} && !({b})', None),
|
||
# Expressions containing the ‘not’ operator are occasionally evaluated
|
||
# inconsistently, as evidenced here. ‘a || !a’ evaluates to ‘unknown’…
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '{a} || !({a})', None),
|
||
# …but ‘!a || a’ — the same expression, just with the operands switched
|
||
# round — evaluates to ‘true’.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '!({a}) || {a}', True),
|
||
# This inconsistency is not observed with other boolean expressions,
|
||
# such as ‘a || a’.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '{a} || ({a} || {a})', True),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{b}', claims.CLAIM_TYPE_BOOLEAN, [1]),
|
||
]),
|
||
], '({b} || {b}) || {b}', True),
|
||
# Test a very large claim. Much larger than this, and
|
||
# conditional_ace_encode_binary() will refuse to encode the conditions.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{large_claim}', claims.CLAIM_TYPE_STRING, ['z' * 4900]),
|
||
]),
|
||
], f'{{large_claim}} == "{"z" * 4900}"', True),
|
||
# Test an even larger claim. Windows does not appear to like receiving
|
||
# a claim this large.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{larger_claim}', claims.CLAIM_TYPE_STRING, ['z' * 100000]),
|
||
]),
|
||
], '{larger_claim} > "z"', (True, CRASHES_WINDOWS)),
|
||
# Test a great number of claims. Windows does not appear to like
|
||
# receiving this many claims.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{many_claims}', claims.CLAIM_TYPE_UINT64,
|
||
list(range(0, 100000))),
|
||
]),
|
||
], '{many_claims} Any_of "99999"', (True, CRASHES_WINDOWS)),
|
||
# Test a claim with a very long name. Much larger than this, and
|
||
# conditional_ace_encode_binary() will refuse to encode the conditions.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{long_name}', claims.CLAIM_TYPE_STRING, ['a']),
|
||
]),
|
||
], '{long_name} == "a"', {'long_name': 'z' * 4900}, True),
|
||
# Test attribute name escaping.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{escaped_claim}', claims.CLAIM_TYPE_STRING, ['claim value']),
|
||
]),
|
||
], '{escaped_claim} == "claim value"',
|
||
{'escaped_claim': '(:foo:! /&/ :bar:!)'}, True),
|
||
# Test a claim whose name consists entirely of dots.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{dotty_claim}', claims.CLAIM_TYPE_STRING, ['a']),
|
||
]),
|
||
], '{dotty_claim} == "a"', {'dotty_claim': '...'}, True),
|
||
# Test a claim whose name consists of the first thousand non‐zero
|
||
# Unicode codepoints.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{1000_unicode}', claims.CLAIM_TYPE_STRING, ['a']),
|
||
]),
|
||
], '{1000_unicode} == "a"',
|
||
{'1000_unicode': ''.join(map(chr, range(1, 1001)))}, True),
|
||
# Test a claim whose name consists of some higher Unicode codepoints,
|
||
# including non‐BMP ones.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{higher_unicode}', claims.CLAIM_TYPE_STRING, ['a']),
|
||
]),
|
||
], '{higher_unicode} == "a"',
|
||
{'higher_unicode': ''.join(map(chr, range(0xfe00, 0x10800)))}, True),
|
||
# Duplicate claim values are not allowed…
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_INT64, [42, 42, 42]),
|
||
]),
|
||
], '{a} == {a}', KDC_ERR_GENERIC),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_UINT64, [42, 42]),
|
||
]),
|
||
], '{a} == {a}', KDC_ERR_GENERIC),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['foo', 'foo']),
|
||
]),
|
||
], '{a} == {a}', KDC_ERR_GENERIC),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_STRING, ['FOO', 'foo']),
|
||
]),
|
||
], '{a} == {a}', KDC_ERR_GENERIC),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{a}', claims.CLAIM_TYPE_BOOLEAN, [0, 0]),
|
||
]),
|
||
], '{a} == {a}', KDC_ERR_GENERIC),
|
||
# …but it’s OK if duplicate values are spread across multiple claim
|
||
# entries.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{dup}', claims.CLAIM_TYPE_STRING, ['foo']),
|
||
('{dup}', claims.CLAIM_TYPE_STRING, ['foo']),
|
||
]),
|
||
(claims.CLAIMS_SOURCE_TYPE_CERTIFICATE, [
|
||
('{dup}', claims.CLAIM_TYPE_UINT64, [42]),
|
||
('{dup}', claims.CLAIM_TYPE_UINT64, [42]),
|
||
]),
|
||
(claims.CLAIMS_SOURCE_TYPE_CERTIFICATE, [
|
||
('{dup}', claims.CLAIM_TYPE_STRING, ['foo']),
|
||
('{dup}', claims.CLAIM_TYPE_STRING, ['foo']),
|
||
('{dup}', claims.CLAIM_TYPE_STRING, ['foo', 'bar']),
|
||
('{dup}', claims.CLAIM_TYPE_STRING, ['foo', 'bar']),
|
||
]),
|
||
], '{dup} == {dup}', True),
|
||
# Test invalid claim types. Be careful! Windows will *crash* if you
|
||
# send it invalid claim types. I doubt Microsoft will consider this a
|
||
# security issue though.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{invalid_sid}', 5, []),
|
||
]),
|
||
], '{invalid_sid} == {invalid_sid}', (None, CRASHES_WINDOWS)),
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{invalid_octet_string}', 16, []),
|
||
]),
|
||
], '{invalid_octet_string} == {invalid_octet_string}', (None, CRASHES_WINDOWS)),
|
||
# Sending an empty string will crash Windows.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{empty_string}', claims.CLAIM_TYPE_STRING, ['']),
|
||
]),
|
||
], '{empty_string}', (None, CRASHES_WINDOWS)),
|
||
# But sending empty arrays is OK.
|
||
([
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
('{empty_array}', claims.CLAIM_TYPE_INT64, []),
|
||
('{empty_array}', claims.CLAIM_TYPE_UINT64, []),
|
||
('{empty_array}', claims.CLAIM_TYPE_BOOLEAN, []),
|
||
('{empty_array}', claims.CLAIM_TYPE_STRING, []),
|
||
]),
|
||
], '{empty_array}', None),
|
||
]
|
||
|
||
def _test_pac_claim_cmp_with_args(self,
|
||
pac_claims,
|
||
expression,
|
||
claim_map,
|
||
outcome):
|
||
self.assertIsInstance(expression, str)
|
||
|
||
try:
|
||
outcome, crashes_windows = outcome
|
||
self.assertIs(crashes_windows, CRASHES_WINDOWS)
|
||
if not self.crash_windows:
|
||
self.skipTest('test crashes Windows servers')
|
||
except TypeError:
|
||
self.assertIsNot(outcome, CRASHES_WINDOWS)
|
||
|
||
if claim_map is None:
|
||
claim_map = {}
|
||
|
||
claim_ids = {}
|
||
|
||
def get_claim_id(claim_name):
|
||
claim = claim_ids.get(claim_name)
|
||
if claim is None:
|
||
claim = claim_map.pop(claim_name, None)
|
||
if claim is None:
|
||
claim = self.get_new_username()
|
||
|
||
claim_ids[claim_name] = claim
|
||
|
||
return claim
|
||
|
||
def formatted_claim_expression(expr):
|
||
formatter = Formatter()
|
||
result = []
|
||
|
||
for literal_text, field_name, format_spec, conversion in (
|
||
formatter.parse(expr)):
|
||
self.assertFalse(format_spec,
|
||
f'format specifier ({format_spec}) should '
|
||
f'not be specified')
|
||
self.assertFalse(conversion,
|
||
f'conversion ({conversion}) should not be '
|
||
'specified')
|
||
|
||
result.append(literal_text)
|
||
|
||
if field_name is not None:
|
||
self.assertTrue(field_name,
|
||
'a field name should be specified')
|
||
|
||
claim_id = get_claim_id(field_name)
|
||
claim_id = escaped_claim_id(claim_id)
|
||
result.append(f'@User.{claim_id}')
|
||
|
||
return ''.join(result)
|
||
|
||
# Construct the conditional ACE expression.
|
||
expression = formatted_claim_expression(expression)
|
||
|
||
self.assertFalse(claim_map, 'unused claim mapping(s) remain')
|
||
|
||
# Create an authentication policy that will allow authentication when
|
||
# the expression is true, and a second that will deny authentication in
|
||
# the same circumstance. By observing the results of authenticating
|
||
# against each of these policies in turn, we can determine whether the
|
||
# expression evaluates to a True, False, or Unknown value.
|
||
|
||
allowed_sddl = f'O:SYD:(XA;;CR;;;WD;({expression}))'
|
||
denied_sddl = f'O:SYD:(XD;;CR;;;WD;({expression}))(A;;CR;;;WD)'
|
||
|
||
allowed_policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=allowed_sddl)
|
||
denied_policy = self.create_authn_policy(
|
||
enforced=True,
|
||
user_allowed_from=denied_sddl)
|
||
|
||
# Create a user account assigned to each policy.
|
||
allowed_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=allowed_policy)
|
||
denied_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=denied_policy)
|
||
|
||
# Create a computer account.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER)
|
||
|
||
def expected_values(val):
|
||
if isinstance(val, (str, bytes)):
|
||
return val,
|
||
|
||
return val
|
||
|
||
# Fetch the computer account’s TGT.
|
||
armor_tgt = self.get_tgt(mach_creds)
|
||
|
||
if pac_claims:
|
||
# Replace the claims in the PAC with our own.
|
||
armor_tgt = self.modified_ticket(
|
||
armor_tgt,
|
||
modify_pac_fn=partial(self.set_pac_claims,
|
||
client_claims=pac_claims,
|
||
claim_ids=claim_ids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# The first or the second authentication request is expected to succeed
|
||
# if the outcome is True or False, respectively. An Unknown outcome,
|
||
# represented by None, will result in a policy error in either case.
|
||
if outcome is True:
|
||
allowed_error, denied_error = 0, KDC_ERR_POLICY
|
||
elif outcome is False:
|
||
allowed_error, denied_error = KDC_ERR_POLICY, 0
|
||
elif outcome is None:
|
||
allowed_error, denied_error = KDC_ERR_POLICY, KDC_ERR_POLICY
|
||
else:
|
||
allowed_error, denied_error = outcome, outcome
|
||
|
||
# Attempt to authenticate and ensure that we observe the expected
|
||
# results.
|
||
self._get_tgt(allowed_creds, armor_tgt=armor_tgt,
|
||
expected_error=allowed_error)
|
||
self._get_tgt(denied_creds, armor_tgt=armor_tgt,
|
||
expected_error=denied_error)
|
||
|
||
def test_rbcd_without_aa_asserted_identity(self):
|
||
service_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({self.aa_asserted_identity})',
|
||
service_sids=service_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({self.aa_asserted_identity})',
|
||
service_sids=service_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_with_aa_asserted_identity(self):
|
||
service_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = service_sids | {
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({self.aa_asserted_identity})',
|
||
service_sids=service_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({self.aa_asserted_identity})',
|
||
service_sids=service_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_rbcd_without_service_asserted_identity(self):
|
||
service_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({self.service_asserted_identity})',
|
||
service_sids=service_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({self.service_asserted_identity})',
|
||
service_sids=service_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_with_service_asserted_identity(self):
|
||
service_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
# The Application Authority Asserted Identity SID has replaced the
|
||
# Service Asserted Identity SID.
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({self.service_asserted_identity})',
|
||
service_sids=service_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({self.service_asserted_identity})',
|
||
service_sids=service_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_rbcd_without_claims_valid(self):
|
||
service_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
service_sids=service_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
service_sids=service_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_with_claims_valid(self):
|
||
service_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = service_sids | {
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
service_sids=service_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
service_sids=service_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_rbcd_without_compounded_authentication(self):
|
||
service_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
service_sids=service_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
service_sids=service_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_with_compounded_authentication(self):
|
||
service_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
service_sids=service_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
service_sids=service_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_rbcd_client_without_aa_asserted_identity(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({self.aa_asserted_identity})',
|
||
client_sids=client_sids)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({self.aa_asserted_identity})',
|
||
client_sids=client_sids)
|
||
|
||
def test_rbcd_client_with_aa_asserted_identity(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({self.aa_asserted_identity})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({self.aa_asserted_identity})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_rbcd_client_without_service_asserted_identity(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({self.service_asserted_identity})',
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({self.service_asserted_identity})',
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_client_with_service_asserted_identity(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Not_Member_of SID({self.service_asserted_identity})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
self._rbcd(target_policy=f'Not_Member_of SID({self.service_asserted_identity})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_rbcd_client_without_claims_valid(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_sids=client_sids)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_sids=client_sids)
|
||
|
||
def test_rbcd_client_with_claims_valid(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_rbcd_client_without_compounded_authentication(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_client_with_compounded_authentication(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Not_Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
self._rbcd(target_policy=f'Not_Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_rbcd_device_without_aa_asserted_identity(self):
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Device_Member_of SID({self.aa_asserted_identity})',
|
||
device_sids=device_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Device_Member_of SID({self.aa_asserted_identity})',
|
||
device_sids=device_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_device_without_aa_asserted_identity_not_memberof(self):
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Not_Device_Member_of SID({self.aa_asserted_identity})',
|
||
device_sids=device_sids)
|
||
|
||
self._rbcd(target_policy=f'Not_Device_Member_of SID({self.aa_asserted_identity})',
|
||
device_sids=device_sids)
|
||
|
||
def test_rbcd_device_with_aa_asserted_identity(self):
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Device_Member_of SID({self.aa_asserted_identity})',
|
||
device_sids=device_sids)
|
||
|
||
self._rbcd(target_policy=f'Device_Member_of SID({self.aa_asserted_identity})',
|
||
device_sids=device_sids)
|
||
|
||
def test_rbcd_device_without_service_asserted_identity(self):
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Device_Member_of SID({self.service_asserted_identity})',
|
||
device_sids=device_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Device_Member_of SID({self.service_asserted_identity})',
|
||
device_sids=device_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_device_with_service_asserted_identity(self):
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Device_Member_of SID({self.service_asserted_identity})',
|
||
device_sids=device_sids)
|
||
|
||
self._rbcd(target_policy=f'Device_Member_of SID({self.service_asserted_identity})',
|
||
device_sids=device_sids)
|
||
|
||
def test_rbcd_device_without_claims_valid(self):
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Device_Member_of SID({security.SID_CLAIMS_VALID})',
|
||
device_sids=device_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Device_Member_of SID({security.SID_CLAIMS_VALID})',
|
||
device_sids=device_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_device_with_claims_valid(self):
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Device_Member_of SID({security.SID_CLAIMS_VALID})',
|
||
device_sids=device_sids)
|
||
|
||
self._rbcd(target_policy=f'Device_Member_of SID({security.SID_CLAIMS_VALID})',
|
||
device_sids=device_sids)
|
||
|
||
def test_rbcd_device_without_compounded_authentication(self):
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._rbcd(f'Device_Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
device_sids=device_sids,
|
||
code=KDC_ERR_BADOPTION,
|
||
status=ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
edata=self.expect_padata_outer)
|
||
|
||
self._rbcd(target_policy=f'Device_Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
device_sids=device_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_rbcd_device_with_compounded_authentication(self):
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._rbcd(f'Device_Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
device_sids=device_sids)
|
||
|
||
self._rbcd(target_policy=f'Device_Member_of SID({security.SID_COMPOUNDED_AUTHENTICATION})',
|
||
device_sids=device_sids)
|
||
|
||
def test_rbcd(self):
|
||
self._rbcd('Member_of SID({service_sid})')
|
||
|
||
def test_rbcd_device_from_rodc(self):
|
||
self._rbcd('Member_of SID({service_sid})',
|
||
device_from_rodc=True,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def test_rbcd_service_from_rodc(self):
|
||
self._rbcd('Member_of SID({service_sid})',
|
||
service_from_rodc=True)
|
||
|
||
def test_rbcd_device_and_service_from_rodc(self):
|
||
self._rbcd('Member_of SID({service_sid})',
|
||
service_from_rodc=True,
|
||
device_from_rodc=True,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def test_rbcd_client_from_rodc(self):
|
||
self._rbcd('Member_of SID({service_sid})',
|
||
client_from_rodc=True)
|
||
|
||
def test_rbcd_client_and_device_from_rodc(self):
|
||
self._rbcd('Member_of SID({service_sid})',
|
||
client_from_rodc=True,
|
||
device_from_rodc=True,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def test_rbcd_client_and_service_from_rodc(self):
|
||
self._rbcd('Member_of SID({service_sid})',
|
||
client_from_rodc=True,
|
||
service_from_rodc=True)
|
||
|
||
def test_rbcd_all_from_rodc(self):
|
||
self._rbcd('Member_of SID({service_sid})',
|
||
client_from_rodc=True,
|
||
service_from_rodc=True,
|
||
device_from_rodc=True,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def test_delegating_proxy_in_world_group_rbcd(self):
|
||
self._check_delegating_proxy_in_group_rbcd(security.SID_WORLD)
|
||
|
||
def test_delegating_proxy_in_network_group_rbcd(self):
|
||
self._check_delegating_proxy_not_in_group_rbcd(security.SID_NT_NETWORK)
|
||
|
||
def test_delegating_proxy_in_authenticated_users_rbcd(self):
|
||
self._check_delegating_proxy_in_group_rbcd(
|
||
security.SID_NT_AUTHENTICATED_USERS)
|
||
|
||
def test_delegating_proxy_in_aa_asserted_identity_rbcd(self):
|
||
self._check_delegating_proxy_in_group_rbcd(
|
||
security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY)
|
||
|
||
def test_delegating_proxy_in_service_asserted_identity_rbcd(self):
|
||
self._check_delegating_proxy_not_in_group_rbcd(
|
||
security.SID_SERVICE_ASSERTED_IDENTITY)
|
||
|
||
def test_delegating_proxy_in_compounded_authentication_rbcd(self):
|
||
self._check_delegating_proxy_not_in_group_rbcd(
|
||
security.SID_COMPOUNDED_AUTHENTICATION)
|
||
|
||
def test_delegating_proxy_in_claims_valid_rbcd(self):
|
||
self._check_delegating_proxy_in_group_rbcd(security.SID_CLAIMS_VALID)
|
||
|
||
def test_device_in_world_group_rbcd(self):
|
||
self._check_device_in_group_rbcd(security.SID_WORLD)
|
||
|
||
def test_device_in_network_group_rbcd(self):
|
||
self._check_device_not_in_group_rbcd(security.SID_NT_NETWORK)
|
||
|
||
def test_device_in_authenticated_users_rbcd(self):
|
||
self._check_device_in_group_rbcd(security.SID_NT_AUTHENTICATED_USERS)
|
||
|
||
def test_device_in_aa_asserted_identity_rbcd(self):
|
||
self._check_device_in_group_rbcd(
|
||
security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY)
|
||
|
||
def test_device_in_service_asserted_identity_rbcd(self):
|
||
self._check_device_not_in_group_rbcd(
|
||
security.SID_SERVICE_ASSERTED_IDENTITY)
|
||
|
||
def test_device_in_compounded_authentication_rbcd(self):
|
||
self._check_device_not_in_group_rbcd(
|
||
security.SID_COMPOUNDED_AUTHENTICATION)
|
||
|
||
def test_device_in_claims_valid_rbcd(self):
|
||
self._check_device_in_group_rbcd(security.SID_CLAIMS_VALID)
|
||
|
||
def _check_delegating_proxy_in_group_rbcd(self, group):
|
||
self._check_membership_rbcd(group, expect_in_group=True)
|
||
|
||
def _check_delegating_proxy_not_in_group_rbcd(self, group):
|
||
self._check_membership_rbcd(group, expect_in_group=False)
|
||
|
||
def _check_device_in_group_rbcd(self, group):
|
||
self._check_membership_rbcd(group, expect_in_group=True, device=True)
|
||
|
||
def _check_device_not_in_group_rbcd(self, group):
|
||
self._check_membership_rbcd(group, expect_in_group=False, device=True)
|
||
|
||
def _check_membership_rbcd(self,
|
||
group,
|
||
*,
|
||
expect_in_group,
|
||
device=False):
|
||
"""Test that authentication succeeds or fails when the delegating proxy
|
||
is required to belong to a certain group.
|
||
"""
|
||
|
||
sddl_op = 'Device_Member_of' if device else 'Member_of'
|
||
|
||
samdb = self.get_samdb()
|
||
functional_level = self.get_domain_functional_level(samdb)
|
||
|
||
if functional_level < dsdb.DS_DOMAIN_FUNCTION_2008:
|
||
self.skipTest('RBCD requires FL2008')
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
|
||
client_tkt_options = 'forwardable'
|
||
expected_flags = krb5_asn1.TicketFlags(client_tkt_options)
|
||
client_tgt = self.get_tgt(client_creds,
|
||
kdc_options=client_tkt_options,
|
||
expected_flags=expected_flags)
|
||
|
||
client_sid = client_creds.get_sid()
|
||
|
||
client_username = client_creds.get_username()
|
||
client_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
|
||
names=[client_username])
|
||
|
||
service_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'service'})
|
||
service_tgt = self.get_tgt(service_creds)
|
||
|
||
client_service_tkt = self.get_service_ticket(
|
||
client_tgt,
|
||
service_creds,
|
||
kdc_options=client_tkt_options,
|
||
expected_flags=expected_flags)
|
||
|
||
domain_sid_str = samdb.get_domain_sid()
|
||
domain_sid = security.dom_sid(domain_sid_str)
|
||
|
||
# Require the principal to belong to a certain group.
|
||
in_group_sddl = self.allow_if(f'{sddl_op} {{SID({group})}}')
|
||
in_group_descriptor = security.descriptor.from_sddl(in_group_sddl,
|
||
domain_sid)
|
||
|
||
# Create a target account that allows RBCD if the principal belongs to
|
||
# the group.
|
||
in_group_target_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={
|
||
'additional_details': (
|
||
('msDS-AllowedToActOnBehalfOfOtherIdentity',
|
||
ndr_pack(in_group_descriptor)),
|
||
),
|
||
})
|
||
|
||
kdc_options = str(krb5_asn1.KDCOptions('cname-in-addl-tkt'))
|
||
|
||
in_group_target_key = self.TicketDecryptionKey_from_creds(
|
||
in_group_target_creds)
|
||
in_group_target_etypes = in_group_target_creds.tgs_supported_enctypes
|
||
|
||
service_name = service_creds.get_username()
|
||
if service_name[-1] == '$':
|
||
service_name = service_name[:-1]
|
||
expected_transited_services = [
|
||
f'host/{service_name}@{service_creds.get_realm()}'
|
||
]
|
||
|
||
pac_options = '1001' # supports claims, RBCD
|
||
|
||
success_result = 0, None, None
|
||
failure_result = (
|
||
KDC_ERR_BADOPTION,
|
||
ntstatus.NT_STATUS_UNSUCCESSFUL,
|
||
self.expect_padata_outer,
|
||
)
|
||
|
||
code, status, expect_edata = (success_result if expect_in_group
|
||
else failure_result)
|
||
|
||
# Test whether obtaining a service ticket with RBCD is allowed.
|
||
self._tgs_req(service_tgt,
|
||
code,
|
||
service_creds,
|
||
in_group_target_creds,
|
||
armor_tgt=mach_tgt,
|
||
kdc_options=kdc_options,
|
||
pac_options=pac_options,
|
||
expected_cname=client_cname,
|
||
expected_account_name=client_username,
|
||
additional_ticket=client_service_tkt,
|
||
decryption_key=in_group_target_key,
|
||
expected_sid=client_sid,
|
||
expected_supported_etypes=in_group_target_etypes,
|
||
expected_proxy_target=in_group_target_creds.get_spn(),
|
||
expected_transited_services=expected_transited_services,
|
||
expected_status=status,
|
||
expect_edata=expect_edata)
|
||
|
||
effective_client_creds = service_creds if code else client_creds
|
||
self.check_tgs_log(effective_client_creds, in_group_target_creds,
|
||
checked_creds=service_creds,
|
||
status=status)
|
||
|
||
# Require the principal not to belong to a certain group.
|
||
not_in_group_sddl = self.allow_if(f'Not_{sddl_op} {{SID({group})}}')
|
||
not_in_group_descriptor = security.descriptor.from_sddl(
|
||
not_in_group_sddl, domain_sid)
|
||
|
||
# Create a target account that allows RBCD if the principal does not
|
||
# belong to the group.
|
||
not_in_group_target_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={
|
||
'additional_details': (
|
||
('msDS-AllowedToActOnBehalfOfOtherIdentity',
|
||
ndr_pack(not_in_group_descriptor)),
|
||
),
|
||
})
|
||
|
||
not_in_group_target_key = self.TicketDecryptionKey_from_creds(
|
||
not_in_group_target_creds)
|
||
not_in_group_target_etypes = (
|
||
not_in_group_target_creds.tgs_supported_enctypes)
|
||
|
||
code, status, expect_edata = (failure_result if expect_in_group
|
||
else success_result)
|
||
|
||
# Test whether obtaining a service ticket with RBCD is allowed.
|
||
self._tgs_req(service_tgt,
|
||
code,
|
||
service_creds,
|
||
not_in_group_target_creds,
|
||
armor_tgt=mach_tgt,
|
||
kdc_options=kdc_options,
|
||
pac_options=pac_options,
|
||
expected_cname=client_cname,
|
||
expected_account_name=client_username,
|
||
additional_ticket=client_service_tkt,
|
||
decryption_key=not_in_group_target_key,
|
||
expected_sid=client_sid,
|
||
expected_supported_etypes=not_in_group_target_etypes,
|
||
expected_proxy_target=not_in_group_target_creds.get_spn(),
|
||
expected_transited_services=expected_transited_services,
|
||
expected_status=status,
|
||
expect_edata=expect_edata)
|
||
|
||
effective_client_creds = service_creds if code else client_creds
|
||
self.check_tgs_log(effective_client_creds, not_in_group_target_creds,
|
||
checked_creds=service_creds,
|
||
status=status)
|
||
|
||
def _rbcd(self,
|
||
rbcd_expression=None,
|
||
*,
|
||
code=0,
|
||
status=None,
|
||
event=AuditEvent.OK,
|
||
reason=AuditReason.NONE,
|
||
edata=False,
|
||
target_policy=None,
|
||
client_from_rodc=False,
|
||
service_from_rodc=False,
|
||
device_from_rodc=False,
|
||
client_sids=None,
|
||
client_claims=None,
|
||
service_sids=None,
|
||
service_claims=None,
|
||
device_sids=None,
|
||
device_claims=None,
|
||
expected_groups=None,
|
||
expected_device_groups=None,
|
||
expected_claims=None):
|
||
try:
|
||
code, crashes_windows = code
|
||
self.assertIs(crashes_windows, CRASHES_WINDOWS)
|
||
if not self.crash_windows:
|
||
self.skipTest('test crashes Windows servers')
|
||
except TypeError:
|
||
self.assertIsNot(code, CRASHES_WINDOWS)
|
||
|
||
samdb = self.get_samdb()
|
||
functional_level = self.get_domain_functional_level(samdb)
|
||
|
||
if functional_level < dsdb.DS_DOMAIN_FUNCTION_2008:
|
||
self.skipTest('RBCD requires FL2008')
|
||
|
||
domain_sid_str = samdb.get_domain_sid()
|
||
domain_sid = security.dom_sid(domain_sid_str)
|
||
|
||
client_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.USER,
|
||
opts={
|
||
'allowed_replication_mock': client_from_rodc,
|
||
'revealed_to_mock_rodc': client_from_rodc,
|
||
})
|
||
client_sid = client_creds.get_sid()
|
||
|
||
client_username = client_creds.get_username()
|
||
client_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
|
||
names=[client_username])
|
||
|
||
client_tkt_options = 'forwardable'
|
||
expected_flags = krb5_asn1.TicketFlags(client_tkt_options)
|
||
|
||
checksum_key = self.get_krbtgt_checksum_key()
|
||
|
||
if client_from_rodc or service_from_rodc or device_from_rodc:
|
||
rodc_krbtgt_creds = self.get_mock_rodc_krbtgt_creds()
|
||
rodc_krbtgt_key = self.TicketDecryptionKey_from_creds(rodc_krbtgt_creds)
|
||
rodc_checksum_key = {
|
||
krb5pac.PAC_TYPE_KDC_CHECKSUM: rodc_krbtgt_key,
|
||
}
|
||
|
||
client_tgt = self.get_tgt(client_creds,
|
||
kdc_options=client_tkt_options,
|
||
expected_flags=expected_flags)
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={
|
||
'allowed_replication_mock': device_from_rodc,
|
||
'revealed_to_mock_rodc': device_from_rodc,
|
||
})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
device_modify_pac_fn = []
|
||
if device_sids is not None:
|
||
device_modify_pac_fn.append(partial(self.set_pac_sids,
|
||
new_sids=device_sids))
|
||
if device_claims is not None:
|
||
device_modify_pac_fn.append(partial(self.set_pac_claims,
|
||
client_claims=device_claims))
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=device_modify_pac_fn,
|
||
new_ticket_key=rodc_krbtgt_key if device_from_rodc else None,
|
||
checksum_keys=rodc_checksum_key if device_from_rodc else checksum_key)
|
||
|
||
service_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={
|
||
'id': 1,
|
||
'allowed_replication_mock': service_from_rodc,
|
||
'revealed_to_mock_rodc': service_from_rodc,
|
||
})
|
||
service_tgt = self.get_tgt(service_creds)
|
||
|
||
service_modify_pac_fn = []
|
||
if service_sids is not None:
|
||
service_modify_pac_fn.append(partial(self.set_pac_sids,
|
||
new_sids=service_sids))
|
||
if service_claims is not None:
|
||
service_modify_pac_fn.append(partial(self.set_pac_claims,
|
||
client_claims=service_claims))
|
||
service_tgt = self.modified_ticket(
|
||
service_tgt,
|
||
modify_pac_fn=service_modify_pac_fn,
|
||
new_ticket_key=rodc_krbtgt_key if service_from_rodc else None,
|
||
checksum_keys=rodc_checksum_key if service_from_rodc else checksum_key)
|
||
|
||
if target_policy is None:
|
||
policy = None
|
||
assigned_policy = None
|
||
else:
|
||
sddl = f'O:SYD:(XA;;CR;;;WD;({target_policy.format(service_sid=service_creds.get_sid())}))'
|
||
policy = self.create_authn_policy(enforced=True,
|
||
computer_allowed_to=sddl)
|
||
assigned_policy = str(policy.dn)
|
||
|
||
if rbcd_expression is not None:
|
||
sddl = f'O:SYD:(XA;;CR;;;WD;({rbcd_expression.format(service_sid=service_creds.get_sid())}))'
|
||
else:
|
||
sddl = 'O:SYD:(A;;CR;;;WD)'
|
||
descriptor = security.descriptor.from_sddl(sddl, domain_sid)
|
||
descriptor = ndr_pack(descriptor)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={
|
||
'assigned_policy': assigned_policy,
|
||
'additional_details': (
|
||
('msDS-AllowedToActOnBehalfOfOtherIdentity', descriptor),
|
||
),
|
||
})
|
||
|
||
client_service_tkt = self.get_service_ticket(
|
||
client_tgt,
|
||
service_creds,
|
||
kdc_options=client_tkt_options,
|
||
expected_flags=expected_flags)
|
||
client_modify_pac_fn = []
|
||
if client_sids is not None:
|
||
client_modify_pac_fn.append(partial(self.set_pac_sids,
|
||
new_sids=client_sids))
|
||
if client_claims is not None:
|
||
client_modify_pac_fn.append(partial(self.set_pac_claims,
|
||
client_claims=client_claims))
|
||
client_service_tkt = self.modified_ticket(client_service_tkt,
|
||
modify_pac_fn=client_modify_pac_fn,
|
||
checksum_keys=rodc_checksum_key if client_from_rodc else checksum_key)
|
||
|
||
kdc_options = str(krb5_asn1.KDCOptions('cname-in-addl-tkt'))
|
||
|
||
target_decryption_key = self.TicketDecryptionKey_from_creds(
|
||
target_creds)
|
||
target_etypes = target_creds.tgs_supported_enctypes
|
||
|
||
service_name = service_creds.get_username()
|
||
if service_name[-1] == '$':
|
||
service_name = service_name[:-1]
|
||
expected_transited_services = [
|
||
f'host/{service_name}@{service_creds.get_realm()}'
|
||
]
|
||
|
||
expected_groups = self.map_sids(expected_groups, None, domain_sid_str)
|
||
expected_device_groups = self.map_sids(expected_device_groups, None, domain_sid_str)
|
||
|
||
# Show that obtaining a service ticket with RBCD is allowed.
|
||
self._tgs_req(service_tgt, code, service_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
kdc_options=kdc_options,
|
||
pac_options='1001', # supports claims, RBCD
|
||
expected_cname=client_cname,
|
||
expected_account_name=client_username,
|
||
additional_ticket=client_service_tkt,
|
||
decryption_key=target_decryption_key,
|
||
expected_sid=client_sid,
|
||
expected_groups=expected_groups,
|
||
expect_device_info=bool(expected_device_groups) or None,
|
||
expected_device_domain_sid=domain_sid_str,
|
||
expected_device_groups=expected_device_groups,
|
||
expect_client_claims=bool(expected_claims) or None,
|
||
expected_client_claims=expected_claims,
|
||
expected_supported_etypes=target_etypes,
|
||
expected_proxy_target=target_creds.get_spn(),
|
||
expected_transited_services=expected_transited_services,
|
||
expected_status=status,
|
||
expect_edata=edata)
|
||
|
||
if code:
|
||
effective_client_creds = service_creds
|
||
else:
|
||
effective_client_creds = client_creds
|
||
|
||
self.check_tgs_log(effective_client_creds, target_creds,
|
||
policy=policy,
|
||
checked_creds=service_creds,
|
||
status=status,
|
||
event=event,
|
||
reason=reason)
|
||
|
||
def test_tgs_claims_valid_missing(self):
|
||
"""Test that the Claims Valid SID is not added to the PAC when
|
||
performing a TGS‐REQ."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_claims_valid_missing_from_rodc(self):
|
||
"""Test that the Claims Valid SID *is* added to the PAC when
|
||
performing a TGS‐REQ with an RODC‐issued TGT."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = client_sids | {
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_tgs_aa_asserted_identity(self):
|
||
"""Test performing a TGS‐REQ with the Authentication Identity Asserted
|
||
Identity SID present."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_aa_asserted_identity_no_attrs(self):
|
||
"""Test performing a TGS‐REQ with the Authentication Identity Asserted
|
||
Identity SID present, albeit without any attributes."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
# Put the Asserted Identity SID in the PAC without any flags set.
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, 0),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_aa_asserted_identity_from_rodc(self):
|
||
"""Test that the Authentication Identity Asserted Identity SID in an
|
||
RODC‐issued PAC is preserved when performing a TGS‐REQ."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_aa_asserted_identity_from_rodc_no_attrs_from_rodc(self):
|
||
"""Test that the Authentication Identity Asserted Identity SID without
|
||
attributes in an RODC‐issued PAC is preserved when performing a
|
||
TGS‐REQ."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
# Put the Asserted Identity SID in the PAC without any flags set.
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, 0),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
# The SID in the resulting PAC has the default attributes.
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_tgs_compound_authentication(self):
|
||
"""Test performing a TGS‐REQ with the Compounded Authentication SID
|
||
present."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_compound_authentication_from_rodc(self):
|
||
"""Test that the Compounded Authentication SID in an
|
||
RODC‐issued PAC is not preserved when performing a TGS‐REQ."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_tgs_asserted_identity_missing(self):
|
||
"""Test that the Authentication Identity Asserted Identity SID is not
|
||
added to the PAC when performing a TGS‐REQ."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_asserted_identity_missing_from_rodc(self):
|
||
"""Test that the Authentication Identity Asserted Identity SID is not
|
||
added to an RODC‐issued PAC when performing a TGS‐REQ."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_service_asserted_identity(self):
|
||
"""Test performing a TGS‐REQ with the Service Asserted Identity SID
|
||
present."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_service_asserted_identity_from_rodc(self):
|
||
"""Test that the Service Asserted Identity SID in an
|
||
RODC‐issued PAC is not preserved when performing a TGS‐REQ."""
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
# Don’t expect the Service Asserted Identity SID.
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(use_fast=False,
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_tgs_without_aa_asserted_identity(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.aa_asserted_identity})',
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_without_aa_asserted_identity_client_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.aa_asserted_identity})',
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_without_aa_asserted_identity_device_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.aa_asserted_identity})',
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
code=(KDC_ERR_POLICY, CRASHES_WINDOWS),
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_without_aa_asserted_identity_both_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.aa_asserted_identity})',
|
||
client_from_rodc=True,
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
code=(KDC_ERR_POLICY, CRASHES_WINDOWS),
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_with_aa_asserted_identity(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.aa_asserted_identity})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_with_aa_asserted_identity_client_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = client_sids | {
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.aa_asserted_identity})',
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_tgs_with_aa_asserted_identity_device_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.aa_asserted_identity})',
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def test_tgs_with_aa_asserted_identity_both_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
expected_groups = client_sids | {
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.aa_asserted_identity})',
|
||
client_from_rodc=True,
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=expected_groups,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def test_tgs_without_service_asserted_identity(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.service_asserted_identity})',
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_without_service_asserted_identity_client_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.service_asserted_identity})',
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_without_service_asserted_identity_device_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.service_asserted_identity})',
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
code=(KDC_ERR_POLICY, CRASHES_WINDOWS),
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_without_service_asserted_identity_both_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.service_asserted_identity})',
|
||
client_from_rodc=True,
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
code=(KDC_ERR_POLICY, CRASHES_WINDOWS),
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_with_service_asserted_identity(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.service_asserted_identity})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_with_service_asserted_identity_client_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.service_asserted_identity})',
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_with_service_asserted_identity_device_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.service_asserted_identity})',
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def test_tgs_with_service_asserted_identity_both_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({self.service_asserted_identity})',
|
||
client_from_rodc=True,
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
code=(KDC_ERR_POLICY, CRASHES_WINDOWS),
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_without_claims_valid(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_sids=client_sids,
|
||
code=KDC_ERR_POLICY,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_without_claims_valid_client_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
expected_groups = client_sids | {
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=expected_groups)
|
||
|
||
def test_tgs_without_claims_valid_device_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
code=(KDC_ERR_POLICY, CRASHES_WINDOWS),
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
edata=self.expect_padata_outer)
|
||
|
||
def test_tgs_without_claims_valid_both_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
expected_groups = client_sids | {
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_from_rodc=True,
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=expected_groups,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def test_tgs_with_claims_valid(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_with_claims_valid_client_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids)
|
||
|
||
def test_tgs_with_claims_valid_device_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def test_tgs_with_claims_valid_both_from_rodc(self):
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
self._tgs(f'Member_of SID({security.SID_CLAIMS_VALID})',
|
||
client_from_rodc=True,
|
||
device_from_rodc=True,
|
||
client_sids=client_sids,
|
||
expected_groups=client_sids,
|
||
code=(0, CRASHES_WINDOWS))
|
||
|
||
def _tgs(self,
|
||
target_policy=None,
|
||
*,
|
||
code=0,
|
||
event=AuditEvent.OK,
|
||
reason=AuditReason.NONE,
|
||
status=None,
|
||
edata=False,
|
||
use_fast=True,
|
||
client_from_rodc=None,
|
||
device_from_rodc=None,
|
||
client_sids=None,
|
||
client_claims=None,
|
||
device_sids=None,
|
||
device_claims=None,
|
||
expected_groups=None,
|
||
expected_device_groups=None,
|
||
expected_claims=None):
|
||
try:
|
||
code, crashes_windows = code
|
||
self.assertIs(crashes_windows, CRASHES_WINDOWS)
|
||
if not self.crash_windows:
|
||
self.skipTest('test crashes Windows servers')
|
||
except TypeError:
|
||
self.assertIsNot(code, CRASHES_WINDOWS)
|
||
|
||
if not use_fast:
|
||
self.assertIsNone(device_from_rodc)
|
||
self.assertIsNone(device_sids)
|
||
self.assertIsNone(device_claims)
|
||
self.assertIsNone(expected_device_groups)
|
||
|
||
if client_from_rodc is None:
|
||
client_from_rodc = False
|
||
|
||
if device_from_rodc is None:
|
||
device_from_rodc = False
|
||
|
||
client_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.USER,
|
||
opts={
|
||
'allowed_replication_mock': client_from_rodc,
|
||
'revealed_to_mock_rodc': client_from_rodc,
|
||
})
|
||
client_sid = client_creds.get_sid()
|
||
|
||
client_username = client_creds.get_username()
|
||
client_cname = self.PrincipalName_create(name_type=NT_PRINCIPAL,
|
||
names=[client_username])
|
||
|
||
client_tkt_options = 'forwardable'
|
||
expected_flags = krb5_asn1.TicketFlags(client_tkt_options)
|
||
|
||
checksum_key = self.get_krbtgt_checksum_key()
|
||
|
||
if client_from_rodc or device_from_rodc:
|
||
rodc_krbtgt_creds = self.get_mock_rodc_krbtgt_creds()
|
||
rodc_krbtgt_key = self.TicketDecryptionKey_from_creds(rodc_krbtgt_creds)
|
||
rodc_checksum_key = {
|
||
krb5pac.PAC_TYPE_KDC_CHECKSUM: rodc_krbtgt_key,
|
||
}
|
||
|
||
client_tgt = self.get_tgt(client_creds,
|
||
kdc_options=client_tkt_options,
|
||
expected_flags=expected_flags)
|
||
|
||
client_modify_pac_fn = []
|
||
if client_sids is not None:
|
||
client_modify_pac_fn.append(partial(self.set_pac_sids,
|
||
new_sids=client_sids))
|
||
if client_claims is not None:
|
||
client_modify_pac_fn.append(partial(self.set_pac_claims,
|
||
client_claims=client_claims))
|
||
client_tgt = self.modified_ticket(
|
||
client_tgt,
|
||
modify_pac_fn=client_modify_pac_fn,
|
||
new_ticket_key=rodc_krbtgt_key if client_from_rodc else None,
|
||
checksum_keys=rodc_checksum_key if client_from_rodc else checksum_key)
|
||
|
||
if use_fast:
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={
|
||
'allowed_replication_mock': device_from_rodc,
|
||
'revealed_to_mock_rodc': device_from_rodc,
|
||
})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
device_modify_pac_fn = []
|
||
if device_sids is not None:
|
||
device_modify_pac_fn.append(partial(self.set_pac_sids,
|
||
new_sids=device_sids))
|
||
if device_claims is not None:
|
||
device_modify_pac_fn.append(partial(self.set_pac_claims,
|
||
client_claims=device_claims))
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=device_modify_pac_fn,
|
||
new_ticket_key=rodc_krbtgt_key if device_from_rodc else None,
|
||
checksum_keys=rodc_checksum_key if device_from_rodc else checksum_key)
|
||
else:
|
||
mach_tgt = None
|
||
|
||
if target_policy is None:
|
||
policy = None
|
||
assigned_policy = None
|
||
else:
|
||
sddl = f'O:SYD:(XA;;CR;;;WD;({target_policy.format(client_sid=client_creds.get_sid())}))'
|
||
policy = self.create_authn_policy(enforced=True,
|
||
computer_allowed_to=sddl)
|
||
assigned_policy = str(policy.dn)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'assigned_policy': assigned_policy})
|
||
|
||
target_decryption_key = self.TicketDecryptionKey_from_creds(
|
||
target_creds)
|
||
target_etypes = target_creds.tgs_supported_enctypes
|
||
|
||
samdb = self.get_samdb()
|
||
domain_sid_str = samdb.get_domain_sid()
|
||
|
||
expected_groups = self.map_sids(expected_groups, None, domain_sid_str)
|
||
expected_device_groups = self.map_sids(expected_device_groups, None, domain_sid_str)
|
||
|
||
# Show that obtaining a service ticket is allowed.
|
||
self._tgs_req(client_tgt, code, client_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
expected_cname=client_cname,
|
||
expected_account_name=client_username,
|
||
decryption_key=target_decryption_key,
|
||
expected_sid=client_sid,
|
||
expected_groups=expected_groups,
|
||
expect_device_info=bool(expected_device_groups) or None,
|
||
expected_device_domain_sid=domain_sid_str,
|
||
expected_device_groups=expected_device_groups,
|
||
expect_client_claims=bool(expected_claims) or None,
|
||
expected_client_claims=expected_claims,
|
||
expected_supported_etypes=target_etypes,
|
||
expected_status=status,
|
||
expect_edata=edata)
|
||
|
||
self.check_tgs_log(client_creds, target_creds,
|
||
policy=policy,
|
||
checked_creds=client_creds,
|
||
status=status,
|
||
event=event,
|
||
reason=reason)
|
||
|
||
def test_conditional_ace_allowed_from_user_deny(self):
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER)
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that explicitly denies the machine
|
||
# account for a user.
|
||
allowed = 'O:SYD:(A;;CR;;;WD)'
|
||
denied = f'O:SYD:(XD;;CR;;;{mach_creds.get_sid()};(abc))'
|
||
policy = self.create_authn_policy(enforced=True,
|
||
user_allowed_from=denied,
|
||
service_allowed_from=allowed)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=policy)
|
||
|
||
# Show that we get a policy error when trying to authenticate.
|
||
self._get_tgt(client_creds, armor_tgt=mach_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
self.check_as_log(
|
||
client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=policy,
|
||
client_policy_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_DEVICE_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
status=ntstatus.NT_STATUS_INVALID_WORKSTATION)
|
||
|
||
|
||
class DeviceRestrictionTests(ConditionalAceBaseTests):
|
||
def test_pac_groups_not_present(self):
|
||
"""Test that authentication fails if the device does not belong to some
|
||
required groups.
|
||
"""
|
||
|
||
required_sids = {
|
||
('S-1-2-3-4', SidType.EXTRA_SID, self.default_attrs),
|
||
('S-1-9-8-7', SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that requires the device to belong to
|
||
# certain groups.
|
||
client_policy_sddl = self.allow_if(
|
||
f'Member_of {self.sddl_array_from_sids(required_sids)}')
|
||
client_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
# Show that authentication fails.
|
||
self._armored_as_req(client_creds,
|
||
self.get_krbtgt_creds(),
|
||
mach_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
self.check_as_log(
|
||
client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy,
|
||
client_policy_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_DEVICE_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
status=ntstatus.NT_STATUS_INVALID_WORKSTATION)
|
||
|
||
def test_pac_groups_present(self):
|
||
"""Test that authentication succeeds if the device belongs to some
|
||
required groups.
|
||
"""
|
||
|
||
required_sids = {
|
||
('S-1-2-3-4', SidType.EXTRA_SID, self.default_attrs),
|
||
('S-1-9-8-7', SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
device_sids = required_sids | {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the required groups to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=device_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to belong to
|
||
# certain groups.
|
||
client_policy_sddl = self.allow_if(
|
||
f'Member_of {self.sddl_array_from_sids(required_sids)}')
|
||
client_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
# FIXME: we need to pass this parameter only because Samba doesn’t
|
||
# handle ‘krbtgt@REALM’ principals correctly (see
|
||
# https://bugzilla.samba.org/show_bug.cgi?id=15482).
|
||
krbtgt_sname = self.get_krbtgt_sname()
|
||
|
||
# Show that authentication succeeds.
|
||
self._armored_as_req(client_creds,
|
||
self.get_krbtgt_creds(),
|
||
mach_tgt,
|
||
target_sname=krbtgt_sname)
|
||
|
||
self.check_as_log(client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy)
|
||
|
||
def test_pac_resource_groups_present(self):
|
||
"""Test that authentication succeeds if the device belongs to some
|
||
required resource groups.
|
||
"""
|
||
|
||
required_sids = {
|
||
('S-1-2-3-4', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-5', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-6', SidType.RESOURCE_SID, self.resource_attrs),
|
||
}
|
||
|
||
device_sids = required_sids | {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the required groups to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=device_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to belong to
|
||
# certain groups.
|
||
client_policy_sddl = self.allow_if(
|
||
f'Member_of {self.sddl_array_from_sids(required_sids)}')
|
||
client_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
# Show that authentication fails.
|
||
self._armored_as_req(client_creds,
|
||
self.get_krbtgt_creds(),
|
||
mach_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
self.check_as_log(
|
||
client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy,
|
||
client_policy_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_DEVICE_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
status=ntstatus.NT_STATUS_INVALID_WORKSTATION)
|
||
|
||
def test_pac_resource_groups_present_to_service_sid_compression(self):
|
||
"""Test that authentication succeeds if the device belongs to some
|
||
required resource groups, and the request is to a service that supports
|
||
SID compression.
|
||
"""
|
||
|
||
required_sids = {
|
||
('S-1-2-3-4', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-5', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-6', SidType.RESOURCE_SID, self.resource_attrs),
|
||
}
|
||
|
||
device_sids = required_sids | {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the required groups to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=device_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to belong to
|
||
# certain groups.
|
||
client_policy_sddl = self.allow_if(
|
||
f'Member_of {self.sddl_array_from_sids(required_sids)}')
|
||
client_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
target_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'target'})
|
||
|
||
# Show that authentication fails.
|
||
self._armored_as_req(client_creds,
|
||
target_creds,
|
||
mach_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
self.check_as_log(
|
||
client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy,
|
||
client_policy_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_DEVICE_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
status=ntstatus.NT_STATUS_INVALID_WORKSTATION)
|
||
|
||
def test_pac_resource_groups_present_to_service_no_sid_compression(self):
|
||
"""Test that authentication succeeds if the device belongs to some
|
||
required resource groups, and the request is to a service that does not
|
||
support SID compression.
|
||
"""
|
||
|
||
required_sids = {
|
||
('S-1-2-3-4', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-5', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-6', SidType.RESOURCE_SID, self.resource_attrs),
|
||
}
|
||
|
||
device_sids = required_sids | {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the required groups to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=device_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to belong to
|
||
# certain groups.
|
||
client_policy_sddl = self.allow_if(
|
||
f'Member_of {self.sddl_array_from_sids(required_sids)}')
|
||
client_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
target_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={
|
||
'id': 'target',
|
||
'supported_enctypes': (
|
||
security.KERB_ENCTYPE_RC4_HMAC_MD5) | (
|
||
security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96_SK),
|
||
'sid_compression_support': False,
|
||
})
|
||
|
||
# Show that authentication fails.
|
||
self._armored_as_req(client_creds,
|
||
target_creds,
|
||
mach_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
self.check_as_log(
|
||
client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy,
|
||
client_policy_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_DEVICE_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
status=ntstatus.NT_STATUS_INVALID_WORKSTATION)
|
||
|
||
def test_pac_well_known_groups_not_present(self):
|
||
"""Test that authentication fails if the device does not belong to one
|
||
or more required well‐known groups.
|
||
"""
|
||
|
||
required_sids = {
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, self.default_attrs),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Modify the machine account’s TGT to contain only the SID of the
|
||
# machine account’s primary group.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=device_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to belong to
|
||
# certain groups.
|
||
client_policy_sddl = self.allow_if(
|
||
f'Member_of_any {self.sddl_array_from_sids(required_sids)}')
|
||
client_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
# Show that authentication fails.
|
||
self._armored_as_req(client_creds,
|
||
self.get_krbtgt_creds(),
|
||
mach_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
self.check_as_log(
|
||
client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy,
|
||
client_policy_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_DEVICE_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
status=ntstatus.NT_STATUS_INVALID_WORKSTATION)
|
||
|
||
def test_pac_device_info(self):
|
||
"""Test the groups of the client and the device after performing a
|
||
FAST‐armored AS‐REQ.
|
||
"""
|
||
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the required groups to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=device_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
|
||
target_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'target'})
|
||
|
||
expected_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
# The client’s groups are to include the Asserted Identity and
|
||
# Claims Valid SIDs.
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
samdb = self.get_samdb()
|
||
domain_sid_str = samdb.get_domain_sid()
|
||
|
||
expected_sids = self.map_sids(expected_sids, None, domain_sid_str)
|
||
|
||
# Show that authentication succeeds. Check that the groups in the PAC
|
||
# are as expected.
|
||
self._armored_as_req(client_creds,
|
||
target_creds,
|
||
mach_tgt,
|
||
expected_groups=expected_sids,
|
||
expect_device_info=False,
|
||
expected_device_groups=None)
|
||
|
||
self.check_as_log(
|
||
client_creds,
|
||
armor_creds=mach_creds)
|
||
|
||
def test_pac_claims_not_present(self):
|
||
"""Test that authentication fails if the device does not have a
|
||
required claim.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that requires the device to have a
|
||
# certain claim.
|
||
client_policy_sddl = self.allow_if(
|
||
f'@User.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
client_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
# Show that authentication fails.
|
||
self._armored_as_req(client_creds,
|
||
self.get_krbtgt_creds(),
|
||
mach_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
self.check_as_log(
|
||
client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy,
|
||
client_policy_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_DEVICE_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
status=ntstatus.NT_STATUS_INVALID_WORKSTATION)
|
||
|
||
def test_pac_claims_present(self):
|
||
"""Test that authentication succeeds if the device has a required
|
||
claim.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
pac_claims = [
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
(claim_id, claims.CLAIM_TYPE_STRING, [claim_value]),
|
||
]),
|
||
]
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the required claim to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=partial(self.set_pac_claims,
|
||
client_claims=pac_claims),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to have a
|
||
# certain claim.
|
||
client_policy_sddl = self.allow_if(
|
||
f'@User.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
client_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
# FIXME: we need to pass this parameter only because Samba doesn’t
|
||
# handle ‘krbtgt@REALM’ principals correctly (see
|
||
# https://bugzilla.samba.org/show_bug.cgi?id=15482).
|
||
krbtgt_sname = self.get_krbtgt_sname()
|
||
|
||
# Show that authentication succeeds.
|
||
self._armored_as_req(client_creds,
|
||
self.get_krbtgt_creds(),
|
||
mach_tgt,
|
||
target_sname=krbtgt_sname)
|
||
|
||
self.check_as_log(client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy)
|
||
|
||
def test_pac_claims_invalid(self):
|
||
"""Test that authentication fails if the device’s required claim is not
|
||
valid.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
pac_claims = [
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
(claim_id, claims.CLAIM_TYPE_STRING, [claim_value]),
|
||
]),
|
||
]
|
||
|
||
# The device’s SIDs do not include the Claims Valid SID.
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the SIDs and the required claim to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=[
|
||
partial(self.set_pac_claims, client_claims=pac_claims),
|
||
partial(self.set_pac_sids, new_sids=device_sids)],
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to have a
|
||
# certain claim.
|
||
client_policy_sddl = self.allow_if(
|
||
f'@User.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
client_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
# Show that authentication fails.
|
||
self._armored_as_req(client_creds,
|
||
self.get_krbtgt_creds(),
|
||
mach_tgt,
|
||
expected_error=KDC_ERR_POLICY)
|
||
|
||
self.check_as_log(
|
||
client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy,
|
||
client_policy_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_DEVICE_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED,
|
||
status=ntstatus.NT_STATUS_INVALID_WORKSTATION)
|
||
|
||
def test_device_in_world_group(self):
|
||
self._check_device_in_group(security.SID_WORLD)
|
||
|
||
def test_device_in_network_group(self):
|
||
self._check_device_not_in_group(security.SID_NT_NETWORK)
|
||
|
||
def test_device_in_authenticated_users(self):
|
||
self._check_device_in_group(security.SID_NT_AUTHENTICATED_USERS)
|
||
|
||
def test_device_in_aa_asserted_identity(self):
|
||
self._check_device_in_group(
|
||
security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY)
|
||
|
||
def test_device_in_service_asserted_identity(self):
|
||
self._check_device_not_in_group(security.SID_SERVICE_ASSERTED_IDENTITY)
|
||
|
||
def test_device_in_compounded_authentication(self):
|
||
self._check_device_not_in_group(security.SID_COMPOUNDED_AUTHENTICATION)
|
||
|
||
def test_device_in_claims_valid(self):
|
||
self._check_device_in_group(security.SID_CLAIMS_VALID)
|
||
|
||
def _check_device_in_group(self, group):
|
||
self._check_device_membership(group, expect_in_group=True)
|
||
|
||
def _check_device_not_in_group(self, group):
|
||
self._check_device_membership(group, expect_in_group=False)
|
||
|
||
def _check_device_membership(self, group, *, expect_in_group):
|
||
"""Test that authentication succeeds or fails when the device is
|
||
required to belong to a certain group.
|
||
"""
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that requires the device to belong to
|
||
# a certain group.
|
||
in_group_sddl = self.allow_if(f'Member_of {{SID({group})}}')
|
||
in_group_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=in_group_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=in_group_policy)
|
||
|
||
krbtgt_creds = self.get_krbtgt_creds()
|
||
|
||
# FIXME: we need to pass this parameter only because Samba doesn’t
|
||
# handle ‘krbtgt@REALM’ principals correctly (see
|
||
# https://bugzilla.samba.org/show_bug.cgi?id=15482).
|
||
krbtgt_sname = self.get_krbtgt_sname()
|
||
|
||
# Test whether authentication succeeds or fails.
|
||
self._armored_as_req(
|
||
client_creds,
|
||
krbtgt_creds,
|
||
mach_tgt,
|
||
target_sname=krbtgt_sname,
|
||
expected_error=0 if expect_in_group else KDC_ERR_POLICY)
|
||
|
||
policy_success_args = {}
|
||
policy_failure_args = {
|
||
'client_policy_status': ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
'event': AuditEvent.KERBEROS_DEVICE_RESTRICTION,
|
||
'reason': AuditReason.ACCESS_DENIED,
|
||
'status': ntstatus.NT_STATUS_INVALID_WORKSTATION,
|
||
}
|
||
|
||
self.check_as_log(client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=in_group_policy,
|
||
**(policy_success_args if expect_in_group
|
||
else policy_failure_args))
|
||
|
||
# Create an authentication policy that requires the device not to belong
|
||
# to the group.
|
||
not_in_group_sddl = self.allow_if(f'Not_Member_of {{SID({group})}}')
|
||
not_in_group_policy = self.create_authn_policy(
|
||
enforced=True, user_allowed_from=not_in_group_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=not_in_group_policy)
|
||
|
||
# Test whether authentication succeeds or fails.
|
||
self._armored_as_req(
|
||
client_creds,
|
||
krbtgt_creds,
|
||
mach_tgt,
|
||
target_sname=krbtgt_sname,
|
||
expected_error=KDC_ERR_POLICY if expect_in_group else 0)
|
||
|
||
self.check_as_log(client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=not_in_group_policy,
|
||
**(policy_failure_args if expect_in_group
|
||
else policy_success_args))
|
||
|
||
|
||
class TgsReqServicePolicyTests(ConditionalAceBaseTests):
|
||
def test_pac_groups_not_present(self):
|
||
"""Test that authorization succeeds if the client does not belong to
|
||
some required groups.
|
||
"""
|
||
|
||
required_sids = {
|
||
('S-1-2-3-4', SidType.EXTRA_SID, self.default_attrs),
|
||
('S-1-9-8-7', SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Create an authentication policy that requires the client to belong to
|
||
# certain groups.
|
||
target_policy_sddl = self.allow_if(
|
||
f'Member_of {self.sddl_array_from_sids(required_sids)}')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization fails.
|
||
self._tgs_req(
|
||
client_tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)
|
||
|
||
self.check_tgs_log(
|
||
client_creds, target_creds,
|
||
policy=target_policy,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED)
|
||
|
||
def test_pac_groups_present(self):
|
||
"""Test that authorization succeeds if the client belongs to some
|
||
required groups.
|
||
"""
|
||
|
||
required_sids = {
|
||
('S-1-2-3-4', SidType.EXTRA_SID, self.default_attrs),
|
||
('S-1-9-8-7', SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
client_sids = required_sids | {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Add the required groups to the client’s TGT.
|
||
client_tgt = self.modified_ticket(
|
||
client_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=client_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the client to belong to
|
||
# certain groups.
|
||
target_policy_sddl = self.allow_if(
|
||
f'Member_of {self.sddl_array_from_sids(required_sids)}')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization succeeds.
|
||
self._tgs_req(client_tgt, 0, client_creds, target_creds, armor_tgt=mach_tgt)
|
||
|
||
self.check_tgs_log(client_creds, target_creds,
|
||
policy=target_policy)
|
||
|
||
def test_pac_resource_groups_present_to_service_sid_compression(self):
|
||
"""Test that authorization succeeds if the client belongs to some
|
||
required resource groups, and the request is to a service that supports
|
||
SID compression.
|
||
"""
|
||
|
||
required_sids = {
|
||
('S-1-2-3-4', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-5', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-6', SidType.RESOURCE_SID, self.resource_attrs),
|
||
}
|
||
|
||
client_sids = required_sids | {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Add the required groups to the client’s TGT.
|
||
client_tgt = self.modified_ticket(
|
||
client_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=client_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the client to belong to
|
||
# certain groups.
|
||
target_policy_sddl = self.allow_if(
|
||
f'Member_of {self.sddl_array_from_sids(required_sids)}')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization fails.
|
||
self._tgs_req(
|
||
client_tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)
|
||
|
||
self.check_tgs_log(
|
||
client_creds, target_creds,
|
||
policy=target_policy,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED)
|
||
|
||
def test_pac_resource_groups_present_to_service_no_sid_compression(self):
|
||
"""Test that authorization succeeds if the client belongs to some
|
||
required resource groups, and the request is to a service that does not
|
||
support SID compression.
|
||
"""
|
||
|
||
required_sids = {
|
||
('S-1-2-3-4', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-5', SidType.RESOURCE_SID, self.resource_attrs),
|
||
('S-1-2-3-6', SidType.RESOURCE_SID, self.resource_attrs),
|
||
}
|
||
|
||
client_sids = required_sids | {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Add the required groups to the client’s TGT.
|
||
client_tgt = self.modified_ticket(
|
||
client_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=client_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the client to belong to
|
||
# certain groups.
|
||
target_policy_sddl = self.allow_if(
|
||
f'Member_of {self.sddl_array_from_sids(required_sids)}')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy,
|
||
additional_details={
|
||
'msDS-SupportedEncryptionTypes': str((
|
||
security.KERB_ENCTYPE_RC4_HMAC_MD5) | (
|
||
security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96_SK) | (
|
||
security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED))})
|
||
|
||
# Show that authorization fails.
|
||
self._tgs_req(
|
||
client_tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)
|
||
|
||
self.check_tgs_log(
|
||
client_creds, target_creds,
|
||
policy=target_policy,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED)
|
||
|
||
def test_pac_well_known_groups_not_present(self):
|
||
"""Test that authorization fails if the client does not belong to one
|
||
or more required well‐known groups.
|
||
"""
|
||
|
||
required_sids = {
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, self.default_attrs),
|
||
(self.aa_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
(self.service_asserted_identity, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Modify the client’s TGT to contain only the SID of the client’s
|
||
# primary group.
|
||
client_tgt = self.modified_ticket(
|
||
client_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=client_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the client to belong to
|
||
# certain groups.
|
||
target_policy_sddl = self.allow_if(
|
||
f'Member_of_any {self.sddl_array_from_sids(required_sids)}')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization fails.
|
||
self._tgs_req(
|
||
client_tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)
|
||
|
||
self.check_tgs_log(
|
||
client_creds, target_creds,
|
||
policy=target_policy,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED)
|
||
|
||
def test_pac_device_info(self):
|
||
self._run_pac_device_info_test()
|
||
|
||
def test_pac_device_info_no_compound_id_support(self):
|
||
self._run_pac_device_info_test(compound_id_support=False)
|
||
|
||
def test_pac_device_info_no_claims_valid(self):
|
||
self._run_pac_device_info_test(device_claims_valid=False)
|
||
|
||
def _run_pac_device_info_test(self, compound_id_support=True, device_claims_valid=True):
|
||
"""Test the groups of the client and the device after performing a
|
||
FAST‐armored TGS‐REQ.
|
||
"""
|
||
|
||
client_claim_id = 'the name of the client’s client claim'
|
||
client_claim_value = 'the value of the client’s client claim'
|
||
|
||
client_claims = [
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
(client_claim_id, claims.CLAIM_TYPE_STRING, [client_claim_value]),
|
||
]),
|
||
]
|
||
|
||
expected_client_claims = {
|
||
client_claim_id: {
|
||
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
||
'type': claims.CLAIM_TYPE_STRING,
|
||
'values': (client_claim_value,),
|
||
},
|
||
}
|
||
|
||
device_claim_id = 'the name of the device’s client claim'
|
||
device_claim_value = 'the value of the device’s client claim'
|
||
|
||
device_claims = [
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
(device_claim_id, claims.CLAIM_TYPE_STRING, [device_claim_value]),
|
||
]),
|
||
]
|
||
|
||
if compound_id_support:
|
||
expected_device_claims = {
|
||
device_claim_id: {
|
||
'source_type': claims.CLAIMS_SOURCE_TYPE_AD,
|
||
'type': claims.CLAIM_TYPE_STRING,
|
||
'values': (device_claim_value,),
|
||
},
|
||
}
|
||
else:
|
||
expected_device_claims = None
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
# This to ensure we have EXTRA_SIDS set already, as
|
||
# windows won't set that flag otherwise when adding one
|
||
# more
|
||
('S-1-2-3-4', SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
('S-1-2-3-4', SidType.EXTRA_SID, self.resource_attrs),
|
||
('S-1-3-4-5', SidType.EXTRA_SID, self.resource_attrs),
|
||
}
|
||
|
||
if device_claims_valid:
|
||
device_sids.add((security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs))
|
||
|
||
# Modify the machine account’s TGT to contain only the SID of the
|
||
# machine account’s primary group.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=[
|
||
partial(self.set_pac_sids,
|
||
new_sids=device_sids),
|
||
partial(self.set_pac_claims, client_claims=device_claims),
|
||
],
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Modify the client’s TGT to contain only the SID of the client’s
|
||
# primary group.
|
||
client_tgt = self.modified_ticket(
|
||
client_tgt,
|
||
modify_pac_fn=[
|
||
partial(self.set_pac_sids,
|
||
new_sids=client_sids),
|
||
partial(self.set_pac_claims, client_claims=client_claims),
|
||
],
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Indicate that Compound Identity is supported.
|
||
target_creds, _ = self.get_target(to_krbtgt=False, compound_id=compound_id_support)
|
||
|
||
expected_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
('S-1-2-3-4', SidType.EXTRA_SID, self.default_attrs),
|
||
# The client’s groups are not to include the Asserted Identity and
|
||
# Claims Valid SIDs.
|
||
}
|
||
|
||
if compound_id_support:
|
||
expected_sids.add((security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, self.default_attrs))
|
||
|
||
expected_device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
('S-1-2-3-4', SidType.EXTRA_SID, self.resource_attrs),
|
||
('S-1-3-4-5', SidType.EXTRA_SID, self.resource_attrs),
|
||
}
|
||
|
||
if device_claims_valid:
|
||
expected_device_sids.add(frozenset([(security.SID_CLAIMS_VALID, SidType.RESOURCE_SID, self.default_attrs)]))
|
||
else:
|
||
expected_device_sids = None
|
||
|
||
samdb = self.get_samdb()
|
||
domain_sid_str = samdb.get_domain_sid()
|
||
|
||
expected_sids = self.map_sids(expected_sids, None, domain_sid_str)
|
||
# The device SIDs will be put into the PAC unmodified.
|
||
expected_device_sids = self.map_sids(expected_device_sids, None, domain_sid_str)
|
||
|
||
# Show that authorization succeeds.
|
||
self._tgs_req(client_tgt, 0, client_creds, target_creds, armor_tgt=mach_tgt,
|
||
expected_groups=expected_sids,
|
||
expect_device_info=bool(expected_device_sids) or None,
|
||
expected_device_domain_sid=domain_sid_str,
|
||
expected_device_groups=expected_device_sids,
|
||
expect_client_claims=bool(expected_client_claims) or None,
|
||
expected_client_claims=expected_client_claims,
|
||
expect_device_claims=bool(expected_device_claims) or None,
|
||
expected_device_claims=expected_device_claims)
|
||
|
||
self.check_tgs_log(client_creds, target_creds)
|
||
|
||
def test_pac_extra_sids_behaviour(self):
|
||
"""Test the groups of the client and the device after performing a
|
||
FAST‐armored TGS‐REQ.
|
||
"""
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
client_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Modify the client’s TGT to contain only the SID of the client’s
|
||
# primary group.
|
||
client_tgt = self.modified_ticket(
|
||
client_tgt,
|
||
modify_pac_fn=partial(self.set_pac_sids,
|
||
new_sids=client_sids),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Indicate that Compound Identity is supported.
|
||
target_creds, _ = self.get_target(to_krbtgt=False, compound_id=True)
|
||
|
||
expected_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_COMPOUNDED_AUTHENTICATION, SidType.EXTRA_SID, self.default_attrs)
|
||
# The client’s groups are not to include the Asserted Identity and
|
||
# Claims Valid SIDs.
|
||
}
|
||
|
||
samdb = self.get_samdb()
|
||
domain_sid_str = samdb.get_domain_sid()
|
||
|
||
expected_sids = self.map_sids(expected_sids, None, domain_sid_str)
|
||
|
||
# Show that authorization succeeds.
|
||
self._tgs_req(client_tgt, 0, client_creds, target_creds, armor_tgt=mach_tgt,
|
||
expected_groups=expected_sids)
|
||
|
||
self.check_tgs_log(client_creds, target_creds)
|
||
|
||
def test_pac_claims_not_present(self):
|
||
"""Test that authentication fails if the device does not have a
|
||
required claim.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that requires the device to have a
|
||
# certain claim.
|
||
target_policy_sddl = self.allow_if(
|
||
f'@User.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization fails.
|
||
self._tgs_req(
|
||
client_tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)
|
||
|
||
self.check_tgs_log(
|
||
client_creds,
|
||
target_creds,
|
||
policy=target_policy,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED)
|
||
|
||
def test_pac_claims_present(self):
|
||
"""Test that authentication succeeds if the user has a required
|
||
claim.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
pac_claims = [
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
(claim_id, claims.CLAIM_TYPE_STRING, [claim_value]),
|
||
]),
|
||
]
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that requires the user to have a
|
||
# certain claim.
|
||
target_policy_sddl = self.allow_if(
|
||
f'@User.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Add the required claim to the client’s TGT.
|
||
client_tgt = self.modified_ticket(
|
||
client_tgt,
|
||
modify_pac_fn=partial(self.set_pac_claims,
|
||
client_claims=pac_claims),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization succeeds.
|
||
self._tgs_req(client_tgt, 0, client_creds, target_creds, armor_tgt=mach_tgt)
|
||
|
||
self.check_tgs_log(client_creds, target_creds,
|
||
policy=target_policy)
|
||
|
||
def test_pac_claims_invalid(self):
|
||
"""Test that authentication fails if the device’s required claim is not
|
||
valid.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
pac_claims = [
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
(claim_id, claims.CLAIM_TYPE_STRING, [claim_value]),
|
||
]),
|
||
]
|
||
|
||
# The device’s SIDs do not include the Claims Valid SID.
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that requires the device to have a
|
||
# certain claim.
|
||
target_policy_sddl = self.allow_if(
|
||
f'@User.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Add the SIDs and the required claim to the client’s TGT.
|
||
client_tgt = self.modified_ticket(
|
||
client_tgt,
|
||
modify_pac_fn=[
|
||
partial(self.set_pac_claims, client_claims=pac_claims),
|
||
partial(self.set_pac_sids, new_sids=device_sids)],
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization fails.
|
||
self._tgs_req(
|
||
client_tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)
|
||
|
||
self.check_tgs_log(
|
||
client_creds,
|
||
target_creds,
|
||
policy=target_policy,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED)
|
||
|
||
def test_pac_device_claims_not_present(self):
|
||
"""Test that authorization fails if the device does not have a
|
||
required claim.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that requires the device to have a
|
||
# certain device claim.
|
||
target_policy_sddl = self.allow_if(
|
||
f'@Device.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization fails.
|
||
self._tgs_req(
|
||
client_tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)
|
||
|
||
self.check_tgs_log(
|
||
client_creds,
|
||
target_creds,
|
||
policy=target_policy,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED)
|
||
|
||
def test_pac_device_claims_present(self):
|
||
"""Test that authorization succeeds if the device has a required claim.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
pac_claims = [
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
(claim_id, claims.CLAIM_TYPE_STRING, [claim_value]),
|
||
]),
|
||
]
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the required claim to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=partial(self.set_pac_claims,
|
||
client_claims=pac_claims),
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to have a
|
||
# certain device claim.
|
||
target_policy_sddl = self.allow_if(
|
||
f'@Device.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization succeeds.
|
||
self._tgs_req(client_tgt, 0, client_creds, target_creds, armor_tgt=mach_tgt)
|
||
|
||
self.check_tgs_log(client_creds, target_creds,
|
||
policy=target_policy)
|
||
|
||
def test_pac_device_claims_invalid(self):
|
||
"""Test that authorization fails if the device’s required claim is not
|
||
valid.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
pac_claims = [
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
(claim_id, claims.CLAIM_TYPE_STRING, [claim_value]),
|
||
]),
|
||
]
|
||
|
||
# The device’s SIDs do not include the Claims Valid SID.
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the SIDs and the required claim to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=[
|
||
partial(self.set_pac_claims, client_claims=pac_claims),
|
||
partial(self.set_pac_sids, new_sids=device_sids)],
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to have a
|
||
# certain claim.
|
||
target_policy_sddl = self.allow_if(
|
||
f'@Device.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization fails.
|
||
self._tgs_req(
|
||
client_tgt, KDC_ERR_POLICY, client_creds, target_creds,
|
||
armor_tgt=mach_tgt,
|
||
expect_edata=self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
expect_status=None,
|
||
expected_status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED)
|
||
|
||
self.check_tgs_log(
|
||
client_creds,
|
||
target_creds,
|
||
policy=target_policy,
|
||
status=ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
event=AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
reason=AuditReason.ACCESS_DENIED)
|
||
|
||
def test_pac_device_claims_invalid_no_attrs(self):
|
||
"""Test that authorization fails if the device’s required claim is not
|
||
valid.
|
||
"""
|
||
|
||
claim_id = 'the name of the claim'
|
||
claim_value = 'the value of the claim'
|
||
|
||
pac_claims = [
|
||
(claims.CLAIMS_SOURCE_TYPE_AD, [
|
||
(claim_id, claims.CLAIM_TYPE_STRING, [claim_value]),
|
||
]),
|
||
]
|
||
|
||
device_sids = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
# The device’s SIDs include the Claims Valid SID, but it has no
|
||
# attributes.
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, 0),
|
||
}
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Add the SIDs and the required claim to the machine account’s TGT.
|
||
mach_tgt = self.modified_ticket(
|
||
mach_tgt,
|
||
modify_pac_fn=[
|
||
partial(self.set_pac_claims, client_claims=pac_claims),
|
||
partial(self.set_pac_sids, new_sids=device_sids)],
|
||
checksum_keys=self.get_krbtgt_checksum_key())
|
||
|
||
# Create an authentication policy that requires the device to have a
|
||
# certain claim.
|
||
target_policy_sddl = self.allow_if(
|
||
f'@Device.{escaped_claim_id(claim_id)} == "{claim_value}"')
|
||
target_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
# Show that authorization succeeds.
|
||
self._tgs_req(client_tgt, 0, client_creds, target_creds, armor_tgt=mach_tgt)
|
||
|
||
self.check_tgs_log(client_creds, target_creds,
|
||
policy=target_policy)
|
||
|
||
def test_simple_as_req_client_and_target_policy(self):
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER)
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that explicitly allows the machine
|
||
# account for a user.
|
||
client_policy_sddl = f'O:SYD:(XA;;CR;;;{mach_creds.get_sid()};(Member_of {{SID({mach_creds.get_sid()}), SID({mach_creds.get_sid()})}}))'
|
||
client_policy = self.create_authn_policy(enforced=True,
|
||
user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
# Create an authentication policy that applies to a computer and
|
||
# explicitly allows the user account to obtain a service ticket.
|
||
target_policy_sddl = f'O:SYD:(XA;;CR;;;{client_creds.get_sid()};(Member_of SID({client_creds.get_sid()})))'
|
||
target_policy = self.create_authn_policy(enforced=True,
|
||
computer_allowed_to=target_policy_sddl)
|
||
|
||
# Create a computer account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=target_policy)
|
||
|
||
expected_groups = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
# Show that obtaining a service ticket with an AS‐REQ is allowed.
|
||
self._armored_as_req(client_creds,
|
||
target_creds,
|
||
mach_tgt,
|
||
expected_groups=expected_groups)
|
||
|
||
self.check_as_log(client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy,
|
||
server_policy=target_policy)
|
||
|
||
def test_device_in_world_group(self):
|
||
self._check_device_in_group(security.SID_WORLD)
|
||
|
||
def test_device_in_network_group(self):
|
||
self._check_device_not_in_group(security.SID_NT_NETWORK)
|
||
|
||
def test_device_in_authenticated_users(self):
|
||
self._check_device_in_group(security.SID_NT_AUTHENTICATED_USERS)
|
||
|
||
def test_device_in_aa_asserted_identity(self):
|
||
self._check_device_in_group(
|
||
security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY)
|
||
|
||
def test_device_in_service_asserted_identity(self):
|
||
self._check_device_not_in_group(security.SID_SERVICE_ASSERTED_IDENTITY)
|
||
|
||
def test_device_in_compounded_authentication(self):
|
||
self._check_device_not_in_group(security.SID_COMPOUNDED_AUTHENTICATION)
|
||
|
||
def test_device_in_claims_valid(self):
|
||
self._check_device_in_group(security.SID_CLAIMS_VALID)
|
||
|
||
def _check_device_in_group(self, group):
|
||
self._check_device_membership(group, expect_in_group=True)
|
||
|
||
def _check_device_not_in_group(self, group):
|
||
self._check_device_membership(group, expect_in_group=False)
|
||
|
||
def _check_device_membership(self, group, *, expect_in_group):
|
||
"""Test that authentication succeeds or fails when the device is
|
||
required to belong to a certain group.
|
||
"""
|
||
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER,
|
||
opts={'id': 'device'})
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that requires the device to belong to
|
||
# a certain group.
|
||
in_group_sddl = self.allow_if(f'Device_Member_of {{SID({group})}}')
|
||
in_group_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=in_group_sddl)
|
||
|
||
# Create a user account.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER)
|
||
client_tgt = self.get_tgt(client_creds)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=in_group_policy)
|
||
|
||
tgs_success_args = {}
|
||
tgs_failure_args = {
|
||
'expect_edata': self.expect_padata_outer,
|
||
# We aren’t particular about whether or not we get an NTSTATUS.
|
||
'expect_status': None,
|
||
'expected_status': ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
}
|
||
|
||
# Test whether authorization succeeds or fails.
|
||
self._tgs_req(client_tgt,
|
||
0 if expect_in_group else KDC_ERR_POLICY,
|
||
client_creds,
|
||
target_creds,
|
||
armor_tgt=mach_tgt,
|
||
**(tgs_success_args if expect_in_group
|
||
else tgs_failure_args))
|
||
|
||
policy_success_args = {}
|
||
policy_failure_args = {
|
||
'status': ntstatus.NT_STATUS_AUTHENTICATION_FIREWALL_FAILED,
|
||
'event': AuditEvent.KERBEROS_SERVER_RESTRICTION,
|
||
'reason': AuditReason.ACCESS_DENIED,
|
||
}
|
||
|
||
self.check_tgs_log(client_creds, target_creds,
|
||
policy=in_group_policy,
|
||
**(policy_success_args if expect_in_group
|
||
else policy_failure_args))
|
||
|
||
# Create an authentication policy that requires the device not to belong
|
||
# to the group.
|
||
not_in_group_sddl = self.allow_if(
|
||
f'Not_Device_Member_of {{SID({group})}}')
|
||
not_in_group_policy = self.create_authn_policy(
|
||
enforced=True, computer_allowed_to=not_in_group_sddl)
|
||
|
||
# Create a target account with the assigned policy.
|
||
target_creds = self._get_creds(account_type=self.AccountType.COMPUTER,
|
||
assigned_policy=not_in_group_policy)
|
||
|
||
# Test whether authorization succeeds or fails.
|
||
self._tgs_req(client_tgt,
|
||
KDC_ERR_POLICY if expect_in_group else 0,
|
||
client_creds,
|
||
target_creds,
|
||
armor_tgt=mach_tgt,
|
||
**(tgs_failure_args if expect_in_group
|
||
else tgs_success_args))
|
||
|
||
self.check_tgs_log(client_creds, target_creds,
|
||
policy=not_in_group_policy,
|
||
**(policy_failure_args if expect_in_group
|
||
else policy_success_args))
|
||
|
||
def test_simple_as_req_client_policy_only(self):
|
||
# Create a machine account with which to perform FAST.
|
||
mach_creds = self.get_cached_creds(
|
||
account_type=self.AccountType.COMPUTER)
|
||
mach_tgt = self.get_tgt(mach_creds)
|
||
|
||
# Create an authentication policy that explicitly allows the machine
|
||
# account for a user.
|
||
client_policy_sddl = f'O:SYD:(XA;;CR;;;{mach_creds.get_sid()};(Member_of SID({mach_creds.get_sid()})))'
|
||
client_policy = self.create_authn_policy(enforced=True,
|
||
user_allowed_from=client_policy_sddl)
|
||
|
||
# Create a user account with the assigned policy.
|
||
client_creds = self._get_creds(account_type=self.AccountType.USER,
|
||
assigned_policy=client_policy)
|
||
|
||
expected_groups = {
|
||
(security.DOMAIN_RID_USERS, SidType.BASE_SID, self.default_attrs),
|
||
(security.DOMAIN_RID_USERS, SidType.PRIMARY_GID, None),
|
||
(security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, SidType.EXTRA_SID, self.default_attrs),
|
||
(security.SID_CLAIMS_VALID, SidType.EXTRA_SID, self.default_attrs),
|
||
}
|
||
|
||
# FIXME: we need to pass this parameter only because Samba doesn’t
|
||
# handle ‘krbtgt@REALM’ principals correctly (see
|
||
# https://bugzilla.samba.org/show_bug.cgi?id=15482).
|
||
krbtgt_sname = self.get_krbtgt_sname()
|
||
|
||
# Show that obtaining a service ticket with an AS‐REQ is allowed.
|
||
self._armored_as_req(client_creds,
|
||
self.get_krbtgt_creds(),
|
||
mach_tgt,
|
||
target_sname=krbtgt_sname,
|
||
expected_groups=expected_groups)
|
||
|
||
self.check_as_log(client_creds,
|
||
armor_creds=mach_creds,
|
||
client_policy=client_policy)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
global_asn1_print = False
|
||
global_hexdump = False
|
||
import unittest
|
||
unittest.main()
|