os.makedirs(targetdir)
+# For '--no-secrets' backups, this sets the Administrator user's password to a
+# randomly-generated value. This is similar to the provision behaviour
+def set_admin_password(logger, samdb, username):
+ """Sets a randomly generated password for the backup DB's admin user"""
+
+ adminpass = samba.generate_random_password(12, 32)
+ logger.info("Setting %s password in backup to: %s" % (username, adminpass))
+ logger.info("Run 'samba-tool user setpassword %s' after restoring DB" %
+ username)
+ samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
+ % ldb.binary_encode(username), adminpass,
+ force_change_at_next_login=False,
+ username=username)
+
+
class cmd_domain_backup_online(samba.netcmd.Command):
'''Copy a running DC's current DB into a backup tar file.
Option("--server", help="The DC to backup", type=str),
Option("--targetdir", type=str,
help="Directory to write the backup file to"),
+ Option("--no-secrets", action="store_true", default=False,
+ help="Exclude secret values from the backup created")
]
- def run(self, sambaopts=None, credopts=None, server=None, targetdir=None):
+ def run(self, sambaopts=None, credopts=None, server=None, targetdir=None,
+ no_secrets=False):
logger = self.get_logger()
logger.setLevel(logging.DEBUG)
tmpdir = tempfile.mkdtemp(dir=targetdir)
# Run a clone join on the remote
+ include_secrets = not no_secrets
ctx = join_clone(logger=logger, creds=creds, lp=lp,
- include_secrets=True, dns_backend='SAMBA_INTERNAL',
- server=server, targetdir=tmpdir)
+ include_secrets=include_secrets, server=server,
+ dns_backend='SAMBA_INTERNAL', targetdir=tmpdir)
# get the paths used for the clone, then drop the old samdb connection
paths = ctx.paths
add_backup_marker(samdb, "backupDate", time_str)
add_backup_marker(samdb, "sidForRestore", new_sid)
+ # ensure the admin user always has a password set (same as provision)
+ if no_secrets:
+ set_admin_password(logger, samdb, creds.get_username())
+
# Add everything in the tmpdir to the backup tar file
backup_file = backup_filepath(targetdir, realm, time_str)
create_backup_tar(logger, tmpdir, backup_file)
type=str),
Option("--keep-dns-realm", action="store_true", default=False,
help="Retain the DNS entries for the old realm in the backup"),
+ Option("--no-secrets", action="store_true", default=False,
+ help="Exclude secret values from the backup created")
]
takes_args = ["new_domain_name", "new_dns_realm"]
samdb.transaction_commit()
def run(self, new_domain_name, new_dns_realm, sambaopts=None,
- credopts=None, server=None, targetdir=None, keep_dns_realm=False):
+ credopts=None, server=None, targetdir=None, keep_dns_realm=False,
+ no_secrets=False):
logger = self.get_logger()
logger.setLevel(logging.INFO)
# Clone and rename the remote server
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp)
+ include_secrets = not no_secrets
ctx = DCCloneAndRenameContext(new_base_dn, new_domain_name,
new_dns_realm, logger=logger,
- creds=creds, lp=lp, include_secrets=True,
+ creds=creds, lp=lp,
+ include_secrets=include_secrets,
dns_backend='SAMBA_INTERNAL',
server=server, targetdir=tmpdir)
ctx.do_join()
logger.info("Fixing DN attributes after rename...")
self.fix_old_dn_attributes(samdb)
+ # ensure the admin user always has a password set (same as provision)
+ if no_secrets:
+ set_admin_password(logger, samdb, creds.get_username())
+
# Add everything in the tmpdir to the backup tar file
backup_file = backup_filepath(targetdir, new_dns_realm, time_str)
create_backup_tar(logger, tmpdir, backup_file)
lp = self.check_restored_smbconf()
self.check_restored_database(lp)
+ def _test_backup_restore_no_secrets(self):
+ """Does a backup/restore with secrets excluded from the resulting DB"""
+
+ # exclude secrets when we create the backup
+ backup_file = self.create_backup(extra_args=["--no-secrets"])
+ self.restore_backup(backup_file)
+ lp = self.check_restored_smbconf()
+
+ # assert that we don't find user secrets in the DB
+ self.check_restored_database(lp, expect_secrets=False)
+
def create_smbconf(self, settings):
"""Creates a very basic smb.conf to pass to the restore tool"""
self.assertEqual(bkp_lp.get('state directory'), state_dir)
return bkp_lp
- def check_restored_database(self, bkp_lp):
+ def check_restored_database(self, bkp_lp, expect_secrets=True):
paths = provision.provision_paths_from_lp(bkp_lp, bkp_lp.get("realm"))
bkp_pd = get_prim_dom(paths.secrets, bkp_lp)
self.assert_partitions_present(samdb)
self.assert_dcs_present(samdb, self.new_server, expected_count=1)
self.assert_fsmo_roles(samdb, self.new_server, self.server)
+ self.assert_secrets(samdb, expect_secrets=expect_secrets)
return samdb
+ def assert_user_secrets(self, samdb, username, expect_secrets):
+ """Asserts that a user has/doesn't have secrets as expected"""
+ basedn = str(samdb.get_default_basedn())
+ user_dn = "CN=%s,CN=users,%s" % (username, basedn)
+
+ if expect_secrets:
+ self.assertIsNotNone(samdb.searchone("unicodePwd", user_dn))
+ else:
+ # the search should throw an exception because the secrets
+ # attribute isn't actually there
+ self.assertRaises(KeyError, samdb.searchone, "unicodePwd", user_dn)
+
+ def assert_secrets(self, samdb, expect_secrets):
+ """Check the user secrets in the restored DB match what's expected"""
+
+ # check secrets for the built-in testenv users match what's expected
+ test_users = ["alice", "bob", "jane"]
+ for user in test_users:
+ self.assert_user_secrets(samdb, user, expect_secrets)
+
def assert_fsmo_roles(self, samdb, server, exclude_server):
"""Asserts the expected server is the FSMO role owner"""
domain_dn = samdb.domain_dn()
out = self.check_output("samba-tool " + cmd)
print(out)
- def create_backup(self):
+ def create_backup(self, extra_args=None):
"""Runs the backup cmd to produce a backup file for the testenv DC"""
# Run the backup command and check we got one backup tar file
args = self.base_cmd + ["--server=" + self.server, self.user_auth,
"--targetdir=" + self.tempdir]
+ if extra_args:
+ args += extra_args
self.run_cmd(args)
def test_backup_restore_with_conf(self):
self._test_backup_restore_with_conf()
+ def test_backup_restore_no_secrets(self):
+ self._test_backup_restore_no_secrets()
+
class DomainBackupRename(DomainBackupBase):
def test_backup_restore_with_conf(self):
self._test_backup_restore_with_conf()
+ def test_backup_restore_no_secrets(self):
+ self._test_backup_restore_no_secrets()
+
def add_link(self, attr, source, target):
m = ldb.Message()
m.dn = ldb.Dn(self.ldb, source)
self.assertTrue(new_server_dn in res[0][link_attr])
# extra checks we run on the restored DB in the rename case
- def check_restored_database(self, lp):
+ def check_restored_database(self, lp, expect_secrets=True):
# run the common checks over the restored DB
- samdb = super(DomainBackupRename, self).check_restored_database(lp)
+ common_test = super(DomainBackupRename, self)
+ samdb = common_test.check_restored_database(lp, expect_secrets)
# check we have actually renamed the DNs
basedn = str(samdb.get_default_basedn())