1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-23 17:34:34 +03:00

r13107: Follow the lead of Heimdal's kpasswdd and use the HDB (hdb-ldb in our

case) as the keytab.

This avoids issues in replicated setups, as we will replicate the
kpasswd key correctly (including from windows, which is why I care at
the moment).

Andrew Bartlett
(This used to be commit 849500d1aa)
This commit is contained in:
Andrew Bartlett 2006-01-24 05:31:08 +00:00 committed by Gerald (Jerry) Carter
parent fc29c3250a
commit 28d78c40ad
15 changed files with 369 additions and 34 deletions

View File

@ -267,17 +267,12 @@ NTSTATUS cli_credentials_set_secrets(struct cli_credentials *cred,
cli_credentials_set_nt_hash(cred, &hash, CRED_SPECIFIED); cli_credentials_set_nt_hash(cred, &hash, CRED_SPECIFIED);
} else { } else {
cli_credentials_set_password(cred, NULL, CRED_SPECIFIED);
DEBUG(1, ("Could not find 'secret' in join record to domain: %s\n",
cli_credentials_get_domain(cred)));
/* set anonymous as the fallback, if the machine account won't work */
cli_credentials_set_anonymous(cred);
talloc_free(mem_ctx);
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
} }
} else {
cli_credentials_set_password(cred, password, CRED_SPECIFIED);
} }
domain = ldb_msg_find_string(msgs[0], "flatname", NULL); domain = ldb_msg_find_string(msgs[0], "flatname", NULL);
if (domain) { if (domain) {
@ -290,9 +285,6 @@ NTSTATUS cli_credentials_set_secrets(struct cli_credentials *cred,
} }
cli_credentials_set_username(cred, machine_account, CRED_SPECIFIED); cli_credentials_set_username(cred, machine_account, CRED_SPECIFIED);
if (password) {
cli_credentials_set_password(cred, password, CRED_SPECIFIED);
}
cli_credentials_set_kvno(cred, ldb_msg_find_int(msgs[0], "msDS-KeyVersionNumber", 0)); cli_credentials_set_kvno(cred, ldb_msg_find_int(msgs[0], "msDS-KeyVersionNumber", 0));
@ -417,13 +409,14 @@ NTSTATUS cli_credentials_update_all_keytabs(TALLOC_CTX *parent_ctx)
return NT_STATUS_ACCESS_DENIED; return NT_STATUS_ACCESS_DENIED;
} }
/* search for the secret record */ /* search for the secret record, but only of things we can
* actually update */
ldb_ret = gendb_search(ldb, ldb_ret = gendb_search(ldb,
mem_ctx, NULL, mem_ctx, NULL,
&msgs, attrs, &msgs, attrs,
"objectClass=kerberosSecret"); "(&(objectClass=kerberosSecret)(|(secret=*)(ntPwdHash=*)))");
if (ldb_ret == -1) { if (ldb_ret == -1) {
DEBUG(1, ("Error looking for kerberos type secrets to push into a keytab")); DEBUG(1, ("Error looking for kerberos type secrets to push into a keytab:: %s", ldb_errstring(ldb)));
talloc_free(mem_ctx); talloc_free(mem_ctx);
return NT_STATUS_INTERNAL_DB_CORRUPTION; return NT_STATUS_INTERNAL_DB_CORRUPTION;
} }
@ -442,15 +435,13 @@ NTSTATUS cli_credentials_update_all_keytabs(TALLOC_CTX *parent_ctx)
if (!NT_STATUS_IS_OK(status)) { if (!NT_STATUS_IS_OK(status)) {
DEBUG(1, ("Failed to read secrets for keytab update for %s\n", DEBUG(1, ("Failed to read secrets for keytab update for %s\n",
filter)); filter));
talloc_free(mem_ctx); continue;
return status;
} }
ret = cli_credentials_update_keytab(creds); ret = cli_credentials_update_keytab(creds);
if (ret != 0) { if (ret != 0) {
DEBUG(1, ("Failed to update keytab for %s\n", DEBUG(1, ("Failed to update keytab for %s\n",
filter)); filter));
talloc_free(mem_ctx); continue;
return NT_STATUS_UNSUCCESSFUL;
} }
} }
return NT_STATUS_OK; return NT_STATUS_OK;

View File

@ -43,6 +43,20 @@ int cli_credentials_get_krb5_context(struct cli_credentials *cred,
return 0; return 0;
} }
/* This needs to be called directly after the cli_credentials_init(),
* otherwise we might have problems with the krb5 context already
* being here.
*/
NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred,
struct smb_krb5_context *smb_krb5_context)
{
if (!talloc_reference(cred, smb_krb5_context)) {
return NT_STATUS_NO_MEMORY;
}
cred->smb_krb5_context = smb_krb5_context;
return NT_STATUS_OK;
}
int cli_credentials_set_from_ccache(struct cli_credentials *cred, int cli_credentials_set_from_ccache(struct cli_credentials *cred,
enum credentials_obtained obtained) enum credentials_obtained obtained)
{ {

View File

@ -397,9 +397,9 @@ static int create_keytab(TALLOC_CTX *parent_ctx,
const struct samr_Password *mach_pwd; const struct samr_Password *mach_pwd;
mach_pwd = cli_credentials_get_nt_hash(machine_account, mem_ctx); mach_pwd = cli_credentials_get_nt_hash(machine_account, mem_ctx);
if (!mach_pwd) { if (!mach_pwd) {
talloc_free(mem_ctx);
DEBUG(1, ("create_keytab: Domain trust informaton for account %s not available\n", DEBUG(1, ("create_keytab: Domain trust informaton for account %s not available\n",
cli_credentials_get_principal(machine_account, mem_ctx))); cli_credentials_get_principal(machine_account, mem_ctx)));
talloc_free(mem_ctx);
return EINVAL; return EINVAL;
} }
ret = krb5_keyblock_init(smb_krb5_context->krb5_context, ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
@ -410,6 +410,7 @@ static int create_keytab(TALLOC_CTX *parent_ctx,
DEBUG(1, ("create_keytab: krb5_keyblock_init failed: %s\n", DEBUG(1, ("create_keytab: krb5_keyblock_init failed: %s\n",
smb_get_krb5_error_message(smb_krb5_context->krb5_context, smb_get_krb5_error_message(smb_krb5_context->krb5_context,
ret, mem_ctx))); ret, mem_ctx)));
talloc_free(mem_ctx);
return ret; return ret;
} }
@ -516,6 +517,7 @@ static krb5_error_code remove_old_entries(TALLOC_CTX *parent_ctx,
switch (ret) { switch (ret) {
case 0: case 0:
break; break;
case HEIM_ERR_OPNOTSUPP:
case ENOENT: case ENOENT:
case KRB5_KT_END: case KRB5_KT_END:
/* no point enumerating if there isn't anything here */ /* no point enumerating if there isn't anything here */

View File

@ -448,6 +448,8 @@ static void smb_krb5_send_and_recv_close_func(krb5_context context, void *data)
return ret; return ret;
} }
(*smb_krb5_context)->krb5_context->mem_ctx = *smb_krb5_context;
talloc_steal(parent_ctx, *smb_krb5_context); talloc_steal(parent_ctx, *smb_krb5_context);
talloc_free(tmp_ctx); talloc_free(tmp_ctx);

View File

@ -490,6 +490,12 @@ hdb_ldapi_create (
HDB ** /*db*/, HDB ** /*db*/,
const char */*arg*/); const char */*arg*/);
krb5_error_code
hdb_ldb_create (
krb5_context /*context*/,
HDB ** /*db*/,
const char */*arg*/);
krb5_error_code krb5_error_code
hdb_list_builtin ( hdb_list_builtin (
krb5_context /*context*/, krb5_context /*context*/,

View File

@ -54,6 +54,9 @@ static struct hdb_method methods[] = {
#if defined(OPENLDAP) && !defined(OPENLDAP_MODULE) #if defined(OPENLDAP) && !defined(OPENLDAP_MODULE)
{"ldap:", hdb_ldap_create}, {"ldap:", hdb_ldap_create},
{"ldapi:", hdb_ldapi_create}, {"ldapi:", hdb_ldapi_create},
#endif
#ifdef _SAMBA_BUILD_
{"ldb:", hdb_ldb_create},
#endif #endif
{NULL, NULL} {NULL, NULL}
}; };
@ -395,6 +398,6 @@ hdb_create(krb5_context context, HDB **db, const char *filename)
h = find_dynamic_method (context, filename, &residual); h = find_dynamic_method (context, filename, &residual);
#endif #endif
if (h == NULL) if (h == NULL)
krb5_errx(context, 1, "No database support! (hdb_create)"); krb5_errx(context, 1, "No database support! (hdb_create(%s))", filename);
return (*h->create)(context, db, residual); return (*h->create)(context, db, residual);
} }

View File

@ -0,0 +1,276 @@
/*
* Copyright (c) 1999 - 2002 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "hdb_locl.h"
/* keytab backend for HDB databases */
RCSID("$Id: keytab.c,v 1.8 2005/12/12 12:35:36 lha Exp $");
struct hdb_data {
char *dbname;
char *mkey;
};
/*
* the format for HDB keytabs is:
* HDB:[database:file:mkey]
*/
static krb5_error_code
hdb_resolve(krb5_context context, const char *name, krb5_keytab id)
{
struct hdb_data *d;
const char *db, *mkey;
d = malloc(sizeof(*d));
if(d == NULL) {
krb5_set_error_string(context, "malloc: out of memory");
return ENOMEM;
}
db = name;
mkey = strrchr(name, ':');
if(mkey == NULL || mkey[1] == '\0') {
if(*name == '\0')
d->dbname = NULL;
else {
d->dbname = strdup(name);
if(d->dbname == NULL) {
free(d);
krb5_set_error_string(context, "malloc: out of memory");
return ENOMEM;
}
}
d->mkey = NULL;
} else {
if((mkey - db) == 0) {
d->dbname = NULL;
} else {
d->dbname = malloc(mkey - db);
if(d->dbname == NULL) {
free(d);
krb5_set_error_string(context, "malloc: out of memory");
return ENOMEM;
}
memmove(d->dbname, db, mkey - db);
d->dbname[mkey - db] = '\0';
}
d->mkey = strdup(mkey + 1);
if(d->mkey == NULL) {
free(d->dbname);
free(d);
krb5_set_error_string(context, "malloc: out of memory");
return ENOMEM;
}
}
id->data = d;
return 0;
}
static krb5_error_code
hdb_close(krb5_context context, krb5_keytab id)
{
struct hdb_data *d = id->data;
free(d->dbname);
free(d->mkey);
free(d);
return 0;
}
static krb5_error_code
hdb_get_name(krb5_context context,
krb5_keytab id,
char *name,
size_t namesize)
{
struct hdb_data *d = id->data;
snprintf(name, namesize, "%s%s%s",
d->dbname ? d->dbname : "",
(d->dbname || d->mkey) ? ":" : "",
d->mkey ? d->mkey : "");
return 0;
}
static void
set_config (krb5_context context,
const krb5_config_binding *binding,
const char **dbname,
const char **mkey)
{
*dbname = krb5_config_get_string(context, binding, "dbname", NULL);
*mkey = krb5_config_get_string(context, binding, "mkey_file", NULL);
}
/*
* try to figure out the database (`dbname') and master-key (`mkey')
* that should be used for `principal'.
*/
static void
find_db (krb5_context context,
const char **dbname,
const char **mkey,
krb5_const_principal principal)
{
const krb5_config_binding *top_bind = NULL;
const krb5_config_binding *default_binding = NULL;
const krb5_config_binding *db;
krb5_realm *prealm = krb5_princ_realm(context, rk_UNCONST(principal));
*dbname = *mkey = NULL;
while ((db =
krb5_config_get_next(context,
NULL,
&top_bind,
krb5_config_list,
"kdc",
"database",
NULL)) != NULL) {
const char *p;
p = krb5_config_get_string (context, db, "realm", NULL);
if (p == NULL) {
if(default_binding) {
krb5_warnx(context, "WARNING: more than one realm-less "
"database specification");
krb5_warnx(context, "WARNING: using the first encountered");
} else
default_binding = db;
} else if (strcmp (*prealm, p) == 0) {
set_config (context, db, dbname, mkey);
break;
}
}
if (*dbname == NULL && default_binding != NULL)
set_config (context, default_binding, dbname, mkey);
if (*dbname == NULL)
*dbname = HDB_DEFAULT_DB;
}
/*
* find the keytab entry in `id' for `principal, kvno, enctype' and return
* it in `entry'. return 0 or an error code
*/
static krb5_error_code
hdb_get_entry(krb5_context context,
krb5_keytab id,
krb5_const_principal principal,
krb5_kvno kvno,
krb5_enctype enctype,
krb5_keytab_entry *entry)
{
hdb_entry_ex ent;
krb5_error_code ret;
struct hdb_data *d = id->data;
int i;
HDB *db;
const char *dbname = d->dbname;
const char *mkey = d->mkey;
if (dbname == NULL)
find_db (context, &dbname, &mkey, principal);
ret = hdb_create (context, &db, dbname);
if (ret)
return ret;
ret = hdb_set_master_keyfile (context, db, mkey);
if (ret) {
(*db->hdb_destroy)(context, db);
return ret;
}
ret = (*db->hdb_open)(context, db, O_RDONLY, 0);
if (ret) {
(*db->hdb_destroy)(context, db);
return ret;
}
ret = (*db->hdb_fetch)(context, db, HDB_F_DECRYPT, principal, HDB_ENT_TYPE_SERVER, &ent);
/* Shutdown the hdb on error */
if(ret == HDB_ERR_NOENTRY) {
(*db->hdb_close)(context, db);
(*db->hdb_destroy)(context, db);
return KRB5_KT_NOTFOUND;
} else if (ret) {
(*db->hdb_close)(context, db);
(*db->hdb_destroy)(context, db);
return ret;
}
if(kvno && ent.entry.kvno != kvno) {
/* The order here matters, we must free these in this order
* due to hdb-ldb and Samba4's talloc */
hdb_free_entry(context, &ent);
(*db->hdb_close)(context, db);
(*db->hdb_destroy)(context, db);
return KRB5_KT_NOTFOUND;
}
if(enctype == 0)
if(ent.entry.keys.len > 0)
enctype = ent.entry.keys.val[0].key.keytype;
ret = KRB5_KT_NOTFOUND;
for(i = 0; i < ent.entry.keys.len; i++) {
if(ent.entry.keys.val[i].key.keytype == enctype) {
krb5_copy_principal(context, principal, &entry->principal);
entry->vno = ent.entry.kvno;
krb5_copy_keyblock_contents(context,
&ent.entry.keys.val[i].key,
&entry->keyblock);
ret = 0;
break;
}
}
/* The order here matters, we must free these in this order
* due to hdb-ldb and Samba4's talloc */
hdb_free_entry(context, &ent);
(*db->hdb_close)(context, db);
(*db->hdb_destroy)(context, db);
return ret;
}
krb5_kt_ops hdb_kt_ops = {
"HDB",
hdb_resolve,
hdb_get_name,
hdb_close,
hdb_get_entry,
NULL, /* start_seq_get */
NULL, /* next_entry */
NULL, /* end_seq_get */
NULL, /* add */
NULL /* remove */
};

View File

@ -451,6 +451,9 @@ typedef struct krb5_context_data {
int large_msg_size; int large_msg_size;
krb5_boolean fdns; /* Lookup hostnames to find full name, or send as-is */ krb5_boolean fdns; /* Lookup hostnames to find full name, or send as-is */
struct send_and_recv *send_and_recv; /* Alternate functions for KDC communication */ struct send_and_recv *send_and_recv; /* Alternate functions for KDC communication */
void *mem_ctx; /* Some parts of Samba4 need a valid
memory context (under the event
context) to use */
} krb5_context_data; } krb5_context_data;
enum { enum {

View File

@ -23,6 +23,7 @@ OBJ_FILES = \
../heimdal/lib/hdb/hdb.o \ ../heimdal/lib/hdb/hdb.o \
../heimdal/lib/hdb/ext.o \ ../heimdal/lib/hdb/ext.o \
../heimdal/lib/hdb/keys.o \ ../heimdal/lib/hdb/keys.o \
../heimdal/lib/hdb/keytab.o \
../heimdal/lib/hdb/mkey.o \ ../heimdal/lib/hdb/mkey.o \
../heimdal/lib/hdb/ndbm.o \ ../heimdal/lib/hdb/ndbm.o \
../heimdal/lib/hdb/asn1_Event.o \ ../heimdal/lib/hdb/asn1_Event.o \
@ -40,6 +41,7 @@ OBJ_FILES = \
../heimdal/lib/hdb/asn1_Salt.o \ ../heimdal/lib/hdb/asn1_Salt.o \
../heimdal/lib/hdb/asn1_hdb_entry.o \ ../heimdal/lib/hdb/asn1_hdb_entry.o \
../heimdal/lib/hdb/hdb_err.o ../heimdal/lib/hdb/hdb_err.o
REQUIRED_SUBSYSTEMS = HDB_LDB
NOPROTO = YES NOPROTO = YES
# End SUBSYSTEM HEIMDAL_HDB # End SUBSYSTEM HEIMDAL_HDB
####################### #######################

View File

@ -6,10 +6,21 @@
NOPROTO = YES NOPROTO = YES
OBJ_FILES = \ OBJ_FILES = \
kdc.o \ kdc.o \
pac-glue.o \
hdb-ldb.o \
kpasswdd.o kpasswdd.o
REQUIRED_SUBSYSTEMS = \ REQUIRED_SUBSYSTEMS = \
LIBLDB KERBEROS_LIB HEIMDAL_KDC HEIMDAL_HDB LIBLDB KERBEROS_LIB HEIMDAL_KDC HEIMDAL_HDB
# End SUBSYSTEM KDC # End SUBSYSTEM KDC
####################### #######################
#######################
# Start SUBSYSTEM KDC
[SUBSYSTEM::HDB_LDB]
NOPROTO = YES
OBJ_FILES = \
hdb-ldb.o \
pac-glue.o
REQUIRED_SUBSYSTEMS = \
LIBLDB KERBEROS_LIB HEIMDAL_HDB
# End SUBSYSTEM KDC
#######################

View File

@ -948,8 +948,13 @@ static krb5_error_code LDB_destroy(krb5_context context, HDB *db)
return 0; return 0;
} }
NTSTATUS hdb_ldb_create(TALLOC_CTX *mem_ctx, /* This interface is to be called by the KDC, which is expecting Samba
krb5_context context, struct HDB **db, const char *arg) * calling conventions. It is also called by a wrapper
* (hdb_ldb_create) from the kpasswdd -> krb5 -> keytab_hdb -> hdb
* code */
NTSTATUS kdc_hdb_ldb_create(TALLOC_CTX *mem_ctx,
krb5_context context, struct HDB **db, const char *arg)
{ {
NTSTATUS nt_status; NTSTATUS nt_status;
struct auth_session_info *session_info; struct auth_session_info *session_info;
@ -1008,3 +1013,15 @@ NTSTATUS hdb_ldb_create(TALLOC_CTX *mem_ctx,
return NT_STATUS_OK; return NT_STATUS_OK;
} }
krb5_error_code hdb_ldb_create(krb5_context context, struct HDB **db, const char *arg)
{
NTSTATUS nt_status;
/* Disgusting, ugly hack, but it means one less private hook */
nt_status = kdc_hdb_ldb_create(context->mem_ctx, context, db, arg);
if (NT_STATUS_IS_OK(nt_status)) {
return 0;
}
return EINVAL;
}

View File

@ -570,13 +570,18 @@ static void kdc_task_init(struct task_server *task)
} }
kdc->config->num_db = 1; kdc->config->num_db = 1;
status = hdb_ldb_create(kdc, kdc->smb_krb5_context->krb5_context, status = kdc_hdb_ldb_create(kdc, kdc->smb_krb5_context->krb5_context,
&kdc->config->db[0], NULL); &kdc->config->db[0], NULL);
if (!NT_STATUS_IS_OK(status)) { if (!NT_STATUS_IS_OK(status)) {
task_server_terminate(task, "kdc: hdb_ldb_create (setup KDC database) failed"); task_server_terminate(task, "kdc: hdb_ldb_create (setup KDC database) failed");
return; return;
} }
ret = krb5_kt_register(kdc->smb_krb5_context->krb5_context, &hdb_kt_ops);
if(ret) {
task_server_terminate(task, "kdc: failed to register hdb keytab");
return;
}
/* start listening on the configured network interfaces */ /* start listening on the configured network interfaces */
status = kdc_startup_interfaces(kdc); status = kdc_startup_interfaces(kdc);
if (!NT_STATUS_IS_OK(status)) { if (!NT_STATUS_IS_OK(status)) {

View File

@ -29,8 +29,8 @@
struct kdc_server; struct kdc_server;
NTSTATUS hdb_ldb_create(TALLOC_CTX *mem_ctx, NTSTATUS kdc_hdb_ldb_create(TALLOC_CTX *mem_ctx,
krb5_context context, struct HDB **db, const char *arg); krb5_context context, struct HDB **db, const char *arg);
BOOL kpasswdd_process(struct kdc_server *kdc, BOOL kpasswdd_process(struct kdc_server *kdc,
TALLOC_CTX *mem_ctx, TALLOC_CTX *mem_ctx,
DATA_BLOB *input, DATA_BLOB *input,

View File

@ -457,7 +457,10 @@ BOOL kpasswdd_process(struct kdc_server *kdc,
DEBUG(1, ("Failed to init server credentials\n")); DEBUG(1, ("Failed to init server credentials\n"));
return False; return False;
} }
/* We want the credentials subsystem to use the krb5 context
* we already have, rather than a new context */
cli_credentials_set_krb5_context(server_credentials, kdc->smb_krb5_context);
cli_credentials_set_conf(server_credentials); cli_credentials_set_conf(server_credentials);
nt_status = cli_credentials_set_stored_principal(server_credentials, "kadmin/changepw"); nt_status = cli_credentials_set_stored_principal(server_credentials, "kadmin/changepw");
if (!NT_STATUS_IS_OK(nt_status)) { if (!NT_STATUS_IS_OK(nt_status)) {

View File

@ -38,18 +38,18 @@ msDS-KeyVersionNumber: 1
objectSid: ${DOMAINSID} objectSid: ${DOMAINSID}
privateKeytab: secrets.keytab privateKeytab: secrets.keytab
# A hook from our credentials system into HDB, as we must be on a KDC,
# we can look directly into the database.
dn: samAccountName=krbtgt,flatname=${DOMAIN},CN=Principals dn: samAccountName=krbtgt,flatname=${DOMAIN},CN=Principals
objectClass: top objectClass: top
objectClass: secret objectClass: secret
objectClass: kerberosSecret objectClass: kerberosSecret
flatname: ${DOMAIN} flatname: ${DOMAIN}
realm: ${REALM} realm: ${REALM}
secret: ${KRBTGTPASS}
sAMAccountName: krbtgt sAMAccountName: krbtgt
whenCreated: ${LDAPTIME} whenCreated: ${LDAPTIME}
whenChanged: ${LDAPTIME} whenChanged: ${LDAPTIME}
msDS-KeyVersionNumber: 1
objectSid: ${DOMAINSID} objectSid: ${DOMAINSID}
servicePrincipalName: kadmin/changepw servicePrincipalName: kadmin/changepw
saltPrincipal: krbtgt@${REALM} krb5Keytab: HDB:ldb:sam.ldb:/dev/null
privateKeytab: secrets.keytab #The /dev/null here is a HACK, but it matches the Heimdal format.