diff options
| author | Rob van der Linde <rob@catalyst.net.nz> | 2023-05-16 12:15:06 +1200 |
|---|---|---|
| committer | Andrew Bartlett <abartlet@samba.org> | 2023-06-25 23:29:32 +0000 |
| commit | 3df634e7527c2e0f9c71d62afc7a48300b7bd388 (patch) | |
| tree | 15c5ac249a7a3dfe4f5b6c85f65fa81379abc778 /python | |
| parent | 3a0160ae94301c9931ee25eb7a87cf77cd588f33 (diff) | |
| download | samba-3df634e7527c2e0f9c71d62afc7a48300b7bd388.tar.gz samba-3df634e7527c2e0f9c71d62afc7a48300b7bd388.tar.bz2 samba-3df634e7527c2e0f9c71d62afc7a48300b7bd388.zip | |
netcmd: domain: add authentication silo commands
Authentication policies:
* samba-tool domain auth policy list
* samba-tool domain auth policy view
* samba-tool domain auth policy create
* samba-tool domain auth policy modify
* samba-tool domain auth policy delete
Authentication silos:
* samba-tool domain auth silo list
* samba-tool domain auth silo view
* samba-tool domain auth silo create
* samba-tool domain auth silo modify
* samba-tool domain auth silo delete
Authentication silo members:
* samba-tool domain auth silo member list
* samba-tool domain auth silo member add
* samba-tool domain auth silo member remove
Signed-off-by: Rob van der Linde <rob@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Diffstat (limited to 'python')
| -rw-r--r-- | python/samba/netcmd/domain/__init__.py | 2 | ||||
| -rw-r--r-- | python/samba/netcmd/domain/auth/__init__.py | 35 | ||||
| -rw-r--r-- | python/samba/netcmd/domain/auth/base.py | 61 | ||||
| -rw-r--r-- | python/samba/netcmd/domain/auth/policy.py | 396 | ||||
| -rw-r--r-- | python/samba/netcmd/domain/auth/silo.py | 367 | ||||
| -rw-r--r-- | python/samba/netcmd/domain/auth/silo_member.py | 216 |
6 files changed, 1077 insertions, 0 deletions
diff --git a/python/samba/netcmd/domain/__init__.py b/python/samba/netcmd/domain/__init__.py index 3defc120be7..1c527f1baec 100644 --- a/python/samba/netcmd/domain/__init__.py +++ b/python/samba/netcmd/domain/__init__.py @@ -25,6 +25,7 @@ from samba import is_ad_dc_built from samba.netcmd import SuperCommand +from .auth import cmd_domain_auth from .backup import cmd_domain_backup from .claim import cmd_domain_claim from .classicupgrade import cmd_domain_classicupgrade @@ -56,6 +57,7 @@ class cmd_domain(SuperCommand): subcommands["join"] = cmd_domain_join() subcommands["leave"] = cmd_domain_leave() subcommands["claim"] = cmd_domain_claim() + subcommands["auth"] = cmd_domain_auth() if is_ad_dc_built(): subcommands["demote"] = cmd_domain_demote() subcommands["provision"] = cmd_domain_provision() diff --git a/python/samba/netcmd/domain/auth/__init__.py b/python/samba/netcmd/domain/auth/__init__.py new file mode 100644 index 00000000000..fd74f3e92be --- /dev/null +++ b/python/samba/netcmd/domain/auth/__init__.py @@ -0,0 +1,35 @@ +# Unix SMB/CIFS implementation. +# +# authentication silos +# +# Copyright (C) Catalyst.Net Ltd. 2023 +# +# Written by Rob van der Linde <rob@catalyst.net.nz> +# +# 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/>. +# + +from samba.netcmd import SuperCommand + +from .policy import cmd_domain_auth_policy +from .silo import cmd_domain_auth_silo + + +class cmd_domain_auth(SuperCommand): + """Manage authentication silos and policies on the domain.""" + + subcommands = { + "policy": cmd_domain_auth_policy(), + "silo": cmd_domain_auth_silo(), + } diff --git a/python/samba/netcmd/domain/auth/base.py b/python/samba/netcmd/domain/auth/base.py new file mode 100644 index 00000000000..6734751e6ad --- /dev/null +++ b/python/samba/netcmd/domain/auth/base.py @@ -0,0 +1,61 @@ +# Unix SMB/CIFS implementation. +# +# authentication silos - base class and common code +# +# Copyright (C) Catalyst.Net Ltd. 2023 +# +# Written by Rob van der Linde <rob@catalyst.net.nz> +# +# 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 json + +from samba.auth import system_session +from samba.netcmd import Command, CommandError +from samba.netcmd.encoders import JSONEncoder +from samba.netcmd.domain.models import AuthenticationPolicy +from samba.samdb import SamDB + + +class SiloCommand(Command): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.ldb = None + + def ldb_connect(self, ldap_url, sambaopts, credopts): + """Helper to connect to Ldb database using command line opts.""" + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + return SamDB(ldap_url, credentials=creds, + session_info=system_session(lp), lp=lp) + + def print_json(self, data): + """Print json on the screen using consistent formatting and sorting. + + A custom JSONEncoder class is used to help with serializing unknown + objects such as Dn for example. + """ + json.dump(data, self.outf, cls=JSONEncoder, indent=2, sort_keys=True) + self.outf.write("\n") + + def get_policy(self, name): + """Helper function to return auth policy or raise CommandError. + + :raises CommandError: if the policy was not found. + """ + policy = AuthenticationPolicy.get(self.ldb, cn=name) + if policy is None: + raise CommandError(f"Authentication policy {name} not found.") + return policy diff --git a/python/samba/netcmd/domain/auth/policy.py b/python/samba/netcmd/domain/auth/policy.py new file mode 100644 index 00000000000..d132b0df948 --- /dev/null +++ b/python/samba/netcmd/domain/auth/policy.py @@ -0,0 +1,396 @@ +# Unix SMB/CIFS implementation. +# +# authentication silos - authentication policy management +# +# Copyright (C) Catalyst.Net Ltd. 2023 +# +# Written by Rob van der Linde <rob@catalyst.net.nz> +# +# 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 samba.getopt as options +from ldb import LdbError +from samba.netcmd import CommandError, Option, SuperCommand +from samba.netcmd.domain.models import AuthenticationPolicy +from samba.netcmd.domain.models.auth_policy import MIN_TGT_LIFETIME,\ + MAX_TGT_LIFETIME, StrongNTLMPolicy +from samba.netcmd.validators import Range + +from .base import SiloCommand + + +class cmd_domain_auth_policy_list(SiloCommand): + """List authentication policies on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--json", help="Output results in JSON format.", + dest="output_format", action="store_const", const="json"), + ] + + def run(self, ldap_url=None, sambaopts=None, credopts=None, + output_format=None): + + self.ldb = self.ldb_connect(ldap_url, sambaopts, credopts) + + # Authentication policies grouped by cn. + policies = {policy.cn: policy.as_dict() + for policy in AuthenticationPolicy.query(self.ldb)} + + # Using json output format gives more detail. + if output_format == "json": + self.print_json(policies) + else: + for policy in policies.keys(): + self.outf.write(f"{policy}\n") + + +class cmd_domain_auth_policy_view(SiloCommand): + """View an authentication policy on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--name", + help="Name of authentication policy to view (required).", + dest="name", action="store", type=str), + ] + + def run(self, ldap_url=None, sambaopts=None, credopts=None, name=None): + + if not name: + raise CommandError("Argument --name is required.") + + self.ldb = self.ldb_connect(ldap_url, sambaopts, credopts) + + # Check if authentication policy exists first. + policy = AuthenticationPolicy.get(self.ldb, cn=name) + if policy is None: + raise CommandError(f"Authentication policy {name} not found.") + + # Display policy as JSON. + self.print_json(policy.as_dict()) + + +class cmd_domain_auth_policy_create(SiloCommand): + """Create an authentication policy on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--name", help="Name of authentication policy (required).", + dest="name", action="store", type=str), + Option("--description", + help="Optional description for authentication policy.", + dest="description", action="store", type=str), + Option("--protect", + help="Protect authentication silo from accidental deletion.", + dest="protect", action="store_true"), + Option("--unprotect", + help="Unprotect authentication silo from accidental deletion.", + dest="unprotect", action="store_true"), + Option("--audit", + help="Only audit authentication policy.", + dest="audit", action="store_true"), + Option("--enforce", + help="Enforce authentication policy.", + dest="enforce", action="store_true"), + Option("--strong-ntlm-policy", + help=f"Strong NTLM Policy ({StrongNTLMPolicy.choices_str()}).", + dest="strong_ntlm_policy", type="choice", action="store", + choices=StrongNTLMPolicy.get_choices(), + default="Disabled"), + Option("--user-tgt-lifetime", + help="Ticket-Granting-Ticket lifetime for user accounts.", + dest="user_tgt_lifetime", type=int, action="store", + validators=[Range(min=MIN_TGT_LIFETIME, max=MAX_TGT_LIFETIME)]), + Option("--user-allow-ntlm-auth", + help="Allow NTLM network authentication when user " + "is restricted to selected devices.", + dest="user_allow_ntlm_auth", action="store_true", + default=False), + Option("--service-tgt-lifetime", + help="Ticket-Granting-Ticket lifetime for service accounts.", + dest="service_tgt_lifetime", type=int, action="store", + validators=[Range(min=MIN_TGT_LIFETIME, max=MAX_TGT_LIFETIME)]), + Option("--service-allow-ntlm-auth", + help="Allow NTLM network authentication when service " + "is restricted to selected devices.", + dest="service_allow_ntlm_auth", action="store_true", + default=False), + Option("--computer-tgt-lifetime", + help="Ticket-Granting-Ticket lifetime for computer accounts.", + dest="computer_tgt_lifetime", type=int, action="store", + validators=[Range(min=MIN_TGT_LIFETIME, max=MAX_TGT_LIFETIME)]), + ] + + def run(self, ldap_url=None, sambaopts=None, credopts=None, name=None, + description=None, protect=None, unprotect=None, audit=None, + enforce=None, strong_ntlm_policy=None, user_tgt_lifetime=None, + user_allow_ntlm_auth=None, service_tgt_lifetime=None, + service_allow_ntlm_auth=None, computer_tgt_lifetime=None): + + if not name: + raise CommandError("Argument --name is required.") + if protect and unprotect: + raise CommandError("--protect and --unprotect cannot be used together.") + if audit and enforce: + raise CommandError("--audit and --enforce cannot be used together.") + + self.ldb = self.ldb_connect(ldap_url, sambaopts, credopts) + + # Make sure authentication policy doesn't already exist. + policy = AuthenticationPolicy.get(self.ldb, cn=name) + if policy is not None: + raise CommandError(f"Authentication policy {name} already exists.") + + # New policy object. + policy = AuthenticationPolicy( + cn=name, + description=description, + strong_ntlm_policy=StrongNTLMPolicy[strong_ntlm_policy.upper()], + user_allow_ntlm_auth=user_allow_ntlm_auth, + user_tgt_lifetime=user_tgt_lifetime, + service_allow_ntlm_auth=service_allow_ntlm_auth, + service_tgt_lifetime=service_tgt_lifetime, + computer_tgt_lifetime=computer_tgt_lifetime, + ) + + # Either --enforce will be set or --audit but never both. + # The default if both are missing is enforce=True. + if enforce is not None: + policy.enforced = enforce + else: + policy.enforced = not audit + + # Create policy. + try: + policy.save(self.ldb) + + if protect: + policy.protect(self.ldb) + except LdbError as e: + raise CommandError(e) + + # Authentication policy created successfully. + self.outf.write(f"Created authentication policy: {name}\n") + + +class cmd_domain_auth_policy_modify(SiloCommand): + """Modify authentication policies on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--name", help="Name of authentication policy (required).", + dest="name", action="store", type=str), + Option("--description", + help="Optional description for authentication policy.", + dest="description", action="store", type=str), + Option("--protect", + help="Protect authentication silo from accidental deletion.", + dest="protect", action="store_true"), + Option("--unprotect", + help="Unprotect authentication silo from accidental deletion.", + dest="unprotect", action="store_true"), + Option("--audit", + help="Only audit authentication policy.", + dest="audit", action="store_true"), + Option("--enforce", + help="Enforce authentication policy.", + dest="enforce", action="store_true"), + Option("--strong-ntlm-policy", + help=f"Strong NTLM Policy ({StrongNTLMPolicy.choices_str()}).", + dest="strong_ntlm_policy", type="choice", action="store", + choices=StrongNTLMPolicy.get_choices()), + Option("--user-tgt-lifetime", + help="Ticket-Granting-Ticket lifetime for user accounts.", + dest="user_tgt_lifetime", type=int, action="store", + validators=[Range(min=MIN_TGT_LIFETIME, max=MAX_TGT_LIFETIME)]), + Option("--user-allow-ntlm-auth", + help="Allow NTLM network authentication when user " + "is restricted to selected devices.", + dest="user_allow_ntlm_auth", action="store_true", + default=False), + Option("--service-tgt-lifetime", + help="Ticket-Granting-Ticket lifetime for service accounts.", + dest="service_tgt_lifetime", type=int, action="store", + validators=[Range(min=MIN_TGT_LIFETIME, max=MAX_TGT_LIFETIME)]), + Option("--service-allow-ntlm-auth", + help="Allow NTLM network authentication when service " + "is restricted to selected devices.", + dest="service_allow_ntlm_auth", action="store_true", + default=False), + Option("--computer-tgt-lifetime", + help="Ticket-Granting-Ticket lifetime for computer accounts.", + dest="computer_tgt_lifetime", type=int, action="store", + validators=[Range(min=MIN_TGT_LIFETIME, max=MAX_TGT_LIFETIME)]), + ] + + def run(self, ldap_url=None, sambaopts=None, credopts=None, name=None, + description=None, protect=None, unprotect=None, audit=None, + enforce=None, strong_ntlm_policy=None, user_tgt_lifetime=None, + user_allow_ntlm_auth=None, service_tgt_lifetime=None, + service_allow_ntlm_auth=None, computer_tgt_lifetime=None): + + if not name: + raise CommandError("Argument --name is required.") + if protect and unprotect: + raise CommandError("--protect and --unprotect cannot be used together.") + if audit and enforce: + raise CommandError("--audit and --enforce cannot be used together.") + + self.ldb = self.ldb_connect(ldap_url, sambaopts, credopts) + + # Check if authentication policy exists. + policy = AuthenticationPolicy.get(self.ldb, cn=name) + if policy is None: + raise CommandError(f"Authentication policy {name} not found.") + + # Either --enforce will be set or --audit but never both. + if enforce: + policy.enforced = True + elif audit: + policy.enforced = False + + # Update the description. + if description is not None: + policy.description = description + + # User sign on + ############### + + if strong_ntlm_policy is not None: + policy.strong_ntlm_policy = \ + StrongNTLMPolicy[strong_ntlm_policy.upper()] + + if user_tgt_lifetime is not None: + policy.user_tgt_lifetime = user_tgt_lifetime + + # Service sign on + ################## + + if service_tgt_lifetime is not None: + policy.service_tgt_lifetime = service_tgt_lifetime + + # Computer + ########### + + if computer_tgt_lifetime is not None: + policy.computer_tgt_lifetime = computer_tgt_lifetime + + # Update policy. + try: + policy.save(self.ldb) + + if protect: + policy.protect(self.ldb) + elif unprotect: + policy.unprotect(self.ldb) + except LdbError as e: + raise CommandError(e) + + # Authentication policy updated successfully. + self.outf.write(f"Updated authentication policy: {name}\n") + + +class cmd_domain_auth_policy_delete(SiloCommand): + """Delete authentication policies on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--name", help="Name of authentication policy (required).", + dest="name", action="store", type=str), + Option("--force", help="Force delete protected authentication policy.", + dest="force", action="store_true") + ] + + def run(self, ldap_url=None, sambaopts=None, credopts=None, name=None, + force=None): + + if not name: + raise CommandError("Argument --name is required.") + + self.ldb = self.ldb_connect(ldap_url, sambaopts, credopts) + + # Check if authentication policy exists first. + policy = AuthenticationPolicy.get(self.ldb, cn=name) + if policy is None: + raise CommandError(f"Authentication policy {name} not found.") + + # Delete item, --force removes delete protection first. + try: + if force: + policy.unprotect(self.ldb) + + policy.delete(self.ldb) + except LdbError as e: + if not force: + raise CommandError( + f"{e}\nTry --force to delete protected authentication policies.") + else: + raise CommandError(e) + + # Authentication policy deleted successfully. + self.outf.write(f"Deleted authentication policy: {name}\n") + + +class cmd_domain_auth_policy(SuperCommand): + """Manage authentication policies on the domain.""" + + subcommands = { + "list": cmd_domain_auth_policy_list(), + "view": cmd_domain_auth_policy_view(), + "create": cmd_domain_auth_policy_create(), + "modify": cmd_domain_auth_policy_modify(), + "delete": cmd_domain_auth_policy_delete(), + } diff --git a/python/samba/netcmd/domain/auth/silo.py b/python/samba/netcmd/domain/auth/silo.py new file mode 100644 index 00000000000..a7017e1dfe1 --- /dev/null +++ b/python/samba/netcmd/domain/auth/silo.py @@ -0,0 +1,367 @@ +# Unix SMB/CIFS implementation. +# +# authentication silos - authentication silo management +# +# Copyright (C) Catalyst.Net Ltd. 2023 +# +# Written by Rob van der Linde <rob@catalyst.net.nz> +# +# 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 samba.getopt as options +from ldb import LdbError +from samba.netcmd import CommandError, Option, SuperCommand +from samba.netcmd.domain.models import AuthenticationSilo + +from .base import SiloCommand +from .silo_member import cmd_domain_auth_silo_member + + +class cmd_domain_auth_silo_list(SiloCommand): + """List authentication silos on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--json", help="Output results in JSON format.", + dest="output_format", action="store_const", const="json"), + ] + + def run(self, ldap_url=None, sambaopts=None, credopts=None, + output_format=None): + + self.ldb = self.ldb_connect(ldap_url, sambaopts, credopts) + + # Authentication silos grouped by cn. + silos = {silo.cn: silo.as_dict() + for silo in AuthenticationSilo.query(self.ldb)} + + # Using json output format gives more detail. + if output_format == "json": + self.print_json(silos) + else: + for silo in silos.keys(): + self.outf.write(f"{silo}\n") + + +class cmd_domain_auth_silo_view(SiloCommand): + """View an authentication silo on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--name", + help="Name of authentication silo to view (required).", + dest="name", action="store", type=str), + ] + + def run(self, ldap_url=None, sambaopts=None, credopts=None, name=None): + + if not name: + raise CommandError("Argument --name is required.") + + self.ldb = self.ldb_connect(ldap_url, sambaopts, credopts) + + # Check if silo exists first. + silo = AuthenticationSilo.get(self.ldb, cn=name) + if silo is None: + raise CommandError(f"Authentication silo {name} not found.") + + # Display silo as JSON. + self.print_json(silo.as_dict()) + + +class cmd_domain_auth_silo_create(SiloCommand): + """Create a new authentication silo on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--name", help="Name of authentication silo (required).", + dest="name", action="store", type=str), + Option("--description", + help="Optional description for authentication silo.", + dest="description", action="store", type=str), + Option("--policy", + help="Use single policy for all principals in this silo.", + dest="policy", action="store", type=str), + Option("--user-policy", + help="User account policy.", + dest="user_policy", action="store", type=str), + Option("--service-policy", + help="Managed Service Account policy.", + dest="service_policy", action="store", type=str), + Option("--computer-policy", + help="Computer account policy.", + dest="computer_policy", action="store", type=str), + Option("--protect", + help="Protect authentication silo from accidental deletion.", + dest="protect", action="store_true"), + Option("--unprotect", + help="Unprotect authentication silo from accidental deletion.", + dest="unprotect", action="store_true"), + Option("--audit", + help="Only audit silo policies.", + dest="audit", action="store_true"), + Option("--enforce", + help="Enforce silo policies.", + dest="enforce", action="store_true") + ] + + def run(self, ldap_url=None, sambaopts=None, credopts=None, name=None, + description=None, policy=None, user_policy=None, + service_policy=None, computer_policy=None, protect=None, + unprotect=None, audit=None, enforce=None): + + if not name: + raise CommandError("Argument --name is required.") + if protect and unprotect: + raise CommandError("--protect and --unprotect cannot be used together.") + if audit and enforce: + raise CommandError("--audit and --enforce cannot be used together.") + + # If --policy is present start with that as the base. Then optionally + # --user-policy, --service-policy, --computer-policy can override this. + if policy is not None: + user_policy = user_policy or policy + service_policy = service_policy or policy + computer_policy = computer_policy or policy + + self.ldb = self.ldb_connect(ldap_url, sambaopts, credopts) + + # Make sure silo doesn't already exist. + silo = AuthenticationSilo.get(self.ldb, cn=name) + if silo is not None: + raise CommandError(f"Authentication silo {name} already exists.") + + # New silo object. + silo = AuthenticationSilo(cn=name, description=description) + + # Set user policy + if user_policy: + silo.user_policy = self.get_policy(user_policy).dn + + # Set service policy + if service_policy: + silo.service_policy = self.get_policy(service_policy).dn + + # Set computer policy + if computer_policy: + silo.computer_policy = self.get_policy(computer_policy).dn + + # Either --enforce will be set or --audit but never both. + # The default if both are missing is enforce=True. + if enforce is not None: + silo.enforced = enforce + else: + silo.enforced = not audit + + # Create silo + try: + silo.save(self.ldb) + + if protect: + silo.protect(self.ldb) + except LdbError as e: + raise CommandError(e) + + # Authentication silo created successfully. + self.outf.write(f"Created authentication silo: {name}\n") + + +class cmd_domain_auth_silo_modify(SiloCommand): + """Modify an authentication silo on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--name", help="Name of authentication silo (required).", + dest="name", action="store", type=str), + Option("--description", + help="Optional description for authentication silo.", + dest="description", action="store", type=str), + Option("--policy", + help="Set single policy for all principals in this silo.", + dest="policy", action="store", type=str), + Option("--user-policy", + help="Set User account policy.", + dest="user_policy", action="store", type=str), + Option("--service-policy", + help="Set Managed Service Account policy.", + dest="service_policy", action="store", type=str), + Option("--computer-policy", + help="Set Computer Account policy.", + dest="computer_policy", action="store", type=str), + Option("--protect", + help="Protect authentication silo from accidental deletion.", + dest="protect", action="store_true"), + Option("--unprotect", + help="Unprotect authentication silo from accidental deletion.", + dest="unprotect", action="store_true"), + Option("--audit", + help="Only audit silo policies.", + dest="audit", action="store_true"), + Option("--enforce", + help="Enforce silo policies.", + dest="enforce", action="store_true") + ] + + def run(self, ldap_url=None, sambaopts=None, credopts=None, name=None, + description=None, policy=None, user_policy=None, + service_policy=None, computer_policy=None, protect=None, + unprotect=None, audit=None, enforce=None): + + if not name: + raise CommandError("Argument --name is required.") + if audit and enforce: + raise CommandError("--audit and --enforce cannot be used together.") + if protect and unprotect: + raise CommandError("--protect and --unprotect cannot be used together.") + + # If --policy is set then start with that for all policies. + # They can be individually overridden as well after that. + if policy is not None: + user_policy = user_policy or policy + service_policy = service_policy or policy + computer_policy = computer_policy or policy + + self.ldb = self.ldb_connect(ldap_url, sambaopts, credopts) + + # Check if silo exists first. + silo = AuthenticationSilo.get(self.ldb, cn=name) + if silo is None: + raise CommandError(f"Authentication silo {name} not found.") + + # Either --enforce will be set or --audit but never both. + if enforce: + silo.enforced = True + elif audit: + silo.enforced = False + + # Update the description. + if description is not None: + silo.description = description + + # Silo policies. + if user_policy is not None: + silo.user_policy = self.get_policy(user_policy).dn + if service_policy is not None: + silo.service_policy = self.get_policy(service_policy).dn + if computer_policy is not None: + silo.computer_policy = self.get_policy(computer_policy).dn + + # Update silo + try: + silo.save(self.ldb) + + if protect: + silo.protect(self.ldb) + elif unprotect: + silo.unprotect(self.ldb) + except LdbError as e: + raise CommandError(e) + + # Silo updated successfully. + self.outf.write(f"Updated authentication silo: {name}\n") + + +class cmd_domain_auth_silo_delete(SiloCommand): + """Delete an authentication silo on the domain.""" + + synopsis = "%prog -H <URL> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server.", + type=str, metavar="URL", dest="ldap_url"), + Option("--name", help="Name of authentication silo (required).", |
