from enum import IntFlag
+from samba.dcerpc.security import (
+ KERB_ENCTYPE_FAST_SUPPORTED,
+ KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED,
+ KERB_ENCTYPE_CLAIMS_SUPPORTED,
+ KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED
+)
from samba.dsdb import (
ATYPE_SECURITY_GLOBAL_GROUP,
ATYPE_SECURITY_LOCAL_GROUP,
UF_NO_AUTH_DATA_REQUIRED,
UF_PARTIAL_SECRETS_ACCOUNT,
UF_USE_AES_KEYS,
+ ENC_ALL_TYPES,
+ ENC_CRC32,
+ ENC_RSA_MD5,
+ ENC_RC4_HMAC_MD5,
+ ENC_HMAC_SHA1_96_AES128,
+ ENC_HMAC_SHA1_96_AES256,
+ ENC_HMAC_SHA1_96_AES256_SK,
GTYPE_DISTRIBUTION_GLOBAL_GROUP,
GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
NO_AUTH_DATA_REQUIRED = UF_NO_AUTH_DATA_REQUIRED
PARTIAL_SECRETS_ACCOUNT = UF_PARTIAL_SECRETS_ACCOUNT
USE_AES_KEYS = UF_USE_AES_KEYS
+
+
+class SupportedEncryptionTypes(IntFlag):
+ ALL_TYPES = ENC_ALL_TYPES
+ CRC32 = ENC_CRC32
+ RSA_MD5 = ENC_RSA_MD5
+ RC4_HMAC_MD5 = ENC_RC4_HMAC_MD5
+ HMAC_SHA1_96_AES128 = ENC_HMAC_SHA1_96_AES128
+ HMAC_SHA1_96_AES256 = ENC_HMAC_SHA1_96_AES256
+ HMAC_SHA1_96_AES256_SK = ENC_HMAC_SHA1_96_AES256_SK
+ FAST_SUPPORTED = KERB_ENCTYPE_FAST_SUPPORTED
+ COMPOUND_IDENTITY_SUPPORTED = KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED
+ CLAIMS_SUPPORTED = KERB_ENCTYPE_CLAIMS_SUPPORTED
+ RESOURCE_SID_COMPRESSION_DISABLED = KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-from ldb import Dn
+from ldb import FLAG_MOD_ADD, Dn
+from samba.dcerpc import security
from samba.dsdb import (DS_GUID_MANAGED_SERVICE_ACCOUNTS_CONTAINER,
DS_GUID_USERS_CONTAINER)
+from samba.ndr import ndr_unpack
-from .fields import (DnField, EnumField, IntegerField, SIDField, StringField,
- NtTimeField)
+from .fields import (BinaryField, DnField, EnumField, IntegerField, SDDLField,
+ SIDField, StringField, NtTimeField)
from .model import Model
-from .types import AccountType, UserAccountControl
+from .types import AccountType, SupportedEncryptionTypes, UserAccountControl
class User(Model):
query = {"username": name}
return cls.get(ldb, **query)
+
+
+class GroupManagedServiceAccount(User):
+ """A GroupManagedServiceAccount is a type of User with additional fields."""
+ managed_password_interval = IntegerField("msDS-ManagedPasswordInterval")
+ dns_host_name = StringField("dNSHostName")
+ group_msa_membership = SDDLField("msDS-GroupMSAMembership")
+ managed_password_id = BinaryField("msDS-ManagedPasswordId",
+ readonly=True, hidden=True)
+ managed_password_previous_id = BinaryField("msDS-ManagedPasswordPreviousId",
+ readonly=True, hidden=True)
+ supported_encryption_types = EnumField("msDS-SupportedEncryptionTypes",
+ SupportedEncryptionTypes)
+
+ @staticmethod
+ def get_base_dn(ldb):
+ """Return base Dn for Managed Service Accounts.
+
+ :param ldb: Ldb connection
+ :return: Dn to use for searching
+ """
+ return ldb.get_wellknown_dn(ldb.get_default_basedn(),
+ DS_GUID_MANAGED_SERVICE_ACCOUNTS_CONTAINER)
+
+ @staticmethod
+ def get_object_class():
+ return "msDS-GroupManagedServiceAccount"
+
+ def trustees(self, ldb):
+ """Returns list of trustees from the msDS-GroupMSAMembership SDDL.
+
+ :return: list of User objects
+ """
+ users = []
+ field = self.fields["group_msa_membership"]
+ sddl = self.group_msa_membership
+ message = field.to_db_value(ldb, sddl, FLAG_MOD_ADD)
+ desc = ndr_unpack(security.descriptor, message[0])
+
+ for ace in desc.dacl.aces:
+ users.append(User.get(ldb, object_sid=ace.trustee))
+
+ return users
+
+ @classmethod
+ def find(cls, ldb, name):
+ """Helper function to find a service account first by Dn then username.
+
+ If the Dn can't be parsed use sAMAccountName, automatically add the $.
+ """
+ try:
+ query = {"dn": Dn(ldb, name)}
+ except ValueError:
+ if name.endswith("$"):
+ query = {"username": name}
+ else:
+ query = {"username": name + "$"}
+
+ return cls.get(ldb, **query)
+
+ @staticmethod
+ def group_sddl(group):
+ return f"O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;{group.object_sid})"