provision: Changes to support encrypted_secrets module
authorGary Lockyer <gary@catalyst.net.nz>
Thu, 14 Dec 2017 18:24:14 +0000 (07:24 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Sun, 17 Dec 2017 23:10:17 +0000 (00:10 +0100)
Changes to provision and join to create a database with
encrypted_secrets enabled and a key file generated.

Also adds the --plaintext-secrets option to join and provision commands
to allow the creation of unencrypted databases.

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/join.py
python/samba/netcmd/domain.py
python/samba/provision/__init__.py
selftest/knownfail.d/encrypted_secrets
source4/dsdb/samdb/ldb_modules/samba_dsdb.c
source4/setup/provision_init.ldif

index 3aefb7baa9783e0301bc9ea6a8e24aa4efd32bd9..63e9b9010ed355fe0a0a28a4b630f2c07110efec 100644 (file)
@@ -55,7 +55,8 @@ class dc_join(object):
     def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
                  netbios_name=None, targetdir=None, domain=None,
                  machinepass=None, use_ntvfs=False, dns_backend=None,
-                 promote_existing=False, clone_only=False):
+                 promote_existing=False, clone_only=False,
+                 plaintext_secrets=False):
         if site is None:
             site = "Default-First-Site-Name"
 
@@ -67,6 +68,7 @@ class dc_join(object):
         ctx.site = site
         ctx.targetdir = targetdir
         ctx.use_ntvfs = use_ntvfs
+        ctx.plaintext_secrets = plaintext_secrets
 
         ctx.promote_existing = promote_existing
         ctx.promote_from_dn = None
@@ -837,7 +839,8 @@ class dc_join(object):
                 hostname=ctx.myname, domainsid=ctx.domsid,
                 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
                 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
-                use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend)
+                use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
+                plaintext_secrets=ctx.plaintext_secrets)
         print "Provision OK for domain DN %s" % presult.domaindn
         ctx.local_samdb = presult.samdb
         ctx.lp          = presult.lp
@@ -1398,11 +1401,12 @@ class dc_join(object):
 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
               targetdir=None, domain=None, domain_critical_only=False,
               machinepass=None, use_ntvfs=False, dns_backend=None,
-              promote_existing=False):
+              promote_existing=False, plaintext_secrets=False):
     """Join as a RODC."""
 
     ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
-                  machinepass, use_ntvfs, dns_backend, promote_existing)
+                  machinepass, use_ntvfs, dns_backend, promote_existing,
+                  plaintext_secrets)
 
     lp.set("workgroup", ctx.domain_name)
     logger.info("workgroup is %s" % ctx.domain_name)
@@ -1449,10 +1453,11 @@ def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_
 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
             targetdir=None, domain=None, domain_critical_only=False,
             machinepass=None, use_ntvfs=False, dns_backend=None,
-            promote_existing=False):
+            promote_existing=False, plaintext_secrets=False):
     """Join as a DC."""
     ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
-                  machinepass, use_ntvfs, dns_backend, promote_existing)
+                  machinepass, use_ntvfs, dns_backend, promote_existing,
+                  plaintext_secrets)
 
     lp.set("workgroup", ctx.domain_name)
     logger.info("workgroup is %s" % ctx.domain_name)
@@ -1498,10 +1503,10 @@ def join_clone(logger=None, server=None, creds=None, lp=None,
 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
         netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
         netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
-        dns_backend=None):
+        dns_backend=None, plaintext_secrets=False):
     """Join as a DC."""
     ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, parent_domain,
-                  machinepass, use_ntvfs, dns_backend)
+                  machinepass, use_ntvfs, dns_backend, plaintext_secrets)
     ctx.subdomain = True
     if adminpass is None:
         ctx.adminpass = samba.generate_random_password(12, 32)
index 6f6ef61c6aaf682710e2a5180738bbafd1d10562..6702bc3bcf0b6a98ae258c3eeb0bead7d282d9f7 100644 (file)
@@ -247,6 +247,9 @@ class cmd_domain_provision(Command):
          Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
                 help="List of LDAP-URLS [ ldap://<FQHN>:<PORT>/  (where <PORT> has to be different than 389!) ] separated with comma (\",\") for use with OpenLDAP-MMR (Multi-Master-Replication), e.g.: \"ldap://s4dc1:9000,ldap://s4dc2:9000\""),
          Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
+         Option("--plaintext-secrets", action="store_true",
+                help="Store secret/sensitive values as plain text on disk" +
+                     "(default is to encrypt secret/ensitive values)"),
         ]
 
     openldap_options = [
@@ -316,7 +319,8 @@ class cmd_domain_provision(Command):
             ldap_backend_extra_port=None,
             ldap_backend_forced_uri=None,
             ldap_dryrun_mode=None,
-            base_schema=None):
+            base_schema=None,
+            plaintext_secrets=False):
 
         self.logger = self.get_logger("provision")
         if quiet:
@@ -485,7 +489,8 @@ class cmd_domain_provision(Command):
                   ldap_backend_extra_port=ldap_backend_extra_port,
                   ldap_backend_forced_uri=ldap_backend_forced_uri,
                   nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
-                  base_schema=base_schema)
+                  base_schema=base_schema,
+                  plaintext_secrets=plaintext_secrets)
 
         except ProvisioningError, e:
             raise CommandError("Provision failed", e)
@@ -638,6 +643,9 @@ class cmd_domain_join(Command):
                    "BIND9_DLZ uses samba4 AD to store zone information, "
                    "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
                default="SAMBA_INTERNAL"),
+        Option("--plaintext-secrets", action="store_true",
+               help="Store secret/sensitive values as plain text on disk" +
+                    "(default is to encrypt secret/ensitive values)"),
         Option("--quiet", help="Be quiet", action="store_true"),
         Option("--verbose", help="Be verbose", action="store_true")
        ]
@@ -655,7 +663,7 @@ class cmd_domain_join(Command):
             versionopts=None, server=None, site=None, targetdir=None,
             domain_critical_only=False, parent_domain=None, machinepass=None,
             use_ntvfs=False, dns_backend=None, adminpass=None,
-            quiet=False, verbose=False):
+            quiet=False, verbose=False, plaintext_secrets=False):
         lp = sambaopts.get_loadparm()
         creds = credopts.get_credentials(lp)
         net = Net(creds, lp, server=credopts.ipaddress)
@@ -686,13 +694,16 @@ class cmd_domain_join(Command):
             join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
                     site=site, netbios_name=netbios_name, targetdir=targetdir,
                     domain_critical_only=domain_critical_only,
-                    machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
+                    machinepass=machinepass, use_ntvfs=use_ntvfs,
+                    dns_backend=dns_backend,
+                    plaintext_secrets=plaintext_secrets)
         elif role == "RODC":
             join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
                       site=site, netbios_name=netbios_name, targetdir=targetdir,
                       domain_critical_only=domain_critical_only,
                       machinepass=machinepass, use_ntvfs=use_ntvfs,
-                      dns_backend=dns_backend)
+                      dns_backend=dns_backend,
+                      plaintext_secrets=plaintext_secrets)
         elif role == "SUBDOMAIN":
             if not adminpass:
                 logger.info("Administrator password will be set randomly!")
@@ -705,7 +716,8 @@ class cmd_domain_join(Command):
                            netbios_name=netbios_name, netbios_domain=netbios_domain,
                            targetdir=targetdir, machinepass=machinepass,
                            use_ntvfs=use_ntvfs, dns_backend=dns_backend,
-                           adminpass=adminpass)
+                           adminpass=adminpass,
+                           plaintext_secrets=plaintext_secrets)
         else:
             raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
 
index 5de986463a5648d3cf6324fa9098162a31de5889..cc654f32a88afaa13247617b656655a5ea7b5c43 100644 (file)
@@ -29,6 +29,7 @@ __docformat__ = "restructuredText"
 from base64 import b64encode
 import errno
 import os
+import stat
 import re
 import pwd
 import grp
@@ -554,6 +555,9 @@ def provision_paths_from_lp(lp, dnsdomain):
     paths.kdcconf = os.path.join(paths.private_dir, "kdc.conf")
     paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
     paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
+    paths.encrypted_secrets_key_path = os.path.join(
+        paths.private_dir,
+        "encrypted_secrets.key")
 
     paths.dns = os.path.join(paths.binddns_dir, "dns", dnsdomain + ".zone")
     paths.namedconf = os.path.join(paths.binddns_dir, "named.conf")
@@ -792,7 +796,7 @@ def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
 
 def setup_samdb_partitions(samdb_path, logger, lp, session_info,
                            provision_backend, names, serverrole,
-                           erase=False):
+                           erase=False, plaintext_secrets=False):
     """Setup the partitions for the SAM database.
 
     Alternatively, provision() may call this, and then populate the database.
@@ -821,6 +825,10 @@ def setup_samdb_partitions(samdb_path, logger, lp, session_info,
     if provision_backend.type != "ldb":
         ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
 
+    required_features = "# No required features"
+    if not plaintext_secrets:
+        required_features = "requiredFeatures: encryptedSecrets"
+
     samdb.transaction_start()
     try:
         logger.info("Setting up sam.ldb partitions and settings")
@@ -831,7 +839,8 @@ def setup_samdb_partitions(samdb_path, logger, lp, session_info,
 
         setup_add_ldif(samdb, setup_path("provision_init.ldif"), {
                 "BACKEND_TYPE": provision_backend.type,
-                "SERVER_ROLE": serverrole
+                "SERVER_ROLE": serverrole,
+                "REQUIRED_FEATURES": required_features
                 })
 
         logger.info("Setting up sam.ldb rootDSE")
@@ -1006,6 +1015,30 @@ def setup_privileges(path, session_info, lp):
     privilege_ldb.erase()
     privilege_ldb.load_ldif_file_add(setup_path("provision_privilege.ldif"))
 
+def setup_encrypted_secrets_key(path):
+    """Setup the encrypted secrets key file.
+
+    Any existing key file will be deleted and a new random key generated.
+
+    :param path: Path to the secrets key file.
+
+    """
+    if os.path.exists(path):
+        os.unlink(path)
+
+    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
+    mode = stat.S_IRUSR | stat.S_IWUSR
+
+    umask_original = os.umask(0)
+    try:
+        fd = os.open(path, flags, mode)
+    finally:
+        os.umask(umask_original)
+
+    with os.fdopen(fd, 'w') as f:
+        key = samba.generate_random_bytes(16)
+        f.write(key)
+
 
 def setup_registry(path, session_info, lp):
     """Setup the registry.
@@ -1193,7 +1226,8 @@ def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
 
 
 def setup_samdb(path, session_info, provision_backend, lp, names,
-        logger, fill, serverrole, schema, am_rodc=False):
+        logger, fill, serverrole, schema, am_rodc=False,
+        plaintext_secrets=False):
     """Setup a complete SAM Database.
 
     :note: This will wipe the main SAM database file!
@@ -1202,7 +1236,7 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
     # Also wipes the database
     setup_samdb_partitions(path, logger=logger, lp=lp,
         provision_backend=provision_backend, session_info=session_info,
-        names=names, serverrole=serverrole)
+        names=names, serverrole=serverrole, plaintext_secrets=plaintext_secrets)
 
     # Load the database, but don's load the global schema and don't connect
     # quite yet
@@ -1975,7 +2009,8 @@ def provision(logger, session_info, smbconf=None,
         useeadb=False, am_rodc=False, lp=None, use_ntvfs=False,
         use_rfc2307=False, maxuid=None, maxgid=None, skip_sysvolacl=True,
         ldap_backend_forced_uri=None, nosync=False, ldap_dryrun_mode=False,
-        ldap_backend_extra_port=None, base_schema=None):
+        ldap_backend_extra_port=None, base_schema=None,
+        plaintext_secrets=False):
     """Provision samba4
 
     :note: caution, this wipes all existing data!
@@ -2101,6 +2136,8 @@ def provision(logger, session_info, smbconf=None,
     directory_create_or_exists(paths.binddns_dir, 0o770)
     directory_create_or_exists(os.path.join(paths.private_dir, "tls"))
     directory_create_or_exists(paths.state_dir)
+    if not plaintext_secrets:
+        setup_encrypted_secrets_key(paths.encrypted_secrets_key_path)
 
     if paths.sysvol and not os.path.exists(paths.sysvol):
         os.makedirs(paths.sysvol, 0775)
@@ -2172,7 +2209,8 @@ def provision(logger, session_info, smbconf=None,
         samdb = setup_samdb(paths.samdb, session_info,
                             provision_backend, lp, names, logger=logger,
                             serverrole=serverrole,
-                            schema=schema, fill=samdb_fill, am_rodc=am_rodc)
+                            schema=schema, fill=samdb_fill, am_rodc=am_rodc,
+                            plaintext_secrets=plaintext_secrets)
 
         if serverrole == "active directory domain controller":
             if paths.netlogon is None:
index 4fbc434f58a5f6e2f1e2ed3211831c6023d4e2b9..a8452fb447784f2be05f75b40dc80e839c71af03 100644 (file)
@@ -4,9 +4,5 @@
 #   * --plaintext-secrets option correctly provisions a domain
 #   * the dsdb operational module correctly handles unencrypted secrets
 #   * secrets are not stored as encrypted text when this option is specified
-^samba.tests.encrypted_secrets.samba.tests.encrypted_secrets.EncryptedSecretsTests.test_encrypted_secrets\(fl2000dc:local\)
-^samba.tests.encrypted_secrets.samba.tests.encrypted_secrets.EncryptedSecretsTests.test_required_features\(fl2000dc:local\)
-
-# Tests that will pass as the remaining patches in the set are added
-^samba.tests.encrypted_secrets.samba.tests.encrypted_secrets.EncryptedSecretsTests.test_encrypted_secrets
-^samba.tests.encrypted_secrets.samba.tests.encrypted_secrets.EncryptedSecretsTests.test_required_features
+#^samba.tests.encrypted_secrets.samba.tests.encrypted_secrets.EncryptedSecretsTests.test_encrypted_secrets\(fl2000dc:local\)
+#^samba.tests.encrypted_secrets.samba.tests.encrypted_secrets.EncryptedSecretsTests.test_required_features\(fl2000dc:local\)
index 87d65bd8c2b8fe3832aac353f199b89eacecbf2a..e0acb4e371a9d63b838afca27334981f057a358a 100644 (file)
@@ -228,6 +228,21 @@ static int set_ldap_credentials(struct ldb_context *ldb, bool use_external)
        return LDB_SUCCESS;
 }
 
+static bool check_required_features(struct ldb_message_element *el)
+{
+       if (el != NULL) {
+               int k;
+               DATA_BLOB esf = data_blob_string_const(
+                       SAMBA_ENCRYPTED_SECRETS_FEATURE);
+               for (k = 0; k < el->num_values; k++) {
+                       if (data_blob_cmp(&esf, &el->values[k]) != 0) {
+                               return false;
+                       }
+               }
+       }
+       return true;
+}
+
 static int samba_dsdb_init(struct ldb_module *module)
 {
        struct ldb_context *ldb = ldb_module_get_ctx(module);
@@ -294,6 +309,7 @@ static int samba_dsdb_init(struct ldb_module *module)
                "rdn_name",
                "subtree_delete",
                "repl_meta_data",
+               "encrypted_secrets",
                "operational",
                "unique_object_sids",
                "subtree_rename",
@@ -375,11 +391,14 @@ static int samba_dsdb_init(struct ldb_module *module)
                backendType = ldb_msg_find_attr_as_string(res->msgs[0], "backendType", "ldb");
 
                requiredFeatures = ldb_msg_find_element(res->msgs[0], SAMBA_REQUIRED_FEATURES_ATTR);
-               if (requiredFeatures != NULL) {
-                       ldb_set_errstring(ldb, "This Samba database was created with "
-                                         "a newer Samba version and is marked with "
-                                         "requiredFeatures in @SAMBA_DSDB.  "
-                                         "This database can not safely be read by this Samba version");
+               if (!check_required_features(requiredFeatures)) {
+                       ldb_set_errstring(
+                               ldb,
+                               "This Samba database was created with "
+                               "a newer Samba version and is marked "
+                               "with extra requiredFeatures in "
+                               "@SAMBA_DSDB. This database can not "
+                               "safely be read by this Samba version");
                        return LDB_ERR_OPERATIONS_ERROR;
                }
 
index 486458aaf5a8479ac38f1b2d4125c9febe590375..1d0868726827d6d9171f4a0bf54a84649bdccb3c 100644 (file)
@@ -28,6 +28,7 @@ dn: @SAMBA_DSDB
 backendType: ${BACKEND_TYPE}
 serverRole: ${SERVER_ROLE}
 compatibleFeatures: sortedLinks
+${REQUIRED_FEATURES}
 
 dn: @MODULES
 @LIST: samba_dsdb