[Freeipa-devel] [PATCH] 0180-0190 oneway trust and other trust-related patches

Alexander Bokovoy abokovoy at redhat.com
Tue Jul 7 10:35:24 UTC 2015


Hi,

attached are patches to introduce one-way trust support and few more to
fix currently outstanding trust-related bugs.

More details are in the commit messages.

For oddjobd-activated helper, if you want to test the one-way trust
setup, you need to put SELinux into permissive. We have bugs for both
Fedora and RHEL to add the policy
(https://bugzilla.redhat.com/show_bug.cgi?id=1238163 for RHEL7), it is
in works.
-- 
/ Alexander Bokovoy
-------------- next part --------------
From 0e252fb1f8455daa87dccbc6dcba61b08570b444 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Wed, 20 May 2015 18:24:52 +0300
Subject: [PATCH 03/11] ipa-kdb: use proper memory chunk size when moving sids

Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1222475
---
 daemons/ipa-kdb/ipa_kdb_mspac.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index 0e53a80..390111f 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -1394,7 +1394,15 @@ static krb5_error_code filter_logon_info(krb5_context context,
             if (result) {
                 filter_logon_info_log_message(info->info->info3.sids[i].sid);
             } else {
+                /* Go over incoming SID blacklist */
                 for(k = 0; k < domain->len_sid_blacklist_incoming; k++) {
+                    /* if SID is an exact match, filter it out */
+                    result = dom_sid_check(&domain->sid_blacklist_incoming[k], info->info->info3.sids[i].sid, true);
+                    if (result) {
+                        filter_logon_info_log_message(info->info->info3.sids[i].sid);
+                        break;
+                    }
+                    /* if SID is a suffix of the blacklist element, filter it out*/
                     result = dom_sid_is_prefix(&domain->sid_blacklist_incoming[k], info->info->info3.sids[i].sid);
                     if (result) {
                         filter_logon_info_log_message(info->info->info3.sids[i].sid);
@@ -1403,11 +1411,17 @@ static krb5_error_code filter_logon_info(krb5_context context,
                 }
             }
             if (result) {
+                k = count - i - j - 1;
+                if (k != 0) {
+                    memmove(info->info->info3.sids+i,
+                            info->info->info3.sids+i+1,
+                            sizeof(struct netr_SidAttr)*k);
+                }
                 j++;
-                memmove(info->info->info3.sids+i, info->info->info3.sids+i+1, count-i-1);
+            } else {
+                i++;
             }
-            i++;
-        } while (i < count);
+        } while ((i + j) < count);
 
         if (j != 0) {
             count = count-j;
-- 
2.4.3

-------------- next part --------------
From a797874359544e431bdd96dd11e26f404c578db0 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Thu, 28 May 2015 08:33:51 +0000
Subject: [PATCH 04/11] ipa-kdb: filter out group membership from MS-PAC for
 exact SID matches too

When incoming SID blacklist contains exact SIDs of users and groups,
attempt to filter them out as well, according to [MS-PAC] 4.1.1.2.

Note that we treat user's SID and primary group RID filtering as violation
of the KDC policy because the resulting MS-PAC will have no user SID or
primary group and thus will be invalid.

For group RIDs we filter them out and in unlikely event of empty
list of groups treat that as violation of the KDC policy as well.

Part of fix for https://bugzilla.redhat.com/show_bug.cgi?id=1222475
---
 daemons/ipa-kdb/ipa_kdb_mspac.c | 102 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 101 insertions(+), 1 deletion(-)

diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index 390111f..df19880 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -1317,6 +1317,22 @@ static void filter_logon_info_log_message(struct dom_sid *sid)
     }
 }
 
+static void filter_logon_info_log_message_rid(struct dom_sid *sid, uint32_t rid)
+{
+    char *domstr = NULL;
+
+    domstr = dom_sid_string(NULL, sid);
+    if (domstr) {
+        krb5_klog_syslog(LOG_ERR, "PAC filtering issue: SID [%s-%d] is not allowed "
+                                  "from a trusted source and will be excluded.", domstr, rid);
+        talloc_free(domstr);
+    } else {
+        krb5_klog_syslog(LOG_ERR, "PAC filtering issue: SID is not allowed "
+                                  "from a trusted source and will be excluded."
+                                  "Unable to allocate memory to display SID.");
+    }
+}
+
 static krb5_error_code filter_logon_info(krb5_context context,
                                          TALLOC_CTX *memctx,
                                          krb5_data realm,
@@ -1328,9 +1344,21 @@ static krb5_error_code filter_logon_info(krb5_context context,
      * attempt at getting us to sign fake credentials with the help of a
      * compromised trusted realm */
 
+    /* NOTE: there are two outcomes from filtering:
+     * REJECT TICKET -- ticket is rejected if domain SID of
+     *                  the principal with MS-PAC is filtered out or
+     *                  its primary group RID is filtered out
+     *
+     * REMOVE SID    -- SIDs are removed from the list of SIDs associated
+     *                  with the principal if they are filtered out
+     *                  This applies also to secondary RIDs of the principal
+     *                  if domain_sid-<secondary RID> is filtered out
+     */
+
     struct ipadb_context *ipactx;
     struct ipadb_adtrusts *domain;
-    int i, j, k, count;
+    int i, j, k, l, count;
+    uint32_t rid;
     bool result;
     char *domstr = NULL;
 
@@ -1377,6 +1405,78 @@ static krb5_error_code filter_logon_info(krb5_context context,
         }
     }
 
+    /* Check if this user's SIDs membership is filtered too */
+    for(k = 0; k < domain->len_sid_blacklist_incoming; k++) {
+        /* Short-circuit if there are no RIDs. This may happen if we filtered everything already.
+         * In normal situation there would be at least primary gid as RID in the RIDs array
+         * but if we filtered out the primary RID, this MS-PAC is invalid */
+        count = info->info->info3.base.groups.count;
+        if (count == 0) {
+            krb5_klog_syslog(LOG_ERR, "MS-PAC record of [%s] has no groups, including primary, rejecting.",
+                             info->info->info3.base.account_name.string);
+            return KRB5KDC_ERR_POLICY;
+        }
+        result = dom_sid_is_prefix(info->info->info3.base.domain_sid,
+                                   &domain->sid_blacklist_incoming[k]);
+        if (result) {
+            i = 0;
+            j = 0;
+            if (domain->sid_blacklist_incoming[k].num_auths - info->info->info3.base.domain_sid->num_auths != 1) {
+                krb5_klog_syslog(LOG_ERR, "Incoming SID blacklist element matching domain [%s with SID %s] "
+                                          "has more than one RID component. Invalid check skipped.",
+                                 domain->domain_name, domain->domain_sid);
+                break;
+            }
+            rid = domain->sid_blacklist_incoming[k].sub_auths[domain->sid_blacklist_incoming[k].num_auths - 1];
+            if (rid == info->info->info3.base.rid) {
+                filter_logon_info_log_message_rid(info->info->info3.base.domain_sid, rid);
+                /* Actual user's SID is filtered out */
+                return KRB5KDC_ERR_POLICY;
+            }
+            do {
+                if (rid == info->info->info3.base.groups.rids[i].rid) {
+                    filter_logon_info_log_message_rid(info->info->info3.base.domain_sid, rid);
+                    if (rid == info->info->info3.base.primary_gid) {
+                        /* User's primary group SID is filtered out */
+                        return KRB5KDC_ERR_POLICY;
+                    }
+                    /* If this is just a non-primary RID, we simply remove it from the array of RIDs */
+                    l = count - i - j - 1;
+                    if (l != 0) {
+                         memmove(info->info->info3.base.groups.rids+i,
+                                 info->info->info3.base.groups.rids+i+1,
+                                 sizeof(struct samr_RidWithAttribute)*l);
+                    }
+                    j++;
+                } else {
+                    i++;
+                }
+            } while ((i + j) < count);
+
+            if (j != 0) {
+                count = count-j;
+                if (count == 0) {
+                    /* All RIDs were filtered out, including the primary one, bail out */
+                    info->info->info3.base.groups.count = 0;
+                    talloc_free(info->info->info3.base.groups.rids);
+                    info->info->info3.base.groups.rids = NULL;
+                    krb5_klog_syslog(LOG_ERR, "All group membership in MS-PAC of [%s] is filtered. Rejecting.",
+                                     info->info->info3.base.account_name.string);
+                    return KRB5KDC_ERR_POLICY;
+                } else {
+                    info->info->info3.base.groups.rids = talloc_realloc(memctx,
+                                                                        info->info->info3.base.groups.rids,
+                                                                        struct samr_RidWithAttribute, count);
+                    if (!info->info->info3.base.groups.rids) {
+                        info->info->info3.base.groups.count = 0;
+                        return ENOMEM;
+                    }
+                    info->info->info3.base.groups.count = count;
+                }
+            }
+        }
+    }
+
     /* According to MS-KILE 25.0, info->info->info3.sids may be non zero, so check
      * should include different possibilities into account
      * */
-- 
2.4.3

-------------- next part --------------
From b7a3b206deb3257b3a78939f0d2a6a114e48b758 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Thu, 26 Mar 2015 14:34:06 +0200
Subject: [PATCH 01/11] add one-way trust support to ipasam

When trust is established, ipasam module creates a number of objects in LDAP
to represent the trust information. Among them, for one-way trust we create
a principal named IPA$@AD where IPA is a NetBIOS (flat) name of the IPA forest
and AD is a realm of the trusted Active Directory forest root domain.

This principal is then used by SSSD on IPA masters to authenticate against
trusted Active Directory domain controllers and retrieve information about
user and group identities.

FreeIPA also uses this principal's credentials to retrieve domain topology.

The access to the keys of the principal should be well-protected. We only
allow to retrieve the keytab for it for members of cn=adtrust agents group.
This group is populated with host/ and cifs/ principals from IPA masters.

Starting with FreeIPA 4.2 the group will also have host/ principals of IPA masters
where no ipa-adtrust-install was run. To add them, run ipa-adtrust-install
on the master which will be configured to be a domain controller (e.g.
run Samba with ipasam), and specify --add-agents option to trigger activation
of the interactive mode to specify which IPA masters to enable.

Fixes https://fedorahosted.org/freeipa/ticket/4962
Part of fixes for https://fedorahosted.org/freeipa/ticket/4546
---
 daemons/ipa-sam/ipa_sam.c | 106 +++++++++++++++++++++++++++++++++++++---------
 1 file changed, 85 insertions(+), 21 deletions(-)

diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
index 07249fd..19d0a23 100644
--- a/daemons/ipa-sam/ipa_sam.c
+++ b/daemons/ipa-sam/ipa_sam.c
@@ -147,6 +147,8 @@ void idmap_cache_set_sid2unixid(const struct dom_sid *sid, struct unixid *unix_i
 #define LDAP_OBJ_KRB_PRINCIPAL_AUX "krbPrincipalAux"
 #define LDAP_OBJ_KRB_TICKET_POLICY_AUX "krbTicketPolicyAux"
 #define LDAP_ATTRIBUTE_KRB_PRINCIPAL "krbPrincipalName"
+#define LDAP_ATTRIBUTE_KRB_TICKET_FLAGS "krbTicketFlags"
+#define LDAP_ATTRIBUTE_IPAOPALLOW "ipaAllowedToPerform;read_keys"
 
 #define LDAP_OBJ_IPAOBJECT "ipaObject"
 #define LDAP_OBJ_IPAHOST "ipaHost"
@@ -157,9 +159,13 @@ void idmap_cache_set_sid2unixid(const struct dom_sid *sid, struct unixid *unix_i
 #define LDAP_OBJ_IPAUSERGROUP "ipaUserGroup"
 #define LDAP_OBJ_POSIXGROUP "posixGroup"
 #define LDAP_OBJ_DOMAINRELATED "domainRelatedObject"
+#define LDAP_OBJ_IPAOPALLOW "ipaAllowedOperations"
 
 #define LDAP_CN_REALM_DOMAINS "cn=Realm Domains,cn=ipa,cn=etc"
 
+#define LDAP_CN_ADTRUST_AGENTS "cn=adtrust agents,cn=sysaccounts,cn=etc"
+#define LDAP_CN_ADTRUST_ADMINS "cn=trust admins,cn=groups,cn=accounts"
+
 #define HAS_KRB_PRINCIPAL (1<<0)
 #define HAS_KRB_PRINCIPAL_AUX (1<<1)
 #define HAS_IPAOBJECT (1<<2)
@@ -171,6 +177,9 @@ void idmap_cache_set_sid2unixid(const struct dom_sid *sid, struct unixid *unix_i
 #define HAS_POSIXGROUP (1<<8)
 #define HAS_KRB_TICKET_POLICY_AUX (1<<9)
 
+/* krbTicketFlags flag to don't allow issuing any ticket, keep in decimal form for LDAP use*/
+#define IPASAM_DISALLOW_ALL_TIX 64
+
 const struct dom_sid global_sid_Builtin = { 1, 1, {0,0,0,0,0,5},
 					   {32,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
 
@@ -1677,11 +1686,16 @@ static bool search_krb_princ(struct ldapsam_privates *ldap_state,
 	return true;
 }
 
+#define KRB_PRINC_DEFAULT_ENCTYPES "aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96,arcfour-hmac"
+
 static int set_cross_realm_pw(struct ldapsam_privates *ldap_state,
 			      TALLOC_CTX *mem_ctx,
-			      const char *princ, const char *pwd,
+			      const char *princ,
+			      const char *saltprinc,
+			      const char *pwd,
 			      const char *base_dn)
 {
+
 	int ret;
 	krb5_error_code krberr;
 	krb5_context krbctx;
@@ -1699,14 +1713,14 @@ static int set_cross_realm_pw(struct ldapsam_privates *ldap_state,
 		goto done;
 	}
 
-	krberr = krb5_parse_name(krbctx, princ, &service_princ);
+	krberr = krb5_parse_name(krbctx, (saltprinc != NULL) ? saltprinc : princ, &service_princ);
 	if (krberr != 0) {
 		DEBUG(1, ("Invalid Service Principal Name [%s]\n", princ));
 		ret = krberr;
 		goto done;
 	}
 
-	ret = create_keys(krbctx, service_princ, discard_const(pwd), NULL,
+	ret = create_keys(krbctx, service_princ, discard_const(pwd), KRB_PRINC_DEFAULT_ENCTYPES,
                           &keys, &err_msg);
 	krb5_free_principal(krbctx, service_princ);
 	if (!ret) {
@@ -1748,10 +1762,16 @@ done:
 	return ret;
 }
 
+#define KRB_PRINC_CREATE_DEFAULT            0x00000000
+#define KRB_PRINC_CREATE_DISABLED           0x00000001
+#define KRB_PRINC_CREATE_AGENT_PERMISSION   0x00000002
+
 static bool set_krb_princ(struct ldapsam_privates *ldap_state,
 			  TALLOC_CTX *mem_ctx,
-			  const char *princ, const char *pwd,
-			  const char *base_dn)
+			  const char *princ, const char *saltprinc,
+			  const char *pwd,
+			  const char *base_dn,
+			  uint32_t   create_flags)
 {
 	LDAPMessage *entry = NULL;
 	LDAPMod **mods = NULL;
@@ -1805,6 +1825,33 @@ static bool set_krb_princ(struct ldapsam_privates *ldap_state,
 	smbldap_make_mod(priv2ld(ldap_state), entry, &mods,
 			 LDAP_ATTRIBUTE_KRB_PRINCIPAL, princ);
 
+	if ((create_flags & KRB_PRINC_CREATE_DISABLED)) {
+		smbldap_make_mod(priv2ld(ldap_state), entry, &mods,
+				LDAP_ATTRIBUTE_KRB_TICKET_FLAGS, __TALLOC_STRING_LINE2__(IPASAM_DISALLOW_ALL_TIX));
+	}
+
+	if ((create_flags & KRB_PRINC_CREATE_AGENT_PERMISSION)) {
+		char *agent_dn = NULL;
+		agent_dn = talloc_asprintf(mem_ctx, LDAP_CN_ADTRUST_AGENTS",%s", ldap_state->ipasam_privates->base_dn);
+		if (agent_dn == NULL) {
+			DEBUG(1, ("error configuring cross realm principal data!\n"));
+			return false;
+		}
+		smbldap_set_mod(&mods, LDAP_MOD_ADD,
+				LDAP_ATTRIBUTE_OBJECTCLASS,
+				LDAP_OBJ_IPAOPALLOW);
+		smbldap_make_mod(priv2ld(ldap_state), entry, &mods,
+				LDAP_ATTRIBUTE_IPAOPALLOW, agent_dn);
+		agent_dn = talloc_asprintf(mem_ctx, LDAP_CN_ADTRUST_ADMINS",%s", ldap_state->ipasam_privates->base_dn);
+		if (agent_dn == NULL) {
+			DEBUG(1, ("error configuring cross realm principal data for trust admins!\n"));
+			return false;
+		}
+		smbldap_make_mod(priv2ld(ldap_state), entry, &mods,
+				LDAP_ATTRIBUTE_IPAOPALLOW, agent_dn);
+	}
+
+
 	if (entry == NULL) {
 		ret = smbldap_add(ldap_state->smbldap_state, dn, mods);
 	} else {
@@ -1815,7 +1862,7 @@ static bool set_krb_princ(struct ldapsam_privates *ldap_state,
 		return false;
 	}
 
-	ret = set_cross_realm_pw(ldap_state, mem_ctx, princ, pwd, base_dn);
+	ret = set_cross_realm_pw(ldap_state, mem_ctx, princ, saltprinc, pwd, base_dn);
 	if (ret != 0) {
 		DEBUG(1, ("set_cross_realm_pw failed.\n"));
 		return false;
@@ -1858,11 +1905,14 @@ enum princ_mod {
 
 static bool handle_cross_realm_princs(struct ldapsam_privates *ldap_state,
 				      const char *domain, const char *pwd,
+				      uint32_t trust_direction,
 				      enum princ_mod mod)
 {
 	char *trusted_dn;
 	char *princ_l;
 	char *princ_r;
+	char *princ_tdo;
+	char *saltprinc_tdo;
 	char *remote_realm;
 	bool ok;
 	TALLOC_CTX *tmp_ctx;
@@ -1885,27 +1935,40 @@ static bool handle_cross_realm_princs(struct ldapsam_privates *ldap_state,
 	princ_r = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s",
 			ldap_state->ipasam_privates->realm, remote_realm);
 
-	if (trusted_dn == NULL || princ_l == NULL || princ_r == NULL) {
+	princ_tdo = talloc_asprintf(tmp_ctx, "%s$@%s",
+			ldap_state->ipasam_privates->flat_name, remote_realm);
+
+	saltprinc_tdo = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s",
+			ldap_state->ipasam_privates->flat_name, remote_realm);
+
+	if (trusted_dn == NULL || princ_l == NULL ||
+	    princ_r == NULL || princ_tdo == NULL || saltprinc_tdo == NULL) {
 		ok = false;
 		goto done;
 	}
 
 	switch (mod) {
 		case SET_PRINC:
-			if (!set_krb_princ(ldap_state, tmp_ctx, princ_l, pwd,
-					   trusted_dn) ||
-			    !set_krb_princ(ldap_state, tmp_ctx, princ_r, pwd,
-					   trusted_dn)) {
-				ok = false;
+			/* Create Kerberos principal for inbound trust, enabled by default */
+			ok   = set_krb_princ(ldap_state, tmp_ctx, princ_r, NULL, pwd, trusted_dn, KRB_PRINC_CREATE_DEFAULT);
+			/* Create Kerberos principal corresponding to TDO in AD for SSSD usage, disabled by default */
+			ok |= set_krb_princ(ldap_state, tmp_ctx, princ_tdo, saltprinc_tdo, pwd, trusted_dn,
+					    KRB_PRINC_CREATE_DISABLED | KRB_PRINC_CREATE_AGENT_PERMISSION);
+			if ((trust_direction & LSA_TRUST_DIRECTION_OUTBOUND) != 0) {
+				/* Create Kerberos principal for outbound trust, enabled by default */
+				ok |= set_krb_princ(ldap_state, tmp_ctx, princ_l, NULL, pwd, trusted_dn, KRB_PRINC_CREATE_DEFAULT);
+			}
+			if (!ok) {
 				goto done;
 			}
 			break;
 		case DEL_PRINC:
-			if (!del_krb_princ(ldap_state, tmp_ctx, princ_l,
-					   trusted_dn) ||
-			    !del_krb_princ(ldap_state, tmp_ctx, princ_r,
-					   trusted_dn)) {
-				ok = false;
+			ok  = del_krb_princ(ldap_state, tmp_ctx, princ_r, trusted_dn);
+			ok |= del_krb_princ(ldap_state, tmp_ctx, princ_tdo, trusted_dn);
+			if ((trust_direction & LSA_TRUST_DIRECTION_OUTBOUND) != 0) {
+				ok |= del_krb_princ(ldap_state, tmp_ctx, princ_l, trusted_dn);
+			}
+			if (!ok) {
 				goto done;
 			}
 			break;
@@ -1922,15 +1985,16 @@ done:
 }
 
 static bool set_cross_realm_princs(struct ldapsam_privates *ldap_state,
-				   const char *domain, const char *pwd)
+				   const char *domain, const char *pwd, uint32_t trust_direction)
 {
-	return handle_cross_realm_princs(ldap_state, domain, pwd, SET_PRINC);
+	return handle_cross_realm_princs(ldap_state, domain, pwd, trust_direction, SET_PRINC);
 }
 
 static bool del_cross_realm_princs(struct ldapsam_privates *ldap_state,
 				   const char *domain)
 {
-	return handle_cross_realm_princs(ldap_state, domain, NULL, DEL_PRINC);
+	uint32_t trust_direction = LSA_TRUST_DIRECTION_INBOUND | LSA_TRUST_DIRECTION_OUTBOUND;
+	return handle_cross_realm_princs(ldap_state, domain, NULL, trust_direction, DEL_PRINC);
 }
 
 static bool get_trusted_domain_int(struct ldapsam_privates *ldap_state,
@@ -2518,7 +2582,7 @@ static NTSTATUS ipasam_set_trusted_domain(struct pdb_methods *methods,
 			goto done;
 		}
 		res = set_cross_realm_princs(ldap_state, td->domain_name,
-					     trustpw);
+					     trustpw, td->trust_direction);
 		memset(trustpw, 0, strlen(trustpw));
 		if (!res) {
 			DEBUG(1, ("error writing cross realm principals!\n"));
-- 
2.4.3

-------------- next part --------------
From b850385e6bdfd727cb5a801ae4f341dbd93331fe Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Tue, 12 May 2015 12:31:46 +0000
Subject: [PATCH 02/11] ipa-adtrust-install: add IPA master host principal to
 adtrust agents

Fixes https://fedorahosted.org/freeipa/ticket/4951
---
 ACI.txt                              |  2 +-
 ipalib/plugins/trust.py              |  3 ++-
 ipaserver/install/adtrustinstance.py | 44 ++++++++++++------------------------
 ipaserver/install/dsinstance.py      | 16 +++++++++++++
 ipaserver/install/server/upgrade.py  | 21 +++++++++++++++++
 ipaserver/install/service.py         | 27 ++++++++++++++++++++++
 6 files changed, 81 insertions(+), 32 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index 9206d76..76a7ff7 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -299,7 +299,7 @@ aci: (targetattr = "cmdcategory || cn || createtimestamp || description || entry
 dn: dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || description || entryusn || modifytimestamp || objectclass || ou || sudocommand || sudohost || sudonotafter || sudonotbefore || sudooption || sudoorder || sudorunas || sudorunasgroup || sudorunasuser || sudouser")(target = "ldap:///ou=sudoers,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read Sudoers compat tree";allow (compare,read,search) userdn = "ldap:///anyone";)
 dn: cn=trusts,dc=ipa,dc=example
-aci: (targetattr = "cn || createtimestamp || entryusn || ipantflatname || ipantsecurityidentifier || ipantsidblacklistincoming || ipantsidblacklistoutgoing || ipanttrusteddomainsid || ipanttrustpartner || modifytimestamp || objectclass")(version 3.0;acl "permission:System: Read Trust Information";allow (compare,read,search) userdn = "ldap:///all";)
+aci: (targetattr = "cn || createtimestamp || entryusn || ipantflatname || ipantsecurityidentifier || ipantsidblacklistincoming || ipantsidblacklistoutgoing || ipanttrustdirection || ipanttrusteddomainsid || ipanttrustpartner || modifytimestamp || objectclass")(version 3.0;acl "permission:System: Read Trust Information";allow (compare,read,search) userdn = "ldap:///all";)
 dn: cn=trusts,dc=ipa,dc=example
 aci: (targetattr = "gidnumber || krbprincipalname || uidnumber")(version 3.0;acl "permission:System: Read system trust accounts";allow (compare,read,search) groupdn = "ldap:///cn=System: Read system trust accounts,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=groups,cn=accounts,dc=ipa,dc=example
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 22fbb9c..5b884ca 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -327,7 +327,8 @@ class trust(LDAPObject):
                 'cn', 'objectclass',
                 'ipantflatname', 'ipantsecurityidentifier',
                 'ipanttrusteddomainsid', 'ipanttrustpartner',
-                'ipantsidblacklistincoming', 'ipantsidblacklistoutgoing'
+                'ipantsidblacklistincoming', 'ipantsidblacklistoutgoing',
+                'ipanttrustdirection'
             },
         },
 
diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py
index 8591a43..8343f81 100644
--- a/ipaserver/install/adtrustinstance.py
+++ b/ipaserver/install/adtrustinstance.py
@@ -171,6 +171,9 @@ class ADTRUSTInstance(service.Service):
         self.cifs_agent = DN(('krbprincipalname', self.cifs_principal.lower()),
                              api.env.container_service,
                              self.suffix)
+        self.host_princ = DN(('fqdn', self.fqdn),
+                             api.env.container_host,
+                             self.suffix)
 
 
     def __gen_sid_string(self):
@@ -450,12 +453,11 @@ class ADTRUSTInstance(service.Service):
         """
         self.__add_plugin_conf('CLDAP', 'ipa_cldap', 'ipa-cldap-conf.ldif')
 
-    def __add_sidgen_module(self):
+    def __add_sidgen_task(self):
         """
         Add sidgen directory server plugin configuration and the related task
         if they not already exist.
         """
-        self.__add_plugin_conf('Sidgen', 'IPA SIDGEN', 'ipa-sidgen-conf.ldif')
         self.__add_plugin_conf('Sidgen task', 'ipa-sidgen-task',
                                'ipa-sidgen-task-conf.ldif')
 
@@ -469,14 +471,6 @@ class ADTRUSTInstance(service.Service):
         except:
             pass
 
-    def __add_extdom_module(self):
-        """
-        Add directory server configuration for the extdom extended operation
-        if it not already exists.
-        """
-        self.__add_plugin_conf('Extdom', 'ipa_extdom_extop',
-                               'ipa-extdom-extop-conf.ldif')
-
     def __add_s4u2proxy_target(self):
         """
         Add CIFS principal to S4U2Proxy target
@@ -509,6 +503,13 @@ class ADTRUSTInstance(service.Service):
         finally:
             os.remove(tmp_name)
 
+    def __setup_group_membership(self):
+        # Add the CIFS and host principals 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
+        service.add_principals_to_group(self.admin_conn, self.smb_dn, "member",
+                                        [self.cifs_agent, self.host_princ])
+
     def __setup_principal(self):
         try:
             api.Command.service_add(unicode(self.cifs_principal))
@@ -520,24 +521,6 @@ class ADTRUSTInstance(service.Service):
         except Exception, e:
             self.print_msg("Cannot add CIFS service: %s" % e)
 
-        # 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
-        try:
-            current = self.admin_conn.get_entry(self.smb_dn)
-            members = current.get('member', [])
-            if not(self.cifs_agent in members):
-                current["member"] = members + [self.cifs_agent]
-                self.admin_conn.update_entry(current)
-        except errors.NotFound:
-            entry = self.admin_conn.make_entry(
-                self.smb_dn,
-                objectclass=["top", "GroupOfNames"],
-                cn=[self.smb_dn['cn']],
-                member=[self.cifs_agent],
-            )
-            self.admin_conn.add_entry(entry)
-
         self.clean_samba_keytab()
 
         try:
@@ -846,14 +829,15 @@ class ADTRUSTInstance(service.Service):
         self.step("creating samba config registry", self.__write_smb_registry)
         self.step("writing samba config file", self.__write_smb_conf)
         self.step("adding cifs Kerberos principal", self.__setup_principal)
+        self.step("adding cifs and host Kerberos principals to the adtrust agents group", \
+                  self.__setup_group_membership)
         self.step("check for cifs services defined on other replicas", self.__check_replica)
         self.step("adding cifs principal to S4U2Proxy targets", self.__add_s4u2proxy_target)
         self.step("adding admin(group) SIDs", self.__add_admin_sids)
         self.step("adding RID bases", self.__add_rid_bases)
         self.step("updating Kerberos config", self.__update_krb5_conf)
         self.step("activating CLDAP plugin", self.__add_cldap_module)
-        self.step("activating sidgen plugin and task", self.__add_sidgen_module)
-        self.step("activating extdom plugin", self.__add_extdom_module)
+        self.step("activating sidgen task", self.__add_sidgen_task)
         self.step("configuring smbd to start on boot", self.__enable)
         self.step("adding special DNS service records", \
                   self.__add_dns_service_records)
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 9f24189..d561ca5 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -264,6 +264,8 @@ class DsInstance(service.Service):
         self.step("adding replication acis", self.__add_replication_acis)
         self.step("enabling compatibility plugin",
                   self.__enable_compat_plugin)
+        self.step("activating sidgen plugin", self._add_sidgen_plugin)
+        self.step("activating extdom plugin", self._add_extdom_plugin)
         self.step("tuning directory server", self.__tuning)
 
         self.step("configuring directory to start on boot", self.__enable)
@@ -922,6 +924,20 @@ class DsInstance(service.Service):
     def __add_range_check_plugin(self):
         self._ldap_mod("range-check-conf.ldif", self.sub_dict)
 
+    # These two methods are not local, they are also called from the upgrade code
+    def _add_sidgen_plugin(self):
+        """
+        Add sidgen directory server plugin configuration if it does not already exist.
+        """
+        self._ldap_mod('ipa-sidgen-conf.ldif', self.sub_dict)
+
+    def _add_extdom_plugin(self):
+        """
+        Add directory server configuration for the extdom extended operation
+        if it does not already exist.
+        """
+        self._ldap_mod('ipa-extdom-extop-conf.ldif', self.sub_dict)
+
     def replica_populate(self):
         self.ldap_connect()
 
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 740f046..84a5b06 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -18,6 +18,7 @@ import ipalib.errors
 from ipaplatform import services
 from ipaplatform.tasks import tasks
 from ipapython import ipautil, sysrestore, version, certdb
+from ipapython import ipaldap
 from ipapython.ipa_log_manager import *
 from ipapython import certmonger
 from ipapython import dogtag
@@ -1254,6 +1255,18 @@ def update_mod_nss_protocol(http):
 
     sysupgrade.set_upgrade_state('nss.conf', 'protocol_updated_tls12', True)
 
+def ds_enable_sidgen_extdom_plugins(ds):
+    """For AD trust agents, make sure we enable sidgen and extdom plugins
+    """
+    root_logger.info('[Enable sidgen and extdom plugins by default]')
+
+    if sysupgrade.get_upgrade_state('ds', 'enable_ds_sidgen_extdom_plugins'):
+        root_logger.info('sidgen and extdom plugins are enabled already')
+        return
+
+    ds._add_sidgen_plugin()
+    ds._add_extdom_plugin()
+    sysupgrade.set_upgrade_state('ds', 'enable_ds_sidgen_extdom_plugins', True)
 
 def ca_upgrade_schema(ca):
     root_logger.info('[Upgrading CA schema]')
@@ -1412,6 +1425,14 @@ def upgrade_configuration():
     remove_ds_ra_cert(subject_base)
     ds.start(ds_serverid)
 
+    # Force enabling plugins via LDAPI and external bind
+    ds.ldapi = True
+    ds.autobind = ipaldap.AUTOBIND_ENABLED
+    ds.fqdn = fqdn
+    ds.realm = api.env.realm
+    ds.suffix = ipautil.realm_to_suffix(api.env.realm)
+    ds_enable_sidgen_extdom_plugins(ds)
+
     uninstall_selfsign(ds, http)
 
     simple_service_list = (
diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
index 88307a0..2f5f565 100644
--- a/ipaserver/install/service.py
+++ b/ipaserver/install/service.py
@@ -71,6 +71,33 @@ def format_seconds(seconds):
             parts[-1] += 's'
     return ' '.join(parts)
 
+def add_principals_to_group(admin_conn, group, member_attr, principals):
+    """Add principals to a GroupOfNames LDAP group
+    admin_conn  -- LDAP connection with admin rights
+    group       -- DN of the group
+    member_attr -- attribute to represent members
+    principals  -- list of DNs to add as members
+    """
+    try:
+        current = admin_conn.get_entry(group)
+        members = current.get(member_attr, [])
+        if len(members) == 0:
+            current[member_attr] = []
+        for amember in principals:
+            if not(amember in members):
+                current[member_attr].extend([amember])
+        admin_conn.update_entry(current)
+    except errors.NotFound:
+        entry = admin_conn.make_entry(
+                group,
+                objectclass=["top", "GroupOfNames"],
+                cn=[group['cn']],
+                member=principals,
+        )
+        admin_conn.add_entry(entry)
+    except errors.EmptyModlist:
+        # If there are no changes just pass
+        pass
 
 class Service(object):
     def __init__(self, service_name, service_desc=None, sstore=None,
-- 
2.4.3

-------------- next part --------------
From 4a856d8ff597ec516cc1eb05f06e062bb4ecca5b Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Thu, 28 May 2015 11:49:58 +0000
Subject: [PATCH 05/11] trusts: pass AD DC hostname if specified explicitly

Fixes https://bugzilla.redhat.com/show_bug.cgi?id=1222047
---
 API.txt                 |  3 ++-
 VERSION                 |  2 +-
 ipalib/plugins/trust.py |  9 ++++++++-
 ipaserver/dcerpc.py     | 10 +++++++---
 4 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/API.txt b/API.txt
index e226712..f3b4df8 100644
--- a/API.txt
+++ b/API.txt
@@ -4998,10 +4998,11 @@ output: Output('result', <type 'dict'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: ListOfPrimaryKeys('value', None, None)
 command: trust_fetch_domains
-args: 1,4,4
+args: 1,5,4
 arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('realm_server?', cli_name='server')
 option: Flag('rights', autofill=True, default=False)
 option: Str('version?', exclude='webui')
 output: Output('count', <type 'int'>, None)
diff --git a/VERSION b/VERSION
index 266a04a..c31ddfc 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=137
+IPA_API_VERSION_MINOR=138
 # Last change: mbabinsk: Commands to manage user/host/service certificates
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 5b884ca..13ac52d 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -1302,9 +1302,10 @@ def fetch_domains_from_trust(self, trustinstance, trust_entry, **options):
             sp.insert(0, trustinstance.remote_domain.info['name'])
         creds = u"{name}%{password}".format(name="\\".join(sp),
                                             password=password)
+    server = options.get('realm_server', None)
     domains = ipaserver.dcerpc.fetch_domains(self.api,
                                              trustinstance.local_flatname,
-                                             trust_name, creds=creds)
+                                             trust_name, creds=creds, server=server)
     result = []
     if not domains:
         return result
@@ -1342,6 +1343,12 @@ class trust_fetch_domains(LDAPRetrieve):
     __doc__ = _('Refresh list of the domains associated with the trust')
 
     has_output = output.standard_list_of_entries
+    takes_options = LDAPRetrieve.takes_options + (
+        Str('realm_server?',
+            cli_name='server',
+            label=_('Domain controller for the Active Directory domain (optional)'),
+        ),
+    )
 
     def execute(self, *keys, **options):
         if not _bindings_installed:
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 725b2cd..753e10e 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -1046,7 +1046,7 @@ class TrustDomainInstance(object):
         return False
 
 
-def fetch_domains(api, mydomain, trustdomain, creds=None):
+def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
     trust_flags = dict(
                 NETR_TRUST_FLAG_IN_FOREST = 0x00000001,
                 NETR_TRUST_FLAG_OUTBOUND  = 0x00000002,
@@ -1087,8 +1087,12 @@ def fetch_domains(api, mydomain, trustdomain, creds=None):
     cr.set_workstation(domain_validator.flatname)
     netrc = net.Net(creds=cr, lp=td.parm)
     try:
-        result = netrc.finddc(domain=trustdomain,
-                              flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
+        if server:
+            result = netrc.finddc(address=server,
+                                  flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
+        else:
+            result = netrc.finddc(domain=trustdomain,
+                                  flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
     except RuntimeError, e:
         raise assess_dcerpc_exception(message=str(e))
 
-- 
2.4.3

-------------- next part --------------
From c6b834931193b66063c982078dac7bfaeacb950a Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Thu, 4 Jun 2015 17:36:32 +0000
Subject: [PATCH 06/11] ipa-sidgen: reduce log level to normal if domain SID is
 not available

To support AD trust agents, we need to run sidgen and extdom plugins
on every IPA master. Lack of working configuration, thus, is not a
failure so reduce log level to normal as sidgen plugin will not
be active if domain SID is missing but it can certainly be kept
enabled.

Part of https://fedorahosted.org/freeipa/ticket/4951
---
 daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c
index 135c47a..99e6b85 100644
--- a/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c
+++ b/daemons/ipa-slapi-plugins/ipa-sidgen/ipa_sidgen.c
@@ -104,7 +104,7 @@ static int ipa_sidgen_add_post_op(Slapi_PBlock *pb)
     if (ctx->dom_sid == NULL) {
         ret = get_dom_sid(ctx->plugin_id, ctx->base_dn, &ctx->dom_sid);
         if (ret != 0) {
-            LOG_FATAL("Domain SID not available, nothing to do.\n");
+            LOG("Domain SID not available, nothing to do.\n");
             ret = 0;
             goto done;
         }
-- 
2.4.3

-------------- next part --------------
From a4e2034028d64a8b2b533af9541e698a68388fb2 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Thu, 4 Jun 2015 21:29:36 +0000
Subject: [PATCH 07/11] ipa-adtrust-install: allow configuring of trust agents

Trust agents are IPA master without Samba which can serve
information about users from trusted forests. Such IPA masters
cannot be used to configure trust but they can resolve AD users and groups
for IPA clients enrolled to them.

Since support from both FreeIPA and SSSD is needed to enable
trust agent support, we currently only consider those IPA masters
which have been upgraded to FreeIPA 4.2 or later.

Part of https://fedorahosted.org/freeipa/ticket/4951
---
 install/tools/ipa-adtrust-install       | 81 +++++++++++++++++++++++++++++++++
 install/tools/man/ipa-adtrust-install.1 | 15 +++++-
 2 files changed, 95 insertions(+), 1 deletion(-)

diff --git a/install/tools/ipa-adtrust-install b/install/tools/ipa-adtrust-install
index a412407..5340c31 100755
--- a/install/tools/ipa-adtrust-install
+++ b/install/tools/ipa-adtrust-install
@@ -61,6 +61,9 @@ def parse_options():
     parser.add_option("--add-sids", dest="add_sids", action="store_true",
                       default=False, help="Add SIDs for existing users and" \
                                           " groups as the final step")
+    parser.add_option("--add-agents", dest="add_agents", action="store_true",
+                      default=False, help="Add IPA masters to a list of hosts allowed to serve" \
+                                          "information about users from trusted forests")
     parser.add_option("--enable-compat",
                       dest="enable_compat", default=False, action="store_true",
                       help="Enable support for trusted domains for old clients")
@@ -380,6 +383,84 @@ def main():
     smb.find_local_id_range()
     smb.create_instance()
 
+    if options.add_agents:
+        # Find out IPA masters which are not part of the cn=adtrust agents
+        # and propose them to be added to the list
+        base_dn = api.env.basedn
+        masters_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), base_dn)
+        agents_dn = DN(('cn', 'adtrust agents'), ('cn', 'sysaccounts'), ('cn', 'etc'), base_dn)
+        new_agents = []
+        entries_m = []
+        entries_a = []
+        try:
+            # Search only masters which have support for domain levels
+            # because only these masters will have SSSD recent enough to support AD trust agents
+            (entries_m, truncated) = smb.admin_conn.find_entries(
+                filter="(&(objectclass=ipaSupportedDomainLevelConfig)(!(ipaMaxDomainLevel=0)))",
+                base_dn=masters_dn, attrs_list=['cn'], scope=ldap.SCOPE_ONELEVEL)
+        except errors.NotFound:
+            pass
+        except (errors.DatabaseError, errors.NetworkError), e:
+           print "Could not retrieve a list of existing IPA masters:"
+           print unicode(e)
+
+        try:
+           (entries_a, truncated) = smb.admin_conn.find_entries(filter="",
+               base_dn=agents_dn, attrs_list=['member'], scope=ldap.SCOPE_BASE)
+        except errors.NotFound:
+            pass
+        except (errors.DatabaseError, errors.NetworkError), e:
+            print "Could not retrieve a list of adtrust agents:"
+            print unicode(e)
+
+        if len(entries_m) > 0:
+            existing_masters = [x['cn'][0] for x in entries_m]
+            adtrust_agents = entries_a[0]['member']
+            potential_agents = []
+            for m in existing_masters:
+                mdn = DN(('fqdn', m), api.env.container_host, api.env.basedn)
+                found = False
+                for a in adtrust_agents:
+                    if mdn == a:
+                        found = True
+                        break
+                if not found:
+                    potential_agents += [[m, mdn]]
+
+            object_count = len(potential_agents)
+            if object_count > 0:
+                print ""
+                print "WARNING: %d IPA masters are not yet able to serve information about users from trusted forests." \
+                      % (object_count)
+                print "Installer can add them to the list of IPA masters allowed to access infromation about trusts."
+                print "If you choose to do so, you also need to restart LDAP service on those masters."
+                print "Refer to ipa-adtrust-install(1) man page for details."
+                print ""
+                if options.unattended:
+                    print "Unattended mode was selected, installer will NOT add other IPA masters to the list of allowed to"
+                    print "access information about trusted forests!"
+                else:
+                    print "Do you want to allow following IPA masters to serve information about users from trusted forests?"
+                    for (name, dn) in potential_agents:
+                        if name == api.env.host:
+                            # Don't add this host here
+                            # it shouldn't be here as it was added by the adtrustinstance setup code
+                            continue
+                        if ipautil.user_input("IPA master [%s]?" % (name), default=False, allow_empty=False):
+                            new_agents += [[name, dn]]
+
+            if len(new_agents) > 0:
+                # Add the CIFS and host principals 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
+                service.add_principals_to_group(smb.admin_conn, agents_dn, "member",
+                                                [x[1] for x in new_agents])
+                print """
+WARNING: you MUST restart (e.g. ipactl restart) the following IPA masters in order
+to activate them to serve information about users from trusted forests:"""
+                for x in new_agents:
+                    print x[0]
+
     print """
 =============================================================================
 Setup complete
diff --git a/install/tools/man/ipa-adtrust-install.1 b/install/tools/man/ipa-adtrust-install.1
index a32eefb..2658f19 100644
--- a/install/tools/man/ipa-adtrust-install.1
+++ b/install/tools/man/ipa-adtrust-install.1
@@ -76,7 +76,7 @@ are needed for the IPA domain which should point to all IPA servers:
 \(bu _kerberos._udp.Default-First-Site-Name._sites.dc._msdcs
 .TP
 \fB\-\-add\-sids\fR
-Add SIDs to existing users and groups as a final step of the
+Add SIDs to existing users and groups as on of final steps of the
 ipa\-adtrust\-install run. If there a many existing users and groups and a
 couple of replicas in the environment this operation might lead to a high
 replication traffic and a performance degradation of all IPA servers in the
@@ -85,6 +85,19 @@ ipa\-adtrust\-install is run and scheduled independently. To start this task
 you have to load an edited version of ipa-sidgen-task-run.ldif with the
 ldapmodify command info the directory server.
 .TP
+\fB\-\-add\-agents\fR
+Add IPA masters to the list that allows to serve information about
+users from trusted forests. Starting with FreeIPA 4.2, a regular IPA master
+can provide this information to SSSD clients. IPA masters aren't added
+to the list automatically as restart of the LDAP service on each of them
+is required. The host where ipa\-adtrust\-install is being run is added
+automatically.
+.IP
+Note that IPA masters where ipa\-adtrust\-install wasn't run, can serve
+information about users from trusted forests only if they are enabled
+via \ipa-adtrust\-install run on any other IPA master. At least SSSD
+version 1.13 on IPA master is required to be able to perform as a trust agent.
+.TP
 \fB\-U\fR, \fB\-\-unattended\fR
 An unattended installation that will never prompt for user input
 .TP
-- 
2.4.3

-------------- next part --------------
From eb336b43bbd013c2b94a2a60253100a6a8ad2dcf Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Fri, 5 Jun 2015 12:57:02 +0000
Subject: [PATCH 08/11] trusts: add support for one-way trust and switch to it
 by default

One-way trust is the default now, use 'trust add --two-way ' to
force bidirectional trust

https://fedorahosted.org/freeipa/ticket/4959

In case of one-way trust we cannot authenticate using cross-realm TGT
against an AD DC. We have to use trusted domain object from within AD
domain and access to this object is limited to avoid compromising the whole
trust configuration.

Instead, IPA framework can call out to oddjob daemon and ask it to
run the script which can have access to the TDO object. This script
(com.redhat.idm.trust-fetch-domains) is using cifs/ipa.master principal
to retrieve TDO object credentials from IPA LDAP if needed and then
authenticate against AD DCs using the TDO object credentials.

The script pulls the trust topology out of AD DCs and updates IPA LDAP
store. Then IPA framework can pick the updated data from the IPA LDAP
under normal access conditions.

Part of https://fedorahosted.org/freeipa/ticket/4546
---
 API.txt                                            |   3 +-
 VERSION                                            |   2 +-
 freeipa.spec.in                                    |  14 +-
 install/Makefile.am                                |   1 +
 install/configure.ac                               |   1 +
 install/oddjob/Makefile.am                         |  28 +++
 install/oddjob/com.redhat.idm.trust-fetch-domains  | 198 +++++++++++++++++++++
 .../etc/dbus-1/system.d/oddjob-ipa-trust.conf      |  40 +++++
 .../etc/oddjobd.conf.d/oddjobd-ipa-trust.conf      |  21 +++
 ipalib/plugins/trust.py                            | 141 +++++++++++----
 ipaserver/dcerpc.py                                |  44 +++--
 11 files changed, 442 insertions(+), 51 deletions(-)
 create mode 100644 install/oddjob/Makefile.am
 create mode 100755 install/oddjob/com.redhat.idm.trust-fetch-domains
 create mode 100644 install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf
 create mode 100644 install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf

diff --git a/API.txt b/API.txt
index f3b4df8..020639f 100644
--- a/API.txt
+++ b/API.txt
@@ -4971,11 +4971,12 @@ arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=Tr
 option: Str('version?', exclude='webui')
 output: Output('result', None, None)
 command: trust_add
-args: 1,13,3
+args: 1,14,3
 arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Int('base_id?', cli_name='base_id')
+option: Bool('bidirectional?', cli_name='two_way', default=False)
 option: Int('range_size?', cli_name='range_size')
 option: StrEnum('range_type?', cli_name='range_type', values=(u'ipa-ad-trust-posix', u'ipa-ad-trust'))
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
diff --git a/VERSION b/VERSION
index c31ddfc..02ad7e2 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=138
+IPA_API_VERSION_MINOR=139
 # Last change: mbabinsk: Commands to manage user/host/service certificates
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 52af50d..46586ed 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -204,6 +204,7 @@ Requires: samba >= %{samba_version}
 Requires: samba-winbind
 Requires: libsss_idmap
 Requires: libsss_nss_idmap-python
+Requires: oddjob
 %if (0%{?fedora} >= 22)
 Requires: python-sss
 %endif
@@ -581,6 +582,8 @@ fi
 %post server-trust-ad
 %{_sbindir}/update-alternatives --install %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so \
         winbind_krb5_locator.so /dev/null 90
+/bin/systemctl reload-or-try-restart dbus
+/bin/systemctl reload-or-try-restart oddjobd
 
 %posttrans server-trust-ad
 python2 -c "import sys; from ipaserver.install import installutils; sys.exit(0 if installutils.is_ipa_configured() else 1);" > /dev/null 2>&1
@@ -593,6 +596,8 @@ fi
 %preun server-trust-ad
 if [ $1 -eq 0 ]; then
     %{_sbindir}/update-alternatives --remove winbind_krb5_locator.so /dev/null
+    /bin/systemctl reload-or-try-restart dbus
+    /bin/systemctl reload-or-try-restart oddjobd
 fi
 
 %endif # ONLY_CLIENT
@@ -830,6 +835,9 @@ fi
 %attr(755,root,root) %{plugin_dir}/libipa_otp_counter.so
 %attr(755,root,root) %{plugin_dir}/libipa_otp_lasttoken.so
 %attr(755,root,root) %{plugin_dir}/libtopology.so
+%attr(755,root,root) %{plugin_dir}/libipa_sidgen.so
+%attr(755,root,root) %{plugin_dir}/libipa_sidgen_task.so
+%attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so
 %dir %{_localstatedir}/lib/ipa
 %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/backup
 %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysrestore
@@ -864,15 +872,15 @@ fi
 
 %files server-trust-ad
 %{_sbindir}/ipa-adtrust-install
-%attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so
 %{_usr}/share/ipa/smb.conf.empty
 %attr(755,root,root) %{_libdir}/samba/pdb/ipasam.so
-%attr(755,root,root) %{plugin_dir}/libipa_sidgen.so
-%attr(755,root,root) %{plugin_dir}/libipa_sidgen_task.so
 %{_mandir}/man1/ipa-adtrust-install.1.gz
 %{python_sitelib}/ipaserver/dcerpc*
 %{python_sitelib}/ipaserver/install/adtrustinstance*
 %ghost %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so
+%{_sysconfdir}/dbus-1/system.d/oddjob-ipa-trust.conf
+%{_sysconfdir}/oddjobd.conf.d/oddjobd-ipa-trust.conf
+%%attr(755,root,root) %{_libexecdir}/ipa/com.redhat.idm.trust-fetch-domains
 
 %endif # ONLY_CLIENT
 
diff --git a/install/Makefile.am b/install/Makefile.am
index c07f571..ac52ad3 100644
--- a/install/Makefile.am
+++ b/install/Makefile.am
@@ -17,6 +17,7 @@ SUBDIRS =			\
         po			\
         restart_scripts		\
         wsgi			\
+        oddjob			\
 	$(NULL)
 
 install-exec-local:
diff --git a/install/configure.ac b/install/configure.ac
index 57f4219..cf19758 100644
--- a/install/configure.ac
+++ b/install/configure.ac
@@ -103,6 +103,7 @@ AC_CONFIG_FILES([
     po/Makefile
     restart_scripts/Makefile
     wsgi/Makefile
+    oddjob/Makefile
 ])
 
 AC_OUTPUT
diff --git a/install/oddjob/Makefile.am b/install/oddjob/Makefile.am
new file mode 100644
index 0000000..9dde10c
--- /dev/null
+++ b/install/oddjob/Makefile.am
@@ -0,0 +1,28 @@
+NULL =
+
+oddjobdir = $(libexecdir)/ipa
+oddjobconfdir = $(sysconfdir)/oddjobd.conf.d
+dbusconfdir = $(sysconfdir)/dbus-1/system.d
+
+oddjob_SCRIPTS =				\
+	com.redhat.idm.trust-fetch-domains	\
+	$(NULL)
+
+dbusconf_DATA =						\
+	etc/dbus-1/system.d/oddjob-ipa-trust.conf	\
+	$(NULL)
+
+oddjobconf_DATA =					\
+	etc/oddjobd.conf.d/oddjobd-ipa-trust.conf	\
+	$(NULL)
+
+
+#EXTRA_DIST =				\
+#	$(oddjob_SCRIPTS)		\
+#	$(dbusconf_DATA)		\
+#	$(oddjobconf_DATA)		\
+#	$(NULL)
+
+MAINTAINERCLEANFILES =			\
+	*~				\
+	Makefile.in
diff --git a/install/oddjob/com.redhat.idm.trust-fetch-domains b/install/oddjob/com.redhat.idm.trust-fetch-domains
new file mode 100755
index 0000000..2571dd0
--- /dev/null
+++ b/install/oddjob/com.redhat.idm.trust-fetch-domains
@@ -0,0 +1,198 @@
+#!/usr/bin/python2
+
+from ipaserver import dcerpc
+from ipaserver.install.installutils import is_ipa_configured, ScriptError
+from ipapython import config, ipautil
+from ipalib import api, errors
+from ipapython.dn import DN
+from ipalib.config import Env
+from ipalib.constants import DEFAULT_CONFIG
+from ipalib.krb_utils import KRB5_CCache
+import sys
+import os, pwd
+import krbV
+import time
+
+# This version is different from the original in ipapyton.ipautil
+# in the fact that it returns a krbV.CCache object.
+def kinit_keytab(principal, keytab, ccache_name, attempts=1):
+    errors_to_retry = {krbV.KRB5KDC_ERR_SVC_UNAVAILABLE,
+                       krbV.KRB5_KDC_UNREACH}
+    for attempt in range(1, attempts + 1):
+        try:
+            krbcontext = krbV.default_context()
+            ktab = krbV.Keytab(name=keytab, context=krbcontext)
+            princ = krbV.Principal(name=principal, context=krbcontext)
+            ccache = krbV.CCache(name=ccache_name, context=krbcontext,
+                                 primary_principal=princ)
+            ccache.init(princ)
+            ccache.init_creds_keytab(keytab=ktab, principal=princ)
+            return ccache
+        except krbV.Krb5Error as e:
+            if e.args[0] not in errors_to_retry:
+                raise
+            if attempt == attempts:
+                raise
+            time.sleep(5)
+
+def retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal):
+    getkeytab_args = ["/usr/sbin/ipa-getkeytab",
+                      "-s", api.env.host,
+                      "-p", oneway_principal,
+                      "-k", oneway_keytab_name,
+                      "-r"]
+    (stdout, stderr, retcode) = ipautil.run(getkeytab_args,
+                                            env={'KRB5CCNAME': ccache_name, 'LANG': 'C'},
+                                            raiseonerr=False)
+    # Make sure SSSD is able to read the keytab
+    sssd = pwd.getpwnam('sssd')
+    os.chown(oneway_keytab_name, sssd[2], sssd[3])
+
+
+def parse_options():
+    usage = "%prog <trusted domain name>\n"
+    parser = config.IPAOptionParser(usage=usage,
+                                    formatter=config.IPAFormatter())
+
+    parser.add_option("-d", "--debug", action="store_true", dest="debug",
+                      help="Display debugging information")
+
+    options, args = parser.parse_args()
+    safe_options = parser.get_safe_opts(options)
+
+    return safe_options, options, args
+
+
+if not is_ipa_configured():
+    # LSB status code 6: program is not configured
+    raise ScriptError("IPA is not configured " +
+                      "(see man pages of ipa-server-install for help)", 6)
+
+if not os.getegid() == 0:
+    # LSB status code 4: user had insufficient privilege
+    raise ScriptError("You must be root to run ipactl.", 4)
+
+safe_options, options, args = parse_options()
+
+if len(args) != 1:
+    # LSB status code 2: invalid or excess argument(s)
+    raise ScriptError("You must specify trusted domain name", 2)
+
+trusted_domain = unicode(args[0].lower())
+
+env = Env()
+env._bootstrap(context='server', debug=options.debug, log=None)
+env._finalize_core(**dict(DEFAULT_CONFIG))
+
+# Initialize the API with the proper debug level
+api.bootstrap(context='server', debug=env.debug, log=None)
+api.finalize()
+
+# Only import trust plugin after api is initialized or internal imports
+# within the plugin will not work
+from ipalib.plugins import trust
+
+# We have to dance with two different credentials caches:
+# ccache_name         --  for cifs/ipa.master at IPA.REALM to communicate with LDAP
+# oneway_ccache_name  --  for IPA$@AD.REALM to communicate with AD DCs
+#
+# ccache_name may not exist, we'll have to initialize it from Samba's keytab
+#
+# oneway_ccache_name may not exist either but to initialize it, we need
+# to check if oneway_keytab_name keytab exists and fetch it first otherwise.
+#
+# to fetch oneway_keytab_name keytab, we need to initialize ccache_name ccache first
+# and retrieve our own NetBIOS domain name and use cifs/ipa.master at IPA.REALM to
+# retrieve the keys to oneway_keytab_name.
+
+keytab_name = '/etc/samba/samba.keytab'
+oneway_keytab_name = '/var/lib/sss/keytabs/' + trusted_domain + '.keytab'
+
+principal = str('cifs/' + api.env.host)
+
+oneway_ccache_name = '/var/run/ipa/krb5cc_oddjob_trusts_fetch'
+ccache_name = '/var/run/ipa/krb5cc_oddjob_trusts'
+
+# Standard sequence:
+# - check if ccache exists
+#   - if not, initialize it from Samba's keytab
+# - check if ccache contains valid TGT
+#   - if not, initialize it from Samba's keytab
+# - refer the correct ccache object for further use
+#
+if not os.path.isfile(ccache_name):
+    ccache = kinit_keytab(principal, keytab_name, ccache_name)
+
+ccache_check = KRB5_CCache(ccache_name)
+if not ccache_check.credential_is_valid(principal):
+    ccache = kinit_keytab(principal, keytab_name, ccache_name)
+else:
+    ccache = ccache_check.ccache
+
+old_ccache = os.environ.get('KRB5CCNAME')
+api.Backend.ldap2.connect(ccache)
+
+own_trust_dn = DN(('cn', api.env.domain),('cn','ad'), ('cn', 'etc'), api.env.basedn)
+own_trust_entry = api.Backend.ldap2.get_entry(own_trust_dn, ['ipantflatname'])
+own_trust_flatname = own_trust_entry['ipantflatname'][0].upper()
+
+oneway_principal = str('%s$@%s' % (own_trust_flatname, trusted_domain.upper()))
+
+# If keytab does not exist, retrieve it
+if not os.path.isfile(oneway_keytab_name):
+    retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal)
+
+oneway_ccache = None
+try:
+    # The keytab may have stale key material (from older trust-add run)
+    if not os.path.isfile(oneway_ccache_name):
+        oneway_ccache = kinit_keytab(oneway_principal, oneway_keytab_name, oneway_ccache_name)
+except krbV.Krb5Error as e:
+    # If there was failure on using keytab, assume it is stale and retrieve again
+    retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal)
+
+if oneway_ccache:
+    # There wasn existing ccache, validate its content
+    oneway_ccache_check = KRB5_CCache(oneway_ccache_name)
+    if not oneway_ccache_check.credential_is_valid(oneway_principal):
+        # If credentials were invalid, obtain them again
+        oneway_ccache = kinit_keytab(oneway_principal, oneway_keytab_name, oneway_ccache_name)
+    else:
+        oneway_ccache = oneway_ccache_check.ccache
+else:
+    oneway_ccache = kinit_keytab(oneway_principal, oneway_keytab_name, oneway_ccache_name)
+
+# We are done: we have ccache with TDO credentials and can fetch domains
+ipa_domain = api.env.domain
+os.environ['KRB5CCNAME'] = oneway_ccache_name
+domains = dcerpc.fetch_domains(api, ipa_domain, trusted_domain, creds=True)
+
+if domains:
+    # trust range must exist by the time fetch_domains_from_trust is called
+    range_name = unicode(trusted_domain.upper() + '_id_range')
+    old_range = api.Command.idrange_show(range_name, raw=True)['result']
+    idrange_type = old_range['iparangetype'][0]
+
+    result = []
+    for dom in domains:
+        dom['trust_type'] = u'ad'
+        try:
+            name = dom['cn']
+            del dom['cn']
+
+            res = api.Command.trustdomain_add(trusted_domain, name, **dom)
+            result.append(res['result'])
+
+            if idrange_type != u'ipa-ad-trust-posix':
+                range_name = name.upper() + '_id_range'
+                dom['range_type'] = u'ipa-ad-trust'
+                trust.add_range(range_name, dom['ipanttrusteddomainsid'],
+                                trusted_domain, name, **dom)
+        except errors.DuplicateEntry:
+            # Ignore updating duplicate entries
+            pass
+
+if old_ccache:
+   os.environ['KRB5CCNAME'] = old_ccache
+
+sys.exit(0)
diff --git a/install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf b/install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf
new file mode 100644
index 0000000..2e4c136
--- /dev/null
+++ b/install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf
@@ -0,0 +1,40 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+ <!-- Only root can own (provide) the com.redhat.idm.trust service
+       on the system bus. -->
+  <policy user="root">
+    <allow own="com.redhat.idm.trust"/>
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="com.redhat.idm.trust"
+           send_member="fetch_domains"/>
+  </policy>
+
+  <!-- Allow anyone to call the introspection methods of the "/" object
+       provided by the com.redhat.idm.trust service. -->
+  <policy context="default">
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="org.freedesktop.DBus.Introspectable"
+           send_member="Introspect"/>
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="org.freedesktop.DBus.Properties"
+           send_member="GetAll"/>
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="org.freedesktop.DBus.Properties"
+           send_member="Get"/>
+  </policy>
+
+  <policy user="apache">
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="com.redhat.idm.trust"
+           send_member="fetch_domains"/>
+  </policy>
+
+</busconfig>
diff --git a/install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf b/install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf
new file mode 100644
index 0000000..17817de
--- /dev/null
+++ b/install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<oddjobconfig>
+  <service name="com.redhat.idm.trust">
+    <allow user="root"/>
+    <allow user="apache"/>
+    <object name="/">
+      <interface name="org.freedesktop.DBus.Introspectable">
+        <allow min_uid="0" max_uid="0"/>
+        <!-- <method name="Introspect"/> -->
+      </interface>
+      <interface name="com.redhat.idm.trust">
+        <method name="fetch_domains">
+          <helper exec="/usr/libexec/ipa/com.redhat.idm.trust-fetch-domains"
+		  arguments="1"
+                  argument_passing_method="cmdline"
+		  prepend_user_name="no"/>
+        </method>
+      </interface>
+    </object>
+  </service>
+</oddjobconfig>
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 13ac52d..9fbaf25 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -22,6 +22,7 @@ from ipalib.plugable import Registry
 from ipalib.plugins.baseldap import *
 from ipalib.plugins.dns import dns_container_exists
 from ipapython.ipautil import realm_to_suffix
+from ipapython.ipa_log_manager import root_logger
 from ipalib import api, Str, StrEnum, Password, Bool, _, ngettext
 from ipalib import Command
 from ipalib import errors
@@ -43,6 +44,8 @@ except Exception, e:
 if api.env.in_server and api.env.context in ['lite', 'server']:
     try:
         import ipaserver.dcerpc #pylint: disable=F0401
+        from ipaserver.dcerpc import TRUST_ONEWAY, TRUST_BIDIRECTIONAL
+        import dbus, dbus.mainloop.glib
         _bindings_installed = True
     except ImportError:
         _bindings_installed = False
@@ -161,6 +164,8 @@ _trust_type_option = StrEnum('trust_type',
 
 DEFAULT_RANGE_SIZE = 200000
 
+DBUS_IFACE_TRUST = 'com.redhat.idm.trust'
+
 def trust_type_string(level):
     """
     Returns a string representing a type of the trust. The original field is an enum:
@@ -191,7 +196,7 @@ def make_trust_dn(env, trust_type, dn):
         return DN(dn, container_dn)
     return dn
 
-def add_range(self, range_name, dom_sid, *keys, **options):
+def add_range(myapi, range_name, dom_sid, *keys, **options):
     """
     First, we try to derive the parameters of the ID range based on the
     information contained in the Active Directory.
@@ -224,7 +229,7 @@ def add_range(self, range_name, dom_sid, *keys, **options):
                   + basedn
 
         # Get the domain validator
-        domain_validator = ipaserver.dcerpc.DomainValidator(self.api)
+        domain_validator = ipaserver.dcerpc.DomainValidator(myapi)
         if not domain_validator.is_configured():
             raise errors.NotFound(
                 reason=_('Cannot search in trusted domains without own '
@@ -251,10 +256,10 @@ def add_range(self, range_name, dom_sid, *keys, **options):
 
         if not info_list:
             # We were unable to gain UNIX specific info from the AD
-            self.log.debug("Unable to gain POSIX info from the AD")
+            root_logger.debug("Unable to gain POSIX info from the AD")
         else:
             if all(attr in info for attr in required_msSFU_attrs):
-                self.log.debug("Able to gain POSIX info from the AD")
+                root_logger.debug("Able to gain POSIX info from the AD")
                 range_type = u'ipa-ad-trust-posix'
 
                 max_uid = info.get('msSFU30MaxUidNumber')
@@ -288,16 +293,43 @@ def add_range(self, range_name, dom_sid, *keys, **options):
         ) * DEFAULT_RANGE_SIZE
 
     # Finally, add new ID range
-    self.api.Command['idrange_add'](range_name,
-                                    ipabaseid=base_id,
-                                    ipaidrangesize=range_size,
-                                    ipabaserid=0,
-                                    iparangetype=range_type,
-                                    ipanttrusteddomainsid=dom_sid)
+    myapi.Command['idrange_add'](range_name,
+                                 ipabaseid=base_id,
+                                 ipaidrangesize=range_size,
+                                 ipabaserid=0,
+                                 iparangetype=range_type,
+                                 ipanttrusteddomainsid=dom_sid)
 
     # Return the values that were generated inside this function
     return range_type, range_size, base_id
 
+def fetch_trusted_domains_over_dbus(myapi, log, forest_name):
+    if not _bindings_installed:
+        return
+    # Calling oddjobd-activated service via DBus has some quirks:
+    # - Oddjobd registers multiple canonical names on the same address
+    # - python-dbus only follows name owner changes when mainloop is in use
+    # See https://fedorahosted.org/oddjob/ticket/2 for details
+    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+    try:
+        _ret = 0
+        _stdout = ''
+        _stderr = ''
+        bus = dbus.SystemBus()
+        intf = bus.get_object(DBUS_IFACE_TRUST,"/", follow_name_owner_changes=True)
+        fetch_domains_method = intf.get_dbus_method('fetch_domains', dbus_interface=DBUS_IFACE_TRUST)
+        (_ret, _stdout, _stderr) = fetch_domains_method(forest_name)
+    except dbus.DBusException, e:
+        log.error('Failed to call %(iface)s.fetch_domains helper.'
+                       'DBus exception is %(exc)s.' % dict(iface=DBUS_IFACE_TRUST, exc=str(e)))
+        if _ret != 0:
+            log.error('Helper was called for forest %(forest)s, return code is %(ret)d' % dict(forest=forest_name, ret=_ret))
+            log.error('Standard output from the helper:\n%s---\n' % (_stdout))
+            log.error('Error output from the helper:\n%s--\n' % (_stderr))
+        raise errors.ServerCommandError(server=myapi.env.host,
+                                        error=_('Fetching domains from trusted forest failed. '
+                                                'See details in the error_log'))
+    return
 
 @register()
 class trust(LDAPObject):
@@ -463,6 +495,12 @@ sides.
                  .format(vals=', '.join(range_types.keys())))),
             values=tuple(range_types.keys()),
         ),
+        Bool('bidirectional?',
+             label=_('Two-way trust'),
+             cli_name='two_way',
+             doc=(_('Establish bi-directional trust. By default trust is inbound one-way only.')),
+             default=False,
+        ),
     )
 
     msg_summary = _('Added Active Directory trust for realm "%(value)s"')
@@ -478,7 +516,7 @@ sides.
             # Store the created range type, since for POSIX trusts no
             # ranges for the subdomains should be added, POSIX attributes
             # provide a global mapping across all subdomains
-            (created_range_type, _, _) = add_range(self, range_name, dom_sid,
+            (created_range_type, _, _) = add_range(self.api, range_name, dom_sid,
                                                    *keys, **options)
         else:
             created_range_type = old_range['result']['iparangetype'][0]
@@ -486,19 +524,35 @@ sides.
         trust_filter = "cn=%s" % result['value']
         ldap = self.obj.backend
         (trusts, truncated) = ldap.find_entries(
-                         base_dn=DN(api.env.container_trusts, api.env.basedn),
+                         base_dn=DN(self.api.env.container_trusts, self.api.env.basedn),
                          filter=trust_filter)
 
         result['result'] = entry_to_dict(trusts[0], **options)
 
         # Fetch topology of the trust forest -- we need always to do it
         # for AD trusts, regardless of the type of idranges associated with it
-        # Note that fetch_domains_from_trust will add needed ranges for
+        # Note that add_new_domains_from_trust will add needed ranges for
         # the algorithmic ID mapping case.
         if (options.get('trust_type') == u'ad' and
             options.get('trust_secret') is None):
-            domains = fetch_domains_from_trust(self, self.trustinstance,
+            if options.get('bidirectional') == True:
+                # Bidirectional trust allows us to use cross-realm TGT, so we can
+                # run the call under original user's credentials
+                res = fetch_domains_from_trust(self.api, self.trustinstance,
                                                result['result'], **options)
+                domains = add_new_domains_from_trust(self.api, self.trustinstance,
+                                                     result['result'], res, **options)
+            else:
+                # One-way trust is more complex. We don't have cross-realm TGT
+                # and cannot use IPA principals to authenticate against AD.
+                # Instead, we have to use our trusted domain object's (TDO)
+                # account in AD. Access to the credentials is limited and IPA
+                # framework cannot access it directly.  Instead, we call out to
+                # oddjobd-activated higher privilege process that will use TDO
+                # object credentials to authenticate to AD with Kerberos,
+                # run DCE RPC calls to do discovery and will call
+                # add_new_domains_from_trust() on its own.
+                fetch_trusted_domains_over_dbus(self.api, self.log, result['value'])
 
         # Format the output into human-readable values
         result['result']['trusttype'] = [trust_type_string(
@@ -570,7 +624,7 @@ sides.
         # If domain name and realm does not match, IPA server is not be able
         # to establish trust with Active Directory.
 
-        realm_not_matching_domain = (api.env.domain.upper() != api.env.realm)
+        realm_not_matching_domain = (self.api.env.domain.upper() != self.api.env.realm)
 
         if options['trust_type'] == u'ad' and realm_not_matching_domain:
             raise errors.ValidationError(
@@ -627,7 +681,7 @@ sides.
         range_type = options.get('range_type')
 
         try:
-            old_range = api.Command['idrange_show'](range_name, raw=True)
+            old_range = self.api.Command['idrange_show'](range_name, raw=True)
         except errors.NotFound:
             old_range = None
 
@@ -699,6 +753,9 @@ sides.
         except errors.NotFound:
             dn = None
 
+        trust_type = TRUST_ONEWAY
+        if options.get('bidirectional', False):
+            trust_type = TRUST_BIDIRECTIONAL
         # 1. Full access to the remote domain. Use admin credentials and
         # generate random trustdom password to do work on both sides
         if full_join:
@@ -707,14 +764,15 @@ sides.
                     keys[-1],
                     self.realm_server,
                     self.realm_admin,
-                    self.realm_passwd
+                    self.realm_passwd,
+                    trust_type
                 )
             except errors.NotFound:
                 error_message=_("Unable to resolve domain controller for '%s' domain. ") % (keys[-1])
                 instructions=[]
                 if dns_container_exists(self.obj.backend):
                     try:
-                        dns_zone = api.Command.dnszone_show(keys[-1])['result']
+                        dns_zone = self.api.Command.dnszone_show(keys[-1])['result']
                         if ('idnsforwardpolicy' in dns_zone) and dns_zone['idnsforwardpolicy'][0] == u'only':
                             instructions.append(_("Forward policy is defined for it in IPA DNS, "
                                                    "perhaps forwarder points to incorrect host?"))
@@ -755,7 +813,8 @@ sides.
             result = self.trustinstance.join_ad_ipa_half(
                 keys[-1],
                 self.realm_server,
-                options['trust_secret']
+                options['trust_secret'],
+                trust_type
             )
             ret = dict(
                 value=pkey_to_value(
@@ -940,7 +999,7 @@ class trustconfig(LDAPObject):
                     group,
                     ['posixgroup'],
                     [''],
-                    DN(api.env.container_group, api.env.basedn))
+                    DN(self.api.env.container_group, self.api.env.basedn))
             except errors.NotFound:
                 self.api.Object['group'].handle_not_found(group)
             else:
@@ -1066,11 +1125,11 @@ class adtrust_is_enabled(Command):
         ldap = self.api.Backend.ldap2
         adtrust_dn = DN(
             ('cn', 'ADTRUST'),
-            ('cn', api.env.host),
+            ('cn', self.api.env.host),
             ('cn', 'masters'),
             ('cn', 'ipa'),
             ('cn', 'etc'),
-            api.env.basedn
+            self.api.env.basedn
         )
 
         try:
@@ -1281,7 +1340,7 @@ class trustdomain_del(LDAPDelete):
                 raise errors.ValidationError(name='domain',
                     error=_("cannot delete root domain of the trust, use trust-del to delete the trust itself"))
             try:
-                res = api.Command.trustdomain_enable(keys[0], domain)
+                res = self.api.Command.trustdomain_enable(keys[0], domain)
             except errors.AlreadyActive:
                 pass
         result = super(trustdomain_del, self).execute(*keys, **options)
@@ -1291,7 +1350,7 @@ class trustdomain_del(LDAPDelete):
 
 
 
-def fetch_domains_from_trust(self, trustinstance, trust_entry, **options):
+def fetch_domains_from_trust(myapi, trustinstance, trust_entry, **options):
     trust_name = trust_entry['cn'][0]
     creds = None
     password = options.get('realm_passwd', None)
@@ -1303,16 +1362,20 @@ def fetch_domains_from_trust(self, trustinstance, trust_entry, **options):
         creds = u"{name}%{password}".format(name="\\".join(sp),
                                             password=password)
     server = options.get('realm_server', None)
-    domains = ipaserver.dcerpc.fetch_domains(self.api,
+    domains = ipaserver.dcerpc.fetch_domains(myapi,
                                              trustinstance.local_flatname,
                                              trust_name, creds=creds, server=server)
+    return domains
+
+def add_new_domains_from_trust(myapi, trustinstance, trust_entry, domains, **options):
     result = []
     if not domains:
         return result
 
-    # trust range must exist by the time fetch_domains_from_trust is called
+    trust_name = trust_entry['cn'][0]
+    # trust range must exist by the time add_new_domains_from_trust is called
     range_name = trust_name.upper() + '_id_range'
-    old_range = api.Command.idrange_show(range_name, raw=True)['result']
+    old_range = myapi.Command.idrange_show(range_name, raw=True)['result']
     idrange_type = old_range['iparangetype'][0]
 
     for dom in domains:
@@ -1325,13 +1388,13 @@ def fetch_domains_from_trust(self, trustinstance, trust_entry, **options):
             if 'raw' in options:
                 dom['raw'] = options['raw']
 
-            res = self.api.Command.trustdomain_add(trust_name, name, **dom)
+            res = myapi.Command.trustdomain_add(trust_name, name, **dom)
             result.append(res['result'])
 
             if idrange_type != u'ipa-ad-trust-posix':
                 range_name = name.upper() + '_id_range'
                 dom['range_type'] = u'ipa-ad-trust'
-                add_range(self, range_name, dom['ipanttrusteddomainsid'],
+                add_range(myapi, range_name, dom['ipanttrusteddomainsid'],
                           trust_name, name, **dom)
         except errors.DuplicateEntry:
             # Ignore updating duplicate entries
@@ -1362,6 +1425,17 @@ class trust_fetch_domains(LDAPRetrieve):
             )
         trust = self.api.Command.trust_show(keys[0], raw=True)['result']
 
+        result = dict()
+        result['result'] = []
+        result['count'] = 0
+        result['truncated'] = False
+
+        # For one-way trust fetch over DBus. we don't get the list in this case.
+        if trust['ipanttrustdirection'] & TRUST_BIDIRECTIONAL != TRUST_BIDIRECTIONAL:
+            fetch_trusted_domains_over_dbus(self.api, self.log, keys[0])
+            result['summary'] = unicode(_('List of trust domains successfully refreshed. Use trustdomain-find command to list them.'))
+            return result
+
         trustinstance = ipaserver.dcerpc.TrustDomainJoins(self.api)
         if not trustinstance.configured:
             raise errors.NotFound(
@@ -1372,8 +1446,8 @@ class trust_fetch_domains(LDAPRetrieve):
                     'on the IPA server first'
                 )
             )
-        domains = fetch_domains_from_trust(self, trustinstance, trust)
-        result = dict()
+        res = fetch_domains_from_trust(self.api, trustinstance, trust, **options)
+        domains = add_new_domains_from_trust(self.api, trustinstance, trust, res, **options)
 
         if len(domains) > 0:
             result['summary'] = unicode(_('List of trust domains successfully refreshed'))
@@ -1382,7 +1456,6 @@ class trust_fetch_domains(LDAPRetrieve):
 
         result['result'] = domains
         result['count'] = len(domains)
-        result['truncated'] = False
         return result
 
 
@@ -1413,7 +1486,7 @@ class trustdomain_enable(LDAPQuery):
                 trust_entry['ipantsidblacklistincoming'].remove(sid)
                 ldap.update_entry(trust_entry)
                 # Force MS-PAC cache re-initialization on KDC side
-                domval = ipaserver.dcerpc.DomainValidator(api)
+                domval = ipaserver.dcerpc.DomainValidator(self.api)
                 (ccache_name, principal) = domval.kinit_as_http(keys[0])
             else:
                 raise errors.AlreadyActive()
@@ -1453,7 +1526,7 @@ class trustdomain_disable(LDAPQuery):
                 trust_entry['ipantsidblacklistincoming'].append(sid)
                 ldap.update_entry(trust_entry)
                 # Force MS-PAC cache re-initialization on KDC side
-                domval = ipaserver.dcerpc.DomainValidator(api)
+                domval = ipaserver.dcerpc.DomainValidator(self.api)
                 (ccache_name, principal) = domval.kinit_as_http(keys[0])
             else:
                 raise errors.AlreadyInactive()
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 753e10e..b11233d 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -66,6 +66,10 @@ The code in this module relies heavily on samba4-python package
 and Samba4 python bindings.
 """)
 
+# Both constants can be used as masks against trust direction
+# because bi-directional has two lower bits set.
+TRUST_ONEWAY        = 1
+TRUST_BIDIRECTIONAL = 3
 
 def is_sid_valid(sid):
     try:
@@ -949,7 +953,7 @@ class TrustDomainInstance(object):
             # We can ignore the error here -- setting up name suffix routes may fail
             pass
 
-    def establish_trust(self, another_domain, trustdom_secret):
+    def establish_trust(self, another_domain, trustdom_secret, trust_type='bidirectional'):
         """
         Establishes trust between our and another domain
         Input: another_domain -- instance of TrustDomainInstance, initialized with #retrieve call
@@ -967,7 +971,9 @@ class TrustDomainInstance(object):
         info.domain_name.string = another_domain.info['dns_domain']
         info.netbios_name.string = another_domain.info['name']
         info.sid = security.dom_sid(another_domain.info['sid'])
-        info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
+        info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND
+        if trust_type == TRUST_BIDIRECTIONAL:
+            info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
         info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
         info.trust_attributes = 0
 
@@ -1005,7 +1011,8 @@ class TrustDomainInstance(object):
             pass
 
         try:
-            info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
+            info = self._pipe.QueryTrustedDomainInfo(trustdom_handle, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
+            info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
             self._pipe.SetInformationTrustedDomain(trustdom_handle, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX, info)
         except RuntimeError, e:
             root_logger.error('unable to set trust to transitive: %s' % (str(e)))
@@ -1014,10 +1021,10 @@ class TrustDomainInstance(object):
             self.update_ftinfo(another_domain)
 
     def verify_trust(self, another_domain):
-        def retrieve_netlogon_info_2(domain, function_code, data):
+        def retrieve_netlogon_info_2(logon_server, domain, function_code, data):
             try:
                 netr_pipe = netlogon.netlogon(domain.binding, domain.parm, domain.creds)
-                result = netr_pipe.netr_LogonControl2Ex(logon_server=None,
+                result = netr_pipe.netr_LogonControl2Ex(logon_server=logon_server,
                                            function_code=function_code,
                                            level=2,
                                            data=data
@@ -1026,7 +1033,7 @@ class TrustDomainInstance(object):
             except RuntimeError, (num, message):
                 raise assess_dcerpc_exception(num=num, message=message)
 
-        result = retrieve_netlogon_info_2(self,
+        result = retrieve_netlogon_info_2(None, self,
                                           netlogon.NETLOGON_CONTROL_TC_VERIFY,
                                           another_domain.info['dns_domain'])
         if (result and (result.flags and netlogon.NETLOGON_VERIFY_STATUS_RETURNED)):
@@ -1098,6 +1105,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
 
     td.info['dc'] = unicode(result.pdc_dns_name)
     if creds is None:
+        # Attempt to authenticate as HTTP/ipa.master and use cross-forest trust
         domval = DomainValidator(api)
         (ccache_name, principal) = domval.kinit_as_http(trustdomain)
         td.creds = credentials.Credentials()
@@ -1107,7 +1115,15 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
                 td.creds.guess(td.parm)
                 td.creds.set_workstation(domain_validator.flatname)
                 domains = communicate(td)
+    elif type(creds) is bool:
+        # Rely on existing Kerberos credentials in the environment
+        td.creds = credentials.Credentials()
+        td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
+        td.creds.guess(td.parm)
+        td.creds.set_workstation(domain_validator.flatname)
+        domains = communicate(td)
     else:
+        # Assume we've got credentials as a string user%password
         td.creds = credentials.Credentials()
         td.creds.set_kerberos_state(credentials.DONT_USE_KERBEROS)
         td.creds.guess(td.parm)
@@ -1220,7 +1236,7 @@ class TrustDomainJoins(object):
             ftinfo['rec_type'] = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME
             self.local_domain.ftinfo_records.append(ftinfo)
 
-    def join_ad_full_credentials(self, realm, realm_server, realm_admin, realm_passwd):
+    def join_ad_full_credentials(self, realm, realm_server, realm_admin, realm_passwd, trust_type):
         if not self.configured:
             return None
 
@@ -1238,13 +1254,17 @@ class TrustDomainJoins(object):
         if not self.remote_domain.read_only:
             trustdom_pass = samba.generate_random_password(128, 128)
             self.get_realmdomains()
-            self.remote_domain.establish_trust(self.local_domain, trustdom_pass)
-            self.local_domain.establish_trust(self.remote_domain, trustdom_pass)
-            result = self.remote_domain.verify_trust(self.local_domain)
+            self.remote_domain.establish_trust(self.local_domain, trustdom_pass, trust_type)
+            self.local_domain.establish_trust(self.remote_domain, trustdom_pass, trust_type)
+            # if trust is inbound, we don't need to verify it because AD DC will respond
+            # with WERR_NO_SUCH_DOMAIN -- in only does verification for outbound trusts.
+            result = True
+            if trust_type == TRUST_BIDIRECTIONAL:
+                result = self.remote_domain.verify_trust(self.local_domain)
             return dict(local=self.local_domain, remote=self.remote_domain, verified=result)
         return None
 
-    def join_ad_ipa_half(self, realm, realm_server, trustdom_passwd):
+    def join_ad_ipa_half(self, realm, realm_server, trustdom_passwd, trust_type):
         if not self.configured:
             return None
 
@@ -1254,5 +1274,5 @@ class TrustDomainJoins(object):
         if self.remote_domain.info['dns_domain'] != self.remote_domain.info['dns_forest']:
             raise errors.NotAForestRootError(forest=self.remote_domain.info['dns_forest'], domain=self.remote_domain.info['dns_domain'])
 
-        self.local_domain.establish_trust(self.remote_domain, trustdom_passwd)
+        self.local_domain.establish_trust(self.remote_domain, trustdom_passwd, trust_type)
         return dict(local=self.local_domain, remote=self.remote_domain, verified=False)
-- 
2.4.3

-------------- next part --------------
From 1494495fbfdc1c7d14f05825a437e0020c2b5f94 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Fri, 5 Jun 2015 15:31:32 +0000
Subject: [PATCH 09/11] ipa-pwd-extop: expand error message to tell what user
 is not allowed to fetch keytab

When retrieving keytab, it is useful to know what user was attempting
to fetch the keyts and failed. This is useful to debug one-way trust
where SSSD forks out a process of ipa-getkeytab and it might be using
a wrong credentials cache for authentication purposes.

Part of https://fedorahosted.org/freeipa/ticket/4959
---
 daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
index 09c877f..dc657cc 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
@@ -1612,8 +1612,8 @@ static int ipapwd_getkeytab(Slapi_PBlock *pb, struct ipapwd_krbcfg *krbcfg)
                                            READKEYS_OP_CHECK, NULL,
                                            SLAPI_ACL_READ);
         if (!acl_ok) {
-            LOG_FATAL("Not allowed to retrieve keytab on [%s]!\n",
-                      service_name);
+            LOG_FATAL("Not allowed to retrieve keytab on [%s] as user [%s]!\n",
+                      service_name, bind_dn);
             err_msg = "Insufficient access rights\n";
             rc = LDAP_INSUFFICIENT_ACCESS;
             goto free_and_return;
-- 
2.4.3

-------------- next part --------------
From 992b5dbd372c3c7b2cb7ff7a3713810e3f59515e Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Fri, 5 Jun 2015 17:56:12 +0000
Subject: [PATCH 10/11] trusts: add ACIs to allow AD trust agents to fetch
 cross-realm keytabs

Part of https://fedorahosted.org/freeipa/ticket/4959
---
 install/updates/60-trusts.update | 1 +
 1 file changed, 1 insertion(+)

diff --git a/install/updates/60-trusts.update b/install/updates/60-trusts.update
index d11c765..df9468e 100644
--- a/install/updates/60-trusts.update
+++ b/install/updates/60-trusts.update
@@ -27,6 +27,7 @@ default: cn: trusts
 # 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: (targetattr="ipaProtectedOperation;read_keys")(version 3.0; acl "Allow trust agents to retrieve keytab keys for cross realm principals"; allow(read) userattr="ipaAllowedToPerform;read_keys#GROUPDN";)
 add:aci: (target = "ldap:///cn=trusts,$SUFFIX")(targetattr = "ipaNTTrustType || ipaNTTrustAttributes || ipaNTTrustDirection || ipaNTTrustPartner || ipaNTFlatName || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming || ipaNTSecurityIdentifier || ipaNTTrustForestTrustInfo || ipaNTTrustPosixOffset || ipaNTSupportedEncryptionTypes || krbPrincipalName || krbLastPwdChange || krbTicketFlags || krbLoginFailedCount || krbExtraData || krbPrincipalKey")(version 3.0;acl "Allow trust system user to create and delete trust accounts and cross realm principals"; allow (read,write,add,delete) groupdn="ldap:///cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX";)
 replace:aci:(target = "ldap:///cn=trusts,$SUFFIX")(targetattr = "ipaNTTrustType || ipaNTTrustAttributes || ipaNTTrustDirection || ipaNTTrustPartner || ipaNTFlatName || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming || ipaNTSecurityIdentifier || ipaNTTrustForestTrustInfo || ipaNTTrustPosixOffset || ipaNTSupportedEncryptionTypes || krbPrincipalName || krbLastPwdChange || krbTicketFlags || krbLoginFailedCount || krbExtraData || krbPrincipalKey")(version 3.0;acl "Allow trust system user to create and delete trust accounts and cross realm principals"; allow (read,write,add,delete) groupdn="ldap:///cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX";)::(target = "ldap:///cn=trusts,$SUFFIX")(targetattr = "ipaNTTrustType || ipaNTTrustAttributes || ipaNTTrustDirection || ipaNTTrustPartner || ipaNTFlatName || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming || ipaNTSecurityIdentifier || ipaNTTrustForestTrustInfo || ipaNTTrustPosixOffset || ipaNTSupportedEncryptionTypes || ipaNTSIDBlacklistIncoming || ipaNTSIDBlacklistOutgoing || krbPrincipalName || krbLastPwdChange || krbTicketFlags || krbLoginFailedCount || krbExtraData || krbPrincipalKey")(version 3.0;acl "Allow trust system user to create and delete trust accounts and cross realm principals"; allow (read,write,add,delete) groupdn="ldap:///cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX";)
 replace: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";)::(target = "ldap:///cn=trusts,$SUFFIX")(targetattr = "ipaNTTrustType || ipaNTTrustAttributes || ipaNTTrustDirection || ipaNTTrustPartner || ipaNTFlatName || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming || ipaNTSecurityIdentifier || ipaNTTrustForestTrustInfo || ipaNTTrustPosixOffset || ipaNTSupportedEncryptionTypes || ipaNTSIDBlacklistIncoming || ipaNTSIDBlacklistOutgoing")(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";)
-- 
2.4.3

-------------- next part --------------
From 3628ba9002bbea9c94f2a89c11ba3740bff64882 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Mon, 6 Jul 2015 14:46:24 +0000
Subject: [PATCH 11/11] trust: support retrieving POSIX IDs with one-way trust
 during trust-add

With one-way trust we cannot rely on cross-realm TGT as there will be none.
Thus, if we have AD administrator credentials we should reuse them.
Additionally, such use should be done over Kerberos.

Fixes:
 https://fedorahosted.org/freeipa/ticket/4960
 https://fedorahosted.org/freeipa/ticket/4959
---
 install/oddjob/com.redhat.idm.trust-fetch-domains |  4 +-
 ipalib/plugins/trust.py                           | 65 ++++++++++++++----
 ipaserver/dcerpc.py                               | 83 ++++++++++++++++++-----
 3 files changed, 119 insertions(+), 33 deletions(-)

diff --git a/install/oddjob/com.redhat.idm.trust-fetch-domains b/install/oddjob/com.redhat.idm.trust-fetch-domains
index 2571dd0..85e3cc9 100755
--- a/install/oddjob/com.redhat.idm.trust-fetch-domains
+++ b/install/oddjob/com.redhat.idm.trust-fetch-domains
@@ -186,7 +186,9 @@ if domains:
             if idrange_type != u'ipa-ad-trust-posix':
                 range_name = name.upper() + '_id_range'
                 dom['range_type'] = u'ipa-ad-trust'
-                trust.add_range(range_name, dom['ipanttrusteddomainsid'],
+                # Do not pass ipaserver.dcerpc.TrustInstance to trust.add_range
+                # to force it using existing credentials cache
+                trust.add_range(None, range_name, dom['ipanttrusteddomainsid'],
                                 trusted_domain, name, **dom)
         except errors.DuplicateEntry:
             # Ignore updating duplicate entries
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 9fbaf25..196df59 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -166,6 +166,9 @@ DEFAULT_RANGE_SIZE = 200000
 
 DBUS_IFACE_TRUST = 'com.redhat.idm.trust'
 
+CRED_STYLE_SAMBA = 1
+CRED_STYLE_KERBEROS = 2
+
 def trust_type_string(level):
     """
     Returns a string representing a type of the trust. The original field is an enum:
@@ -196,7 +199,44 @@ def make_trust_dn(env, trust_type, dn):
         return DN(dn, container_dn)
     return dn
 
-def add_range(myapi, range_name, dom_sid, *keys, **options):
+def generate_creds(trustinstance, style, **options):
+    """
+    Generate string representing credentials using trust instance
+    Input:
+       trustinstance -- ipaserver.dcerpc.TrustInstance object
+       style         -- style of credentials
+                        CRED_STYLE_SAMBA -- for using with Samba bindings
+                        CRED_STYLE_KERBEROS -- for obtaining Kerberos ticket
+       **options     -- options with realm_admin and realm_passwd keys
+
+    Result:
+       a string representing credentials with first % separating username and password
+       None is returned if realm_passwd key returns nothing from options
+    """
+    creds = None
+    password = options.get('realm_passwd', None)
+    if password:
+        admin_name = options.get('realm_admin')
+        sp = []
+        sep = '@'
+        if style == CRED_STYLE_SAMBA:
+            sep = "\\"
+            sp = admin_name.split(sep)
+            if len(sp) == 1:
+                sp.insert(0, trustinstance.remote_domain.info['name'])
+        elif style == CRED_STYLE_KERBEROS:
+            sp = admin_name.split('\\')
+            if len(sp) > 1:
+               sp = [sp[1]]
+            else:
+               sp = admin_name.split(sep)
+            if len(sp) == 1:
+                sp.append(trustinstance.remote_domain.info['dns_forest'].upper())
+        creds = u"{name}%{password}".format(name=sep.join(sp),
+                                            password=password)
+    return creds
+
+def add_range(myapi, trustinstance, range_name, dom_sid, *keys, **options):
     """
     First, we try to derive the parameters of the ID range based on the
     information contained in the Active Directory.
@@ -236,6 +276,12 @@ def add_range(myapi, range_name, dom_sid, *keys, **options):
                          'domain configured. Make sure you have run '
                          'ipa-adtrust-install on the IPA server first'))
 
+        creds = None
+        if trustinstance:
+            # Re-use AD administrator credentials if they were provided
+            creds = generate_creds(trustinstance, style=CRED_STYLE_KERBEROS, **options)
+            if creds:
+                domain_validator._admin_creds = creds
         # KDC might not get refreshed data at the first time,
         # retry several times
         for retry in range(10):
@@ -516,7 +562,8 @@ sides.
             # Store the created range type, since for POSIX trusts no
             # ranges for the subdomains should be added, POSIX attributes
             # provide a global mapping across all subdomains
-            (created_range_type, _, _) = add_range(self.api, range_name, dom_sid,
+            (created_range_type, _, _) = add_range(self.api, self.trustinstance,
+                                                   range_name, dom_sid,
                                                    *keys, **options)
         else:
             created_range_type = old_range['result']['iparangetype'][0]
@@ -1348,19 +1395,9 @@ class trustdomain_del(LDAPDelete):
         return result
 
 
-
-
 def fetch_domains_from_trust(myapi, trustinstance, trust_entry, **options):
     trust_name = trust_entry['cn'][0]
-    creds = None
-    password = options.get('realm_passwd', None)
-    if password:
-        admin_name = options.get('realm_admin')
-        sp = admin_name.split('\\')
-        if len(sp) == 1:
-            sp.insert(0, trustinstance.remote_domain.info['name'])
-        creds = u"{name}%{password}".format(name="\\".join(sp),
-                                            password=password)
+    creds = generate_creds(trustinstance, style=CRED_STYLE_SAMBA, **options)
     server = options.get('realm_server', None)
     domains = ipaserver.dcerpc.fetch_domains(myapi,
                                              trustinstance.local_flatname,
@@ -1394,7 +1431,7 @@ def add_new_domains_from_trust(myapi, trustinstance, trust_entry, domains, **opt
             if idrange_type != u'ipa-ad-trust-posix':
                 range_name = name.upper() + '_id_range'
                 dom['range_type'] = u'ipa-ad-trust'
-                add_range(myapi, range_name, dom['ipanttrusteddomainsid'],
+                add_range(myapi, trustinstance, range_name, dom['ipanttrusteddomainsid'],
                           trust_name, name, **dom)
         except errors.DuplicateEntry:
             # Ignore updating duplicate entries
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index b11233d..bc75a60 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -151,6 +151,7 @@ class DomainValidator(object):
         self._domains = None
         self._info = dict()
         self._creds = None
+        self._admin_creds = None
         self._parm = None
 
     def is_configured(self):
@@ -565,6 +566,52 @@ class DomainValidator(object):
                               % (stdout, stderr))
             return (None, None)
 
+    def kinit_as_administrator(self, domain):
+        """
+        Initializes ccache with http service credentials.
+
+        Applies session code defaults for ccache directory and naming prefix.
+        Session code uses krbccache_prefix+<pid>, we use
+        krbccache_prefix+<TD>+<domain netbios name> so there is no clash.
+
+        Returns tuple (ccache path, principal) where (None, None) signifes an
+        error on ccache initialization
+        """
+
+        if self._admin_creds == None:
+            return (None, None)
+
+        domain_suffix = domain.replace('.', '-')
+
+        ccache_name = "%sTDA%s" % (krbccache_prefix, domain_suffix)
+        ccache_path = os.path.join(krbccache_dir, ccache_name)
+
+        (principal, password) = self._admin_creds.split('%', 1)
+
+        # Destroy the contents of the ccache
+        root_logger.debug('Destroying the contents of the separate ccache')
+
+        (stdout, stderr, returncode) = ipautil.run(
+            [paths.KDESTROY, '-A', '-c', ccache_path],
+            env={'KRB5CCNAME': ccache_path},
+            raiseonerr=False)
+
+        # Destroy the contents of the ccache
+        root_logger.debug('Running kinit with credentials of AD administrator')
+
+        (stdout, stderr, returncode) = ipautil.run(
+            [paths.KINIT, principal],
+            env={'KRB5CCNAME': ccache_path},
+            stdin=password,
+            raiseonerr=False)
+
+        if returncode == 0:
+            return (ccache_path, principal)
+        else:
+            root_logger.debug('Kinit failed, stout: %s, stderr: %s'
+                              % (stdout, stderr))
+            return (None, None)
+
     def search_in_dc(self, domain, filter, attrs, scope, basedn=None,
                      quiet=False):
         """
@@ -597,7 +644,8 @@ class DomainValidator(object):
         Returns LDAP result or None.
         """
 
-        (ccache_name, principal) = self.kinit_as_http(info['dns_domain'])
+        if self._admin_creds:
+            (ccache_name, principal) = self.kinit_as_administrator(info['dns_domain'])
 
         if ccache_name:
             with ipautil.private_ccache(path=ccache_name):
@@ -1104,10 +1152,24 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
         raise assess_dcerpc_exception(message=str(e))
 
     td.info['dc'] = unicode(result.pdc_dns_name)
-    if creds is None:
+    if type(creds) is bool:
+        # Rely on existing Kerberos credentials in the environment
+        td.creds = credentials.Credentials()
+        td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
+        td.creds.guess(td.parm)
+        td.creds.set_workstation(domain_validator.flatname)
+        domains = communicate(td)
+    else:
         # Attempt to authenticate as HTTP/ipa.master and use cross-forest trust
+        # or as passed-in user in case of a one-way trust
         domval = DomainValidator(api)
-        (ccache_name, principal) = domval.kinit_as_http(trustdomain)
+        ccache_name = None
+        principal = None
+        if creds:
+            domval._admin_creds = creds
+            (ccache_name, principal) = domval.kinit_as_administrator(trustdomain)
+        else:
+            (ccache_name, principal) = domval.kinit_as_http(trustdomain)
         td.creds = credentials.Credentials()
         td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
         if ccache_name:
@@ -1115,21 +1177,6 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
                 td.creds.guess(td.parm)
                 td.creds.set_workstation(domain_validator.flatname)
                 domains = communicate(td)
-    elif type(creds) is bool:
-        # Rely on existing Kerberos credentials in the environment
-        td.creds = credentials.Credentials()
-        td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
-        td.creds.guess(td.parm)
-        td.creds.set_workstation(domain_validator.flatname)
-        domains = communicate(td)
-    else:
-        # Assume we've got credentials as a string user%password
-        td.creds = credentials.Credentials()
-        td.creds.set_kerberos_state(credentials.DONT_USE_KERBEROS)
-        td.creds.guess(td.parm)
-        td.creds.parse_string(creds)
-        td.creds.set_workstation(domain_validator.flatname)
-        domains = communicate(td)
 
     if domains is None:
         return None
-- 
2.4.3

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 473 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/freeipa-devel/attachments/20150707/8df497da/attachment.sig>


More information about the Freeipa-devel mailing list