[SCM] Samba Shared Repository - branch master updated
Andrew Bartlett
abartlet at samba.org
Thu Feb 29 02:39:01 UTC 2024
The branch, master has been updated
via 0fe263a56d0 pylibs: add string_is_guid() helper.
via 7b089e1206a samba-tool: with --json, error messages are in JSON
via 1f128fee27c samba-tool: instances remember whether --json was requested
via 542ba5cbd5e samba-tool: add self.print_json_status() helper
via 742fc4d841c samba-tool: avoid mutable Command class values
via 29abab6a460 samba-tool domain level: avoid using assert
via 8650ba0a187 samba-tool domain claim: use secrets module for token
via 2908a6d67bc samba-tool user getpassword: Also return the time a GMSA password is valid until
via 71f7c4a3c59 samba-tool: Allow ;format=UnixTime etc to operate on virtual attributes
via dfe71c4235a python/samba/tests: Include more detail on invoication in test of "samba-tool user show"
via 380c80b4d60 samba-tool user getpassword: Do not show preview of gMSA password
via 801e3fd6dd1 s3:libads: Trace ldap search base/filter/scope
from 2b515b7dcc6 s4-kdc: Add "Fresh Public Key Identity" SID if PKINIT freshness used
https://git.samba.org/?p=samba.git;a=shortlog;h=master
- Log -----------------------------------------------------------------
commit 0fe263a56d049b62be71ced9d8a78bc0a749c195
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Thu Feb 15 21:20:24 2024 +0000
pylibs: add string_is_guid() helper.
In various places we use regular expressions to check for GUID-ness,
though typically we don't match GUIDs with uppercase hex digits when
we really should.
If we centralise the check, we have more chance of getting it right.
Pair-programmed-by: Andrew Bartlett <abartlet at samba.org>
Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Signed-off-by: Andrew Bartlett <abartlet at samba.org>
Reviewed-by: Reviewed-by: Andrew Bartlett <abartlet at samba.org>
Autobuild-User(master): Andrew Bartlett <abartlet at samba.org>
Autobuild-Date(master): Thu Feb 29 02:38:07 UTC 2024 on atb-devel-224
commit 7b089e1206a8a8256ad108f5f0e03d3b33f8bf9f
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Wed Feb 28 16:14:24 2024 +1300
samba-tool: with --json, error messages are in JSON
Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
commit 1f128fee27c50aa305de3434443c4a52c408f9c6
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Wed Feb 28 16:13:15 2024 +1300
samba-tool: instances remember whether --json was requested
All our subcommands are going to learn --json eventually, and they
shouldn't all have to do this individually.
The next commit uses this to automatically format CommandErrors as JSON.
Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
commit 542ba5cbd5e9a562cd81b5b2385b56d03555a87f
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Fri Feb 16 00:59:25 2024 +0000
samba-tool: add self.print_json_status() helper
This is a helper to return JSON for simple messages.
Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
commit 742fc4d841c1b02cc733760e7841ca13a95f3ebc
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Fri Feb 23 16:19:02 2024 +1300
samba-tool: avoid mutable Command class values
These values are shared across all instances of the class,
which makes no difference in samba-tool itself, because there
is one instance per process. But in tests we can have many
Command classes at once (due to runcmd()), and if any of them
happened to append to takes_args or takes_options rather than
replacing it, well, the effect would be subtle.
Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
commit 29abab6a460aa61699c4a1811c148552874c1236
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Wed Feb 14 05:09:30 2024 +0000
samba-tool domain level: avoid using assert
Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
commit 8650ba0a187d4c0a05fd4596570b940431338a27
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date: Fri Feb 2 14:23:38 2024 +1300
samba-tool domain claim: use secrets module for token
`binascii.hexlify(os.urandom(8)).decode()` was fine, but `os.urandom`
is OS specific and can theoretically block (says the documentation).
We will let Python's secrets module worry about such details.
Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
commit 2908a6d67bca58c9de6991cbe312276408a34b7a
Author: Andrew Bartlett <abartlet at samba.org>
Date: Fri Feb 9 11:44:33 2024 +1300
samba-tool user getpassword: Also return the time a GMSA password is valid until
Signed-off-by: Andrew Bartlett <abartlet at samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
commit 71f7c4a3c59d170f3cf48c5230d3edf4d51d500c
Author: Andrew Bartlett <abartlet at samba.org>
Date: Wed Feb 28 17:27:31 2024 +1300
samba-tool: Allow ;format=UnixTime etc to operate on virtual attributes
To convert a virtual attribute we must understand that it has
been put into "obj" under the name including the ;format= part
and so we must look it back up with that name when looking to
covert it from (say) NTTIME to a unix time.
Signed-off-by: Andrew Bartlett <abartlet at samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
commit dfe71c4235ad9943ba55d5d192b168554be749b3
Author: Andrew Bartlett <abartlet at samba.org>
Date: Thu Feb 29 10:38:38 2024 +1300
python/samba/tests: Include more detail on invoication in test of "samba-tool user show"
Signed-off-by: Andrew Bartlett <abartlet at samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
commit 380c80b4d609e87af63045ab7695c81159d89311
Author: Andrew Bartlett <abartlet at samba.org>
Date: Fri Feb 2 16:10:06 2024 +1300
samba-tool user getpassword: Do not show preview of gMSA password
The AD server will send a preview of the next gMSA password, 5mins before
it is expected to be active.
This is useful in a keytab, which needs to be in place before a ticket
could possibly be issued, but is not helpful for authentication, as
the server also accepts passwords for 5mins after the change.
This avoids needing teach all users of this tool how to fall back to
the previous password for a 5min period every 30 days, by default.
Signed-off-by: Andrew Bartlett <abartlet at samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
commit 801e3fd6dd102e33f789cbe1e604b728aafc96bb
Author: Pavel Filipenský <pfilipensky at samba.org>
Date: Mon Feb 26 08:31:24 2024 +0100
s3:libads: Trace ldap search base/filter/scope
Signed-off-by: Pavel Filipenský <pfilipensky at samba.org>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
-----------------------------------------------------------------------
Summary of changes:
python/samba/__init__.py | 32 ++++++++++
python/samba/netcmd/__init__.py | 69 +++++++++++++++++++++-
python/samba/netcmd/domain/claim/claim_type.py | 6 +-
python/samba/netcmd/domain/level.py | 12 ++--
python/samba/netcmd/user/readpasswords/common.py | 38 ++++++++++--
python/samba/tests/samba_tool/user.py | 4 +-
.../tests/samba_tool/user_getpassword_gmsa.py | 24 +++++++-
source3/libads/ldap.c | 5 ++
8 files changed, 173 insertions(+), 17 deletions(-)
Changeset truncated at 500 lines:
diff --git a/python/samba/__init__.py b/python/samba/__init__.py
index 3e6ea7d18e1..3b253f7d79c 100644
--- a/python/samba/__init__.py
+++ b/python/samba/__init__.py
@@ -26,6 +26,7 @@ import os
import time
import ldb
import samba.param
+import re
from samba import _glue
from samba._ldb import Ldb as _Ldb
@@ -356,6 +357,37 @@ def arcfour_encrypt(key, data):
return arcfour_crypt_blob(data, key)
+GUID_RE = re.compile(
+ "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
+
+GUID_MIXCASE_RE = re.compile(
+ "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
+ flags = re.IGNORECASE)
+
+
+def string_is_guid(string, lower_case_only=False):
+ """Is the string an ordinary undecorated string GUID?
+
+ That is, like 12345678-abcd-1234-FEED-1234567890ab, and not like
+ variants which have surrounding curly brackets or lack hyphens.
+
+ If lower case_only is true, only lowercase hex characters are
+ accepted. This is tighter than we ever require, but matches what
+ we usually emit.
+ """
+ # Note: it is rightly tempting to use misc.GUID() here and catch
+ # the error, but misc.GUID is more forgiving than we want,
+ # allowing all kinds of weird variants.
+ if lower_case_only:
+ m = GUID_RE.fullmatch(string)
+ else:
+ m = GUID_MIXCASE_RE.fullmatch(string)
+
+ if m is None:
+ return False
+ return True
+
+
def enable_net_export_keytab():
"""This function modifies the samba.net.Net class to contain
an export_keytab() method."""
diff --git a/python/samba/netcmd/__init__.py b/python/samba/netcmd/__init__.py
index 7ddc1dc0828..01497055d68 100644
--- a/python/samba/netcmd/__init__.py
+++ b/python/samba/netcmd/__init__.py
@@ -82,8 +82,8 @@ class Command(object):
# synopsis must be defined in all subclasses in order to provide the
# command usage
synopsis = None
- takes_args = []
- takes_options = []
+ takes_args = ()
+ takes_options = ()
takes_optiongroups = {}
hidden = False
@@ -93,6 +93,7 @@ class Command(object):
raw_argv = None
raw_args = None
raw_kwargs = None
+ preferred_output_format = None
def _set_files(self, outf=None, errf=None):
if outf is not None:
@@ -108,6 +109,19 @@ class Command(object):
parser.print_usage()
def _print_error(self, msg, evalue=None, klass=None):
+ if self.preferred_output_format == 'json':
+ if evalue is None:
+ evalue = 1
+ else:
+ msg = f"{msg} - {evalue}"
+ if klass is not None:
+ kwargs = {'error class': klass}
+ else:
+ kwargs = {}
+
+ self.print_json_status(evalue, msg, **kwargs)
+ return
+
err = colour.c_DARK_RED("ERROR")
klass = '' if klass is None else f'({klass})'
@@ -155,6 +169,50 @@ class Command(object):
json.dump(data, self.outf, cls=JSONEncoder, indent=2, sort_keys=True)
self.outf.write("\n")
+ def print_json_status(self, error=None, message=None, **kwargs):
+ """For commands that really have nothing to say when they succeed
+ (`samba-tool foo delete --json`), we can still emit
+ '{"status": "OK"}\n'. And if they fail they can say:
+ '{"status": "error"}\n'.
+ This function hopes to keep things consistent.
+
+ If error is true-ish but not True, it is stringified and added
+ as a message. For example, if error is an LdbError with an
+ OBJECT_NOT_FOUND code, self.print_json_status(error) results
+ in this:
+
+ '{"status": "error", "message": "object not found"}\n'
+
+ unless an explicit message is added, in which case that is
+ used. A message can be provided on success, like this:
+
+ '{"status": "OK", "message": "thanks for asking!"}\n'
+
+ Extra keywords can be added too.
+
+ In summary, you might go:
+
+ try:
+ samdb.delete(dn)
+ except Exception as e:
+ print_json_status(e)
+ return
+ print_json_status()
+ """
+ data = {}
+ if error:
+ data['status'] = 'error'
+ if error is not True:
+ data['message'] = str(error)
+ else:
+ data['status'] = 'OK'
+
+ if message is not None:
+ data['message'] = message
+
+ data.update(kwargs)
+ self.print_json(data)
+
def show_command_error(self, e):
"""display a command error"""
if isinstance(e, CommandError):
@@ -256,6 +314,13 @@ class Command(object):
del kwargs[option.dest]
kwargs.update(optiongroups)
+ if kwargs.get('output_format') == 'json':
+ self.preferred_output_format = 'json'
+ else:
+ # we need to reset this for the tests that reuse the
+ # samba-tool object.
+ self.preferred_output_format = None
+
if self.use_colour:
self.apply_colour_choice(kwargs.pop('color', 'auto'))
diff --git a/python/samba/netcmd/domain/claim/claim_type.py b/python/samba/netcmd/domain/claim/claim_type.py
index 754f8b11117..72e98d33125 100644
--- a/python/samba/netcmd/domain/claim/claim_type.py
+++ b/python/samba/netcmd/domain/claim/claim_type.py
@@ -20,8 +20,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-import binascii
-import os
+import secrets
import samba.getopt as options
from samba.netcmd import Command, CommandError, Option, SuperCommand
@@ -97,8 +96,7 @@ class cmd_domain_claim_claim_type_create(Command):
# Generate the new Claim Type cn.
# Windows creates a random number here containing 16 hex digits.
- # We can achieve something similar using urandom(8)
- instance = binascii.hexlify(os.urandom(8)).decode()
+ instance = secrets.token_hex(8)
cn = f"ad://ext/{display_name}:{instance}"
# adminDescription should be present but still have a fallback.
diff --git a/python/samba/netcmd/domain/level.py b/python/samba/netcmd/domain/level.py
index eefe360563d..f4d257ac324 100644
--- a/python/samba/netcmd/domain/level.py
+++ b/python/samba/netcmd/domain/level.py
@@ -81,22 +81,26 @@ class cmd_domain_level(Command):
try:
res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
- assert len(res_forest) == 1
+ if len(res_forest) != 1:
+ raise CommandError("Forest not found")
res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
attrs=["msDS-Behavior-Version", "nTMixedDomain"])
- assert len(res_domain) == 1
+ if len(res_domain) != 1:
+ raise CommandError("domain not found")
res_domain_cross = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
scope=ldb.SCOPE_SUBTREE,
expression="(&(objectClass=crossRef)(nCName=%s))" % domain_dn,
attrs=["msDS-Behavior-Version"])
- assert len(res_domain_cross) == 1
+ if len(res_domain_cross) != 1:
+ raise CommandError("no crossRef objects found")
res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
attrs=["msDS-Behavior-Version"])
- assert len(res_dc_s) >= 1
+ if len(res_dc_s) == 0:
+ raise CommandError("no nTDSDSA objects found")
# default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
level_forest = DS_DOMAIN_FUNCTION_2000
diff --git a/python/samba/netcmd/user/readpasswords/common.py b/python/samba/netcmd/user/readpasswords/common.py
index 6d44881823d..8af8be7341e 100644
--- a/python/samba/netcmd/user/readpasswords/common.py
+++ b/python/samba/netcmd/user/readpasswords/common.py
@@ -22,6 +22,7 @@
import base64
import builtins
import binascii
+import datetime
import errno
import io
import os
@@ -34,7 +35,8 @@ from samba.dcerpc import drsblobs, security, gmsa
from samba.ndr import ndr_unpack
from samba.netcmd import Command, CommandError
from samba.samdb import SamDB
-
+from samba.nt_time import timedelta_from_nt_time_delta, nt_time_from_datetime
+from samba.gkdi import MAX_CLOCK_SKEW
# python[3]-gpgme is abandoned since ubuntu 1804 and debian 9
# have to use python[3]-gpg instead
@@ -102,6 +104,8 @@ virtual_attributes = {
"unicodePwd": {
"flags": ldb.ATTR_FLAG_FORCE_BASE64_LDIF,
},
+ "virtualManagedPasswordQueryTime": {
+ },
}
@@ -369,10 +373,28 @@ class GetPasswordCommand(Command):
managed_password = obj["msDS-ManagedPassword"][0]
unpacked_managed_password = ndr_unpack(gmsa.MANAGEDPASSWORD_BLOB,
managed_password)
- calculated["Primary:CLEARTEXT"] = \
- unpacked_managed_password.passwords.current
calculated["OLDCLEARTEXT"] = \
unpacked_managed_password.passwords.previous
+ query_interval = unpacked_managed_password.passwords.query_interval
+ calculated["GMSA:query_interval"] = \
+ query_interval
+
+ query_time_datetime = \
+ timedelta_from_nt_time_delta(query_interval) + datetime.datetime.now(tz=datetime.timezone.utc)
+
+ query_time_nttime = nt_time_from_datetime(query_time_datetime)
+
+ calculated["GMSA:query_time"] = query_time_nttime
+
+ # This password is useful for a keytab, but not for
+ # authentication, so don't show or provide it as the new password
+ # just yet
+ if calculated["GMSA:query_interval"] <= MAX_CLOCK_SKEW:
+ calculated["Primary:CLEARTEXT"] = \
+ unpacked_managed_password.passwords.previous
+ else:
+ calculated["Primary:CLEARTEXT"] = \
+ unpacked_managed_password.passwords.current
account_name = str(obj["sAMAccountName"][0])
if "userPrincipalName" in obj:
@@ -770,6 +792,10 @@ class GetPasswordCommand(Command):
v = get_wDigest(i, primary_wdigest, account_name, account_upn, domain, dns_domain)
if v is None:
continue
+ elif a == "virtualManagedPasswordQueryTime":
+ if "GMSA:query_time" not in calculated:
+ continue
+ v = str(calculated["GMSA:query_time"])
else:
continue
obj[a] = ldb.MessageElement(v, ldb.FLAG_MOD_REPLACE, vattr["raw_attr"])
@@ -840,10 +866,14 @@ class GetPasswordCommand(Command):
continue
if ra["vformat"] != fm:
continue
+
srcattr = get_src_attrname(ra["attr"])
+ if srcattr is not None:
+ an = "%s;format=%s" % (srcattr, fm)
+ else:
+ srcattr = an = get_src_attrname(ra["raw_attr"])
if srcattr is None:
continue
- an = "%s;format=%s" % (srcattr, fm)
if an in generated_formats:
continue
generated_formats[an] = fm
diff --git a/python/samba/tests/samba_tool/user.py b/python/samba/tests/samba_tool/user.py
index 26c9748ffc3..a19592a1f8c 100644
--- a/python/samba/tests/samba_tool/user.py
+++ b/python/samba/tests/samba_tool/user.py
@@ -600,7 +600,9 @@ sAMAccountName: %s
"-H", "ldap://%s" % os.environ["DC_SERVER"],
"-U%s%%%s" % (os.environ["DC_USERNAME"],
os.environ["DC_PASSWORD"]))
- self.assertCmdSuccess(result, out, err, "Error running show")
+ self.assertCmdSuccess(result, out, err,
+ "Error running show --attributes=%s"
+ % ",".join(attrs))
self.assertIn(";format=GeneralizedTime", out)
self.assertIn(";format=UnixTime", out)
diff --git a/python/samba/tests/samba_tool/user_getpassword_gmsa.py b/python/samba/tests/samba_tool/user_getpassword_gmsa.py
index acd8907e992..bbb68c41b7e 100644
--- a/python/samba/tests/samba_tool/user_getpassword_gmsa.py
+++ b/python/samba/tests/samba_tool/user_getpassword_gmsa.py
@@ -26,6 +26,8 @@ import os
sys.path.insert(0, "bin/python")
os.environ["PYTHONUNBUFFERED"] = "1"
+import datetime, shlex
+
from ldb import SCOPE_BASE
from samba.credentials import MUST_USE_KERBEROS
@@ -33,7 +35,8 @@ from samba.dcerpc import security, samr
from samba.dsdb import UF_WORKSTATION_TRUST_ACCOUNT
from samba.netcmd.domain.models import User
from samba.ndr import ndr_pack, ndr_unpack
-from samba.tests import connect_samdb, delete_force
+from samba.nt_time import nt_time_from_datetime
+from samba.tests import connect_samdb, connect_samdb_env, delete_force
from samba.tests import BlackboxTestCase
@@ -88,7 +91,8 @@ class GMSAPasswordTest(BlackboxTestCase):
cls.user = User.get(cls.samdb, username=cls.username)
def getpassword(self, attrs):
- cmd = f"user getpassword --attributes={attrs} {self.username}"
+ shattrs = shlex.quote(attrs)
+ cmd = f"user getpassword --attributes={shattrs} {self.username}"
ldif = self.check_output(cmd).decode()
res = self.samdb.parse_ldif(ldif)
@@ -152,6 +156,22 @@ class GMSAPasswordTest(BlackboxTestCase):
self.assertEqual(self.user.object_sid, connecting_user_sid)
+ def test_querytime(self):
+ user_msg = self.getpassword("virtualManagedPasswordQueryTime")
+ querytime = int(user_msg["virtualManagedPasswordQueryTime"][0])
+
+ # Just assert the number makes sense
+ self.assertGreater(querytime, nt_time_from_datetime(datetime.datetime.now(tz=datetime.timezone.utc)))
+ self.assertLess(querytime, nt_time_from_datetime(datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(hours = 21)))
+
+ def test_querytime_unixtime(self):
+ user_msg = self.getpassword("virtualManagedPasswordQueryTime;format=UnixTime")
+ querytime = int(user_msg["virtualManagedPasswordQueryTime;format=UnixTime"][0])
+
+ # Just assert the number makes sense
+ self.assertGreater(querytime, datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
+ self.assertLess(querytime, (datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(hours = 21)).timestamp())
+
@classmethod
def _make_cmdline(cls, line):
"""Override to pass line as samba-tool subcommand instead.
diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c
index 7f3c20746c8..ff67ad28a2a 100644
--- a/source3/libads/ldap.c
+++ b/source3/libads/ldap.c
@@ -154,6 +154,11 @@ static int ldap_search_with_timeout(LDAP *ld,
struct timeval *timeout_ptr = NULL;
int result;
+ DBG_DEBUG("ldap_search: base => [%s], filter => [%s], scope => [%d]\n",
+ base,
+ filter,
+ scope);
+
/* Setup timeout for the ldap_search_ext_s call - local and remote. */
gotalarm = 0;
--
Samba Shared Repository
More information about the samba-cvs
mailing list