From: Rob van der Linde Date: Fri, 16 Feb 2024 01:47:23 +0000 (+1300) Subject: netcmd: gmsa: cli commands for managing group msa membership X-Git-Tag: tdb-1.4.11~1561 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a7a35ae5e3cb24e0fbfa3e84f24ecec8458fb62e;p=thirdparty%2Fsamba.git netcmd: gmsa: cli commands for managing group msa membership Signed-off-by: Rob van der Linde Reviewed-by: Andrew Bartlett Reviewed-by: Douglas Bagnall --- diff --git a/python/samba/netcmd/service_account/__init__.py b/python/samba/netcmd/service_account/__init__.py index 3c42464fa0c..d19f98a7236 100644 --- a/python/samba/netcmd/service_account/__init__.py +++ b/python/samba/netcmd/service_account/__init__.py @@ -22,6 +22,7 @@ from samba.netcmd import SuperCommand +from .group_msa_membership import cmd_service_account_group_msa_membership from .service_account import (cmd_service_account_create, cmd_service_account_delete, cmd_service_account_list, @@ -38,4 +39,5 @@ class cmd_service_account(SuperCommand): "list": cmd_service_account_list(), "view": cmd_service_account_view(), "modify": cmd_service_account_modify(), + "group-msa-membership": cmd_service_account_group_msa_membership(), } diff --git a/python/samba/netcmd/service_account/group_msa_membership.py b/python/samba/netcmd/service_account/group_msa_membership.py new file mode 100644 index 00000000000..86ae22d9c29 --- /dev/null +++ b/python/samba/netcmd/service_account/group_msa_membership.py @@ -0,0 +1,231 @@ +# Unix SMB/CIFS implementation. +# +# Manage who can view service account passwords. +# +# Copyright (C) Catalyst.Net Ltd. 2024 +# +# Written by Rob van der Linde +# +# 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 . + +from samba.dcerpc import security +from samba.getopt import CredentialsOptions, HostOptions, Option, SambaOptions +from samba.netcmd import Command, CommandError, SuperCommand +from samba.netcmd.domain.models import (Group, GroupManagedServiceAccount, + Model, User) +from samba.netcmd.domain.models.exceptions import ModelError + + +class cmd_service_account_group_msa_membership_show(Command): + """Display who is able to view the service account password.""" + + synopsis = "%prog -H [options]" + + takes_optiongroups = { + "sambaopts": SambaOptions, + "credopts": CredentialsOptions, + "hostopts": HostOptions, + } + + takes_options = [ + Option("--name", + help="Name of managed service account (required).", + dest="name", action="store", type=str, required=True), + Option("--json", help="Output results in JSON format.", + dest="output_format", action="store_const", const="json"), + ] + + def run(self, hostopts=None, sambaopts=None, credopts=None, name=None, + output_format=None): + + ldb = self.ldb_connect(hostopts, sambaopts, credopts) + + try: + gmsa = GroupManagedServiceAccount.find(ldb, name) + except ModelError as e: + raise CommandError(e) + + if gmsa is None: + raise CommandError(f"Group managed service account {name} not found.") + + try: + trustees = [Model.get(ldb, object_sid=sid, polymorphic=True) for sid in gmsa.trustees] + except ModelError as e: + raise CommandError(e) + + if output_format == "json": + self.print_json({ + "dn": gmsa.dn, + "trustees": [trustee.dn for trustee in trustees] + }) + else: + print(f"Account-DN: {gmsa.dn}", file=self.outf) + + print("Accounts or groups that are able to retrieve this group managed service account password:", + file=self.outf) + + for trustee in trustees: + print(f" {trustee.dn}", file=self.outf) + + +class cmd_service_account_group_msa_membership_add(Command): + """Add a password viewer.""" + + synopsis = "%prog -H [options]" + + takes_optiongroups = { + "sambaopts": SambaOptions, + "credopts": CredentialsOptions, + "hostopts": HostOptions, + } + + takes_options = [ + Option("--name", + help="Name of managed service account (required).", + dest="name", action="store", type=str, required=True), + Option("--principal", + help="Principal sAMAccountName or Dn to add (required).", + dest="principal", action="store", type=str, required=True), + ] + + def run(self, hostopts=None, sambaopts=None, credopts=None, name=None, + principal=None): + + ldb = self.ldb_connect(hostopts, sambaopts, credopts) + + try: + gmsa = GroupManagedServiceAccount.find(ldb, name) + except ModelError as e: + raise CommandError(e) + + if gmsa is None: + raise CommandError(f"Group managed service account {name} not found.") + + # Note that principal can be a user or group (by passing in a Dn). + # If the Dn is a group it will see it as a User but this doesn't matter. + try: + trustee = User.find(ldb, principal) + except ModelError as e: + raise CommandError(e) + + if trustee is None: + raise CommandError(f"Trust {principal} not found.") + + try: + trustees = gmsa.trustees + except ModelError as e: + raise CommandError(e) + + if trustee.object_sid in trustees: + print(f"Trustee '{trustee}' is already allowed to show managed passwords for: {gmsa}", + file=self.outf) + else: + aces = gmsa.group_msa_membership.dacl.aces + + ace = security.ace() + ace.type = security.SEC_ACE_TYPE_ACCESS_ALLOWED + ace.trustee = security.dom_sid(trustee.object_sid) + ace.access_mask = security.SEC_ADS_GENERIC_ALL + aces.append(ace) + + # aces is a copy so this is necessary including the len + gmsa.group_msa_membership.dacl.aces = aces + gmsa.group_msa_membership.dacl.num_aces = len(aces) + + try: + gmsa.save(ldb) + except ModelError as e: + raise CommandError(e) + + print(f"Trustee '{trustee}' is now allowed to show managed passwords for: {gmsa}", + file=self.outf) + + +class cmd_service_account_group_msa_membership_remove(Command): + """Remove a password viewer.""" + + synopsis = "%prog -H [options]" + + takes_optiongroups = { + "sambaopts": SambaOptions, + "credopts": CredentialsOptions, + "hostopts": HostOptions, + } + + takes_options = [ + Option("--name", + help="Name of managed service account (required).", + dest="name", action="store", type=str, required=True), + Option("--principal", + help="Principal sAMAccountName or Dn to remove (required).", + dest="principal", action="store", type=str, required=True), + ] + + def run(self, hostopts=None, sambaopts=None, credopts=None, name=None, + principal=None): + + ldb = self.ldb_connect(hostopts, sambaopts, credopts) + + try: + gmsa = GroupManagedServiceAccount.find(ldb, name) + except ModelError as e: + raise CommandError(e) + + if gmsa is None: + raise CommandError(f"Group managed service account {name} not found.") + + # Note that principal can be a user or group (by passing in a Dn). + # If the Dn is a group it will see it as a User but this doesn't matter. + try: + trustee = User.find(ldb, principal) + except ModelError as e: + raise CommandError(e) + + if trustee is None: + raise CommandError(f"User {principal} not found.") + + try: + trustees = gmsa.trustees + except ModelError as e: + raise CommandError(e) + + if trustee.object_sid not in trustees: + print(f"Trustee '{trustee}' cannot currently show managed passwords for: {gmsa}", + file=self.outf) + else: + aces = gmsa.group_msa_membership.dacl.aces + + for ace in aces: + if trustee.object_sid == str(ace.trustee): + gmsa.group_msa_membership.dacl_del_ace(ace) + break + + try: + gmsa.save(ldb) + except ModelError as e: + raise CommandError(e) + + print(f"Trustee '{trustee}' removed access to show managed passwords for: {gmsa}", + file=self.outf) + + +class cmd_service_account_group_msa_membership(SuperCommand): + """View and manage password retrieval for service account.""" + + # set sddl + subcommands = { + "show": cmd_service_account_group_msa_membership_show(), + "add": cmd_service_account_group_msa_membership_add(), + "remove": cmd_service_account_group_msa_membership_remove(), + }