Add trust-related ACIs

A high-level description of the design and ACIs for trusts is available at
https://www.redhat.com/archives/freeipa-devel/2011-December/msg00224.html
and
https://www.redhat.com/archives/freeipa-devel/2011-December/msg00248.html

Ticket #1731
This commit is contained in:
Alexander Bokovoy 2012-05-15 20:03:16 +03:00 committed by Martin Kosek
parent 000bcfe34f
commit bd0d858043
5 changed files with 196 additions and 81 deletions

View File

@ -26,6 +26,10 @@
#include <passdb.h>
#include <sasl/sasl.h>
#include <krb5/krb5.h>
#include <time.h>
/* TODO: remove if smbrunsecret() is removed */
typedef struct connection_structi {} connection_struct;
struct current_user {
@ -600,7 +604,6 @@ static bool ldapsam_sid_to_id(struct pdb_methods *methods,
*gid = strtoul(gid_str, NULL, 10);
*type = SID_NAME_DOM_GRP;
store_gid_sid_cache(sid, *gid);
ret = true;
goto done;
}
@ -617,7 +620,6 @@ static bool ldapsam_sid_to_id(struct pdb_methods *methods,
*uid = strtoul(value, NULL, 10);
*type = SID_NAME_USER;
store_uid_sid_cache(sid, *uid);
ret = true;
done:
@ -683,8 +685,6 @@ static bool ldapsam_uid_to_sid(struct pdb_methods *methods, uid_t uid,
sid_copy(sid, &user_sid);
store_uid_sid_cache(sid, uid);
ret = true;
done:
@ -748,8 +748,6 @@ static bool ldapsam_gid_to_sid(struct pdb_methods *methods, gid_t gid,
sid_copy(sid, &group_sid);
store_gid_sid_cache(sid, gid);
ret = true;
done:
@ -3048,6 +3046,115 @@ done:
return status;
}
struct ipasam_sasl_interact_priv {
krb5_context context;
krb5_principal principal;
krb5_keytab keytab;
krb5_get_init_creds_opt *options;
krb5_creds creds;
krb5_ccache ccache;
const char *name;
int name_len;
};
static int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *sit)
{
sasl_interact_t *in = NULL;
int ret = LDAP_OTHER;
struct ipasam_sasl_interact_priv *data = (struct ipasam_sasl_interact_priv*) priv_data;
krb5_context krbctx;
char *outname = NULL;
krb5_error_code krberr;
if (!ld) return LDAP_PARAM_ERROR;
for (in = sit; in && in->id != SASL_CB_LIST_END; in++) {
switch(in->id) {
case SASL_CB_USER:
in->result = data->name;
in->len = data->name_len;
ret = LDAP_SUCCESS;
break;
case SASL_CB_GETREALM:
in->result = data->principal->realm.data;
in->len = data->principal->realm.length;
ret = LDAP_SUCCESS;
break;
default:
in->result = NULL;
in->len = 0;
ret = LDAP_OTHER;
}
}
return ret;
}
extern const char *lp_parm_const_string(int snum, const char *type, const char *option, const char *def);
extern void become_root();
extern void unbecome_root();
static int bind_callback(LDAP *ldap_struct, struct smbldap_state *ldap_state)
{
char *ccache_name = NULL;
krb5_error_code rc;
struct ipasam_sasl_interact_priv data;
int ret;
data.name = lp_parm_const_string(-1, "ipasam", "principal", NULL);
if (data.name == NULL) {
DEBUG(0, ("bind_callback: ipasam:principal is not set, cannot use GSSAPI bind\n"));
return LDAP_LOCAL_ERROR;
}
/*
* In order to modify the ccache we need to wrap in become/unbecome root here
*/
become_root();
data.name_len = strlen(data.name);
rc = krb5_init_context(&data.context);
rc = krb5_parse_name(data.context, data.name, &data.principal);
DEBUG(0,("principal is %p (%d)\n", (void*) data.principal, rc));
rc = krb5_cc_default(data.context, &data.ccache);
rc = krb5_cc_initialize(data.context, data.ccache, data.principal);
rc = krb5_cc_get_full_name(data.context, data.ccache, &ccache_name);
rc = krb5_cc_set_default_name(data.context, ccache_name);
DEBUG(0, ("default ccache is %s\n", krb5_cc_default_name(data.context)));
rc = krb5_kt_resolve(data.context, "FILE:/etc/samba/samba.keytab", &data.keytab);
DEBUG(0,("keytab is %p (%d)\n", (void*) data.keytab, rc));
rc = krb5_get_init_creds_opt_alloc(data.context, &data.options);
DEBUG(0,("options are %p (%d)\n", (void*) data.options, rc));
rc = krb5_get_init_creds_opt_set_out_ccache(data.context, data.options, data.ccache);
DEBUG(0,("options are using the ccache (%d)\n", rc));
rc = krb5_get_init_creds_keytab(data.context, &data.creds, data.principal, data.keytab,
0, NULL, data.options);
DEBUG(0,("creds uses keytab (%d)\n", rc));
ret = ldap_sasl_interactive_bind_s(ldap_struct,
NULL, "GSSAPI",
NULL, NULL,
LDAP_SASL_QUIET,
ldap_sasl_interact, &data);
if (ret != LDAP_SUCCESS) {
DEBUG(0, ("bind_callback: cannot perform interactive SASL bind with GSSAPI\n"));
}
krb5_get_init_creds_opt_free(data.context, data.options);
krb5_kt_close(data.context, data.keytab);
krb5_cc_close(data.context, data.ccache);
krb5_free_context(data.context);
unbecome_root();
return ret;
}
static NTSTATUS pdb_init_ipasam(struct pdb_methods **pdb_method,
const char *location)
{
@ -3060,6 +3167,7 @@ static NTSTATUS pdb_init_ipasam(struct pdb_methods **pdb_method,
struct dom_sid ldap_domain_sid;
char *bind_dn = NULL;
char *bind_secret = NULL;
const char *service_principal = NULL;
LDAPMessage *result = NULL;
LDAPMessage *entry = NULL;
@ -3088,13 +3196,27 @@ static NTSTATUS pdb_init_ipasam(struct pdb_methods **pdb_method,
return NT_STATUS_NO_MEMORY;
}
trim_char( uri, '\"', '\"' );
if (!fetch_ldap_pw(&bind_dn, &bind_secret)) {
DEBUG(0, ("pdb_init_ipasam: Failed to retrieve LDAP password from secrets.tdb\n"));
return NT_STATUS_NO_MEMORY;
}
status = smbldap_init(*pdb_method, pdb_get_tevent_context(),
service_principal = lp_parm_const_string(-1, "ipasam", "principal", NULL);
if (service_principal == NULL) {
if (!fetch_ldap_pw(&bind_dn, &bind_secret)) {
DEBUG(0, ("pdb_init_ipasam: Failed to retrieve LDAP password from secrets.tdb\n"));
return NT_STATUS_NO_MEMORY;
}
status = smbldap_init(*pdb_method, pdb_get_tevent_context(),
uri, false, bind_dn, bind_secret,
&ldap_state->smbldap_state);
} else {
/* We authenticate via GSSAPI and thus will use kerberos principal to bind our access */
status = smbldap_init(*pdb_method, pdb_get_tevent_context(),
uri, false, NULL, NULL,
&ldap_state->smbldap_state);
if (NT_STATUS_IS_OK(status)) {
ldap_state->smbldap_state->bind_callback = bind_callback;
}
}
talloc_free(uri);
if (!NT_STATUS_IS_OK(status)) {
return status;

View File

@ -14,11 +14,11 @@ passdb backend = ipasam:ldapi://$LDAPI_SOCKET
disable spoolss = yes
ldapsam:trusted=yes
ldap ssl = off
ldap admin dn = $SMB_DN
ldap suffix = $SUFFIX
ldap user suffix = cn=users,cn=accounts
ldap group suffix = cn=groups,cn=accounts
ldap machine suffix = cn=computers,cn=accounts
ipasam:principal = cifs/$FQDN@$REALM
rpc_server:epmapper = external
rpc_server:lsarpc = external
rpc_server:lsass = external

View File

@ -224,13 +224,16 @@ def main():
print "\t\t * 389: (C)LDAP"
print "\t\t * 445: microsoft-ds"
print ""
print "\tAdditionally you have to make sure the FreeIPA LDAP server cannot reached"
print "\tAdditionally you have to make sure the FreeIPA LDAP server cannot be reached"
print "\tby any domain controller in the Active Directory domain by closing the"
print "\tfollowing ports for these servers:"
print "\t\tTCP Ports:"
print "\t\t * 389, 636: LDAP/LDAPS"
print "\tYou may want to choose to REJECT the network packets instead of DROPing them"
print "\tto avoid timeouts on the AD domain controllers."
print ""
print "\tWARNING: you MUST re-kinit admin user before using 'ipa trust-*' commands family"
print "\tin order to re-generate Kerberos tickets to include AD-specific information"
return 0

View File

@ -24,3 +24,39 @@ add:objectClasses: (2.16.840.1.113730.3.8.12.4 NAME 'ipaNTDomainAttrs' SUP top A
replace:objectClasses: (2.16.840.1.113730.3.8.12.5 NAME 'ipaNTTrustedDomain' SUP top STRUCTURAL DESC 'Trusted Domain Object' MUST ( cn ) MAY ( ipaNTTrustType $$ ipaNTTrustAttributes $$ ipaNTTrustDirection $$ ipaNTTrustPartner $$ ipaNTFlatName $$ ipaNTTrustAuthOutgoing $$ ipaNTTrustAuthIncoming $$ ipaNTSecurityIdentifier $$ ipaNTTrustForestTrustInfo $$ ipaNTTrustPosixOffset $$ ipaNTSupportedEncryptionTypes) )::objectClasses: (2.16.840.1.113730.3.8.12.5 NAME 'ipaNTTrustedDomain' SUP top STRUCTURAL DESC 'Trusted Domain Object' MUST ( cn ) MAY ( ipaNTTrustType $$ ipaNTTrustAttributes $$ ipaNTTrustDirection $$ ipaNTTrustPartner $$ ipaNTFlatName $$ ipaNTTrustAuthOutgoing $$ ipaNTTrustAuthIncoming $$ ipaNTTrustedDomainSID $$ ipaNTTrustForestTrustInfo $$ ipaNTTrustPosixOffset $$ ipaNTSupportedEncryptionTypes) )
add:objectClasses: (2.16.840.1.113730.3.8.12.5 NAME 'ipaNTTrustedDomain' SUP top STRUCTURAL DESC 'Trusted Domain Object' MUST ( cn ) MAY ( ipaNTTrustType $$ ipaNTTrustAttributes $$ ipaNTTrustDirection $$ ipaNTTrustPartner $$ ipaNTFlatName $$ ipaNTTrustAuthOutgoing $$ ipaNTTrustAuthIncoming $$ ipaNTTrustedDomainSID $$ ipaNTTrustForestTrustInfo $$ ipaNTTrustPosixOffset $$ ipaNTSupportedEncryptionTypes) )
dn: cn=trust admins,cn=groups,cn=accounts,$SUFFIX
default: objectClass: top
default: objectClass: groupofnames
default: objectClass: ipausergroup
default: objectClass: nestedgroup
default: objectClass: ipaobject
default: cn: trust admins
default: description: Trusts administrators group
default: member: uid=admin,cn=users,cn=accounts,$SUFFIX
default: nsAccountLock: FALSE
default: ipaUniqueID: autogenerate
dn: cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX
default: objectClass: GroupOfNames
default: objectClass: top
default: cn: adtrust agents
default: member: krbprincipalname=cifs/$FQDN@$REALM,cn=services,cn=accounts,$SUFFIX
dn: cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX
add: member: krbprincipalname=cifs/$FQDN@$REALM,cn=services,cn=accounts,$SUFFIX
dn: cn=trusts,$SUFFIX
default: objectClass: top
default: objectClass: nsContainer
default: cn: trusts
# Trust management
# 1. cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX can manage trusts, to allow modification via CIFS
# 2. cn=trust admins,cn=groups,cn=accounts,$SUFFIX can manage trusts (via ipa tools)
dn: cn=trusts,$SUFFIX
add:aci: '(target = "ldap:///cn=trusts,$SUFFIX")(targetattr = "ipaNTTrustType || ipaNTTrustAttributes || ipaNTTrustDirection || ipaNTTrustPartner || ipaNTFlatName || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming || ipaNTSecurityIdentifier || ipaNTTrustForestTrustInfo || ipaNTTrustPosixOffset || ipaNTSupportedEncryptionTypes")(version 3.0;acl "Allow trust system user to create and delete trust accounts"; allow (read,write,add,delete) groupdn="ldap:///cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX";)'
add:aci: '(target = "ldap:///cn=trusts,$SUFFIX")(targetattr = "ipaNTTrustType || ipaNTTrustAttributes || ipaNTTrustDirection || ipaNTTrustPartner || ipaNTFlatName || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming || ipaNTSecurityIdentifier || ipaNTTrustForestTrustInfo || ipaNTTrustPosixOffset || ipaNTSupportedEncryptionTypes")(version 3.0;acl "Allow trust admins manage trust accounts"; allow (read,write,add,delete) groupdn="ldap:///cn=trust admins,cn=groups,cn=accounts,$SUFFIX";)'
# Samba user should be able to read NT passwords to authenticate
dn: $SUFFIX
add:aci: '(targetattr = "ipaNTHash")(version 3.0; acl "Samba system principals can read NT passwords"; allow (read) groupdn="ldap:///cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX";)'

View File

@ -103,6 +103,8 @@ class ADTRUSTInstance(service.Service):
self.trust_dn = None
self.smb_dom_dn = None
self.sub_dict = None
self.cifs_principal = None
self.cifs_agent = None
service.Service.__init__(self, "smb", dm_password=dm_password)
@ -111,55 +113,6 @@ class ADTRUSTInstance(service.Service):
else:
self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')
def __create_samba_user(self):
print "The user for Samba is %s" % self.smb_dn
try:
self.admin_conn.getEntry(self.smb_dn, ldap.SCOPE_BASE)
root_logger.info("Samba user entry exists, resetting password")
self.admin_conn.modify_s(self.smb_dn, \
[(ldap.MOD_REPLACE, "userPassword", self.smb_dn_pwd)])
return
except errors.NotFound:
pass
# The user doesn't exist, add it
entry = ipaldap.Entry(self.smb_dn)
entry.setValues("objectclass", ["account", "simplesecurityobject"])
entry.setValues("uid", "samba")
entry.setValues("userPassword", self.smb_dn_pwd)
self.admin_conn.addEntry(entry)
# And finally grant it permission to read NT passwords, we do not want
# to support LM passwords so there is no need to allow access to them.
# Also the premission to create trusted domain objects below the
# domain object is granted.
mod = [(ldap.MOD_ADD, 'aci',
str('(targetattr = "ipaNTHash")' \
'(version 3.0; acl "Samba user can read NT passwords";' \
'allow (read) userdn="ldap:///%s";)' % self.smb_dn)),
(ldap.MOD_ADD, 'aci',
str('(target = "ldap:///cn=ad,cn=trusts,%s")' \
'(targetattr = "ipaNTTrustType || ipaNTTrustAttributes || ' \
'ipaNTTrustDirection || ' \
'ipaNTTrustPartner || ipaNTFlatName || ' \
'ipaNTTrustAuthOutgoing || ' \
'ipaNTTrustAuthIncoming || ' \
'ipaNTSecurityIdentifier || ' \
'ipaNTTrustForestTrustInfo || ' \
'ipaNTTrustPosixOffset || ' \
'ipaNTSupportedEncryptionTypes")' \
'(version 3.0;acl "Allow samba user to create and delete ' \
'trust accounts";' \
'allow (write,add,delete) userdn = "ldap:///%s";)' % \
(self.suffix, self.smb_dn)))]
try:
self.admin_conn.modify_s(self.suffix, mod)
except ldap.TYPE_OR_VALUE_EXISTS:
root_logger.debug("samba user aci already exists in suffix %s on %s" % (self.suffix, self.admin_conn.host))
def __gen_sid_string(self):
sub_ids = struct.unpack("<LLL", os.urandom(12))
return "S-1-5-21-%d-%d-%d" % (sub_ids[0], sub_ids[1], sub_ids[2])
@ -275,17 +228,18 @@ class ADTRUSTInstance(service.Service):
finally:
os.remove(tmp_name)
def __set_smb_ldap_password(self):
args = ["/usr/bin/smbpasswd", "-c", self.smb_conf, "-s", "-W" ]
ipautil.run(args, stdin = self.smb_dn_pwd + "\n" + \
self.smb_dn_pwd + "\n" )
def __setup_principal(self):
cifs_principal = "cifs/" + self.fqdn + "@" + self.realm_name
try:
api.Command.service_add(unicode(cifs_principal))
api.Command.service_add(unicode(self.cifs_principal))
# Add the principal to the 'adtrust agents' group
# as 389-ds only operates with GroupOfNames, we have to use
# the principal's proper dn as defined in self.cifs_agent
entry = self.admin_conn.getEntry(self.smb_dn, ldap.SCOPE_BASE)
current = ipaldap.Entry(self.smb_dn, entry.toDict())
if not('member' in current):
current['member'] = []
entry.setValues("member", current['member'] + [self.cifs_agent])
self.admin_conn.updateEntry(self.smb_dn, current, entry)
except Exception, e:
# CIFS principal already exists, it is not the first time adtrustinstance is managed
# That's fine, we we'll re-extract the key again.
@ -294,18 +248,18 @@ class ADTRUSTInstance(service.Service):
samba_keytab = "/etc/samba/samba.keytab"
if os.path.exists(samba_keytab):
try:
ipautil.run(["ipa-rmkeytab", "--principal", cifs_principal,
ipautil.run(["ipa-rmkeytab", "--principal", self.cifs_principal,
"-k", samba_keytab])
except ipautil.CalledProcessError, e:
if e.returncode != 5:
root_logger.critical("Failed to remove old key for %s" % cifs_principal)
root_logger.critical("Failed to remove old key for %s" % self.cifs_principal)
try:
ipautil.run(["ipa-getkeytab", "--server", self.fqdn,
"--principal", cifs_principal,
"--principal", self.cifs_principal,
"-k", samba_keytab])
except ipautil.CalledProcessError, e:
root_logger.critical("Failed to add key for %s" % cifs_principal)
root_logger.critical("Failed to add key for %s" % self.cifs_principal)
def __add_dns_service_records(self):
"""
@ -393,7 +347,8 @@ class ADTRUSTInstance(service.Service):
SUFFIX = self.suffix,
NETBIOS_NAME = self.netbios_name,
SMB_DN = self.smb_dn,
LDAPI_SOCKET = self.ldapi_socket)
LDAPI_SOCKET = self.ldapi_socket,
FQDN = self.fqdn)
def setup(self, fqdn, ip_address, realm_name, domain_name, netbios_name,
no_msdcs=False, smbd_user="samba"):
@ -410,12 +365,14 @@ class ADTRUSTInstance(service.Service):
self.smb_conf = "/etc/samba/smb.conf"
self.smb_dn = "uid=samba,cn=sysaccounts,cn=etc,%s" % self.suffix
self.smb_dn_pwd = ipautil.ipa_generate_password()
self.smb_dn = "cn=adtrust agents,cn=sysaccounts,cn=etc,%s" % self.suffix
self.trust_dn = "cn=trusts,%s" % self.suffix
self.smb_dom_dn = "cn=%s,cn=ad,cn=etc,%s" % (self.domain_name, \
self.suffix)
self.cifs_principal = "cifs/" + self.fqdn + "@" + self.realm_name
self.cifs_agent = "krbprincipalname=%s,cn=services,cn=accounts,%s" % \
(self.cifs_principal.lower(), self.suffix)
self.__setup_sub_dict()
@ -425,13 +382,10 @@ class ADTRUSTInstance(service.Service):
self.ldap_connect()
self.step("stopping smbd", self.__stop)
self.step("creating samba user", self.__create_samba_user)
self.step("creating samba domain object", \
self.__create_samba_domain_object)
self.step("creating samba config registry", self.__write_smb_registry)
self.step("writing samba config file", self.__write_smb_conf)
self.step("setting password for the samba user", \
self.__set_smb_ldap_password)
self.step("adding cifs Kerberos principal", self.__setup_principal)
self.step("adding admin(group) SIDs", self.__add_admin_sids)
self.step("activating CLDAP plugin", self.__add_cldap_module)