Penrose is a neat LDAP Virtual Directory that can make almost any combination of databases and other LDAP servers appear to be a single directory, for applications that don’t support communicating with more than one LDAP server. This includes doing things like passing through bind requests and updates, in addition to just searches.
Although Kerberos works pretty seamlessly between forests (if there’s a trust in place), LDAP can be a bit more complicated. Especially if your application doesn’t support following referrals. One useful but quite simple thing you can do is make several Active Directory forests appear to be a single LDAP server (for certain types of applications) by merging their Global Catalogs together. Here’s an example for a forest ‘vc.example.com’ that has the child domain ‘uk.vc.example.com’, joined to another forest ‘xx.example.com’ that only has a single domain. For every search Penrose will query both forest GCs and return all the results in a single response. With this configuration, the following types of queries work correctly:
Looking up a user based on userPrincipalName (or any other attribute), that exists in any domain:
imac:/Users/chrisl/ 1$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D cn=penrose,dc=example,dc=com -w penrose -b dc=example,dc=com '(userPrincipalName=harryp@vc.example.com)' cn dn: CN=Harry Potter,OU=People,DC=vc,DC=example,DC=com cn: Harry Potter imac:/Users/chrisl/ 2$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D cn=penrose,dc=example,dc=com -w penrose -b dc=example,dc=com '(userPrincipalName=tomr@uk.vc.example.com)' cn dn: CN=Tom Riddle,OU=People,DC=uk,DC=vc,DC=example,DC=com cn: Tom Riddle imac:/Users/chrisl/ 3$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D cn=penrose,dc=example,dc=com -w penrose -b dc=example,dc=com '(userPrincipalName=lunal@xx.example.com)' cn dn: CN=Luna Lovegood,OU=People,DC=xx,DC=example,DC=com cn: Luna Lovegood
Looking up multiple users across forests that match a particular attribute. Note users pulled from vc.example.com, uk.vc.example.com and xx.example.com:
imac:/Users/chrisl/ 4$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D cn=penrose,dc=example,dc=com -w penrose -b dc=example,dc=com '(mail=*.example.com)' cn dn: CN=Harry Potter,OU=People,DC=vc,DC=example,DC=com cn: Harry Potter dn: CN=Ron Weasley,OU=People,DC=vc,DC=example,DC=com cn: Ron Weasley dn: CN=Hermione Granger,OU=People,DC=vc,DC=example,DC=com cn: Hermione Granger dn: CN=Tom Riddle,OU=People,DC=uk,DC=vc,DC=example,DC=com cn: Tom Riddle dn: CN=Luna Lovegood,OU=People,DC=xx,DC=example,DC=com cn: Luna Lovegood
Looking up a list of groups to be used by an application, based on those groups being a member of a ‘service group’:
imac:/Users/chrisl/ 5$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D cn=penrose,dc=example,dc=com -w penrose -b dc=example,dc=com '(&(objectclass=group)(|(memberof=CN=Penrose Groups,OU=People,DC=xx,DC=example,DC=com)(memberof=CN=Penrose Groups,OU=People,DC=vc,DC=example,DC=com)))' cn dn: CN=Sales,OU=People,DC=vc,DC=example,DC=com cn: Sales dn: CN=Engineering,OU=People,DC=vc,DC=example,DC=com cn: Engineering dn: CN=Marketing,OU=People,DC=vc,DC=example,DC=com cn: Marketing dn: CN=Engineering XX,OU=People,DC=xx,DC=example,DC=com cn: Engineering XX
Performing LDAP authentication from an email address and password, using the LDAP search+bind method. Penrose automatically passes through the credentials:
imac:/Users/chrisl/ 6$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D cn=penrose,dc=example,dc=com -w penrose -b dc=example,dc=com '(mail=harryp@vc.example.com)' dn dn: CN=Harry Potter,OU=People,DC=vc,DC=example,DC=com imac:/Users/chrisl/ 7$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D 'CN=Harry Potter,OU=People,DC=vc,DC=example,DC=com' -w hpotter -b 'CN=Harry Potter,OU=People,DC=vc,DC=example,DC=com' cn dn: CN=Harry Potter,OU=People,DC=vc,DC=example,DC=com cn: Harry Potter
Special queries with bitwise filters work too, for example recursively finding all groups a user is a member of:
imac:/Users/chrisl/ 8$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D cn=penrose,dc=example,dc=com -w penrose -b dc=example,dc=com '(member:1.2.840.113556.1.4.1941:=CN=Hermione Granger,OU=People,DC=vc,DC=example,DC=com)' cn dn: CN=Terminal Services Users,CN=Users,DC=vc,DC=example,DC=com cn: Terminal Services Users dn: CN=Sales,OU=People,DC=vc,DC=example,DC=com cn: Sales dn: CN=Marketing,OU=People,DC=vc,DC=example,DC=com cn: Marketing dn: CN=Penrose Groups,OU=People,DC=vc,DC=example,DC=com cn: Penrose Groups
And finally, ambiguous name resolution searches:
imac:/Users/chrisl/ 9$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D cn=penrose,dc=example,dc=com -w penrose -b dc=example,dc=com '(anr=Har)' cn dn: CN=Harry Potter,OU=People,DC=vc,DC=example,DC=com cn: Harry Potter imac:/Users/chrisl/ 10$ ldapsearch -LLL -x -h dev5.example.com -p 10389 -D cn=penrose,dc=example,dc=com -w penrose -b dc=example,dc=com '(anr=Lun)' cn dn: CN=Luna Lovegood,OU=People,DC=xx,DC=example,DC=com cn: Luna Lovegood
Here are the three relevant configuration files, with comments.
connections.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE connections PUBLIC "-//Penrose/DTD Connections 2.0//EN" "http://penrose.safehaus.org/dtd/connections.dtd"> <connections> <!-- Connection definition for vc.example.com forest --> <connection name="vc"> <adapter-name>LDAP</adapter-name> <!-- Using the Global Catalog server for the 'vc.example.com' domain --> <parameter> <param-name>java.naming.provider.url</param-name> <param-value>ldap://dc1.vc.example.com:3268/</param-value> </parameter> <!-- Service user for vc.example.com --> <parameter> <param-name>java.naming.security.principal</param-name> <param-value>cn=penrose service user,ou=service users,dc=vc,dc=example,dc=com</param-value> </parameter> <!-- Service user's password --> <parameter> <param-name>java.naming.security.credentials</param-name> <param-value>penrose</param-value> </parameter> <!-- Connection pool details --> <parameter> <param-name>minEvictableIdleTimeMillis</param-name> <param-value>300000</param-value> </parameter> <parameter> <param-name>timeBetweenEvictionRunsMillis</param-name> <param-value>60000</param-value> </parameter> <parameter> <param-name>minIdle</param-name> <param-value>0</param-value> </parameter> <!-- 'Grow' works best for the pool --> <parameter> <param-name>whenExhaustedAction</param-name> <param-value>grow</param-value> </parameter> <!-- Set the LDAP page size. Penrose doesn't support paging on queries, --> <!-- but it does support querying other directories using paging --> <parameter> <param-name>pageSize</param-name> <param-value>500</param-value> </parameter> </connection> <!-- Connection definition for xx.example.com forest --> <connection name="xx"> <adapter-name>LDAP</adapter-name> <parameter> <param-name>java.naming.provider.url</param-name> <param-value>ldap://dc4.xx.example.com:3268/</param-value> </parameter> <parameter> <param-name>java.naming.security.principal</param-name> <param-value>cn=penrose service user,ou=service users,dc=xx,dc=example,dc=com</param-value> </parameter> <parameter> <param-name>java.naming.security.credentials</param-name> <param-value>penrose</param-value> </parameter> <parameter> <param-name>minEvictableIdleTimeMillis</param-name> <param-value>300000</param-value> </parameter> <parameter> <param-name>timeBetweenEvictionRunsMillis</param-name> <param-value>60000</param-value> </parameter> <parameter> <param-name>minIdle</param-name> <param-value>0</param-value> </parameter> <parameter> <param-name>whenExhaustedAction</param-name> <param-value>grow</param-value> </parameter> <parameter> <param-name>pageSize</param-name> <param-value>500</param-value> </parameter> </connection> </connections>
sources.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sources PUBLIC "-//Penrose/DTD Sources 2.0//EN" "http://penrose.safehaus.org/dtd/sources.dtd"> <sources> <!-- Source definition for vc.example.com forest --> <source name="vc"> <connection-name>vc</connection-name> <!-- Use 'example.com' as the baseDn, this is the root of all the featured domains --> <parameter> <param-name>baseDn</param-name> <param-value>dc=example,dc=com</param-value> </parameter> </source> <!-- Source definition for xx.example.com forest --> <source name="xx"> <connection-name>xx</connection-name> <parameter> <param-name>baseDn</param-name> <param-value>dc=example,dc=com</param-value> </parameter> </source> </sources>
directory.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE directory PUBLIC "-//Penrose/DTD Directory 2.0//EN" "http://penrose.safehaus.org/dtd/directory.dtd"> <directory> <!-- Entry to be used for authenticating to Penrose to query the directory --> <entry dn="cn=penrose,dc=example,dc=com"> <oc>person</oc> <at name="cn" rdn="true"> <constant>penrose</constant> </at> <at name="userPassword"> <constant>penrose</constant> </at> </entry> <!-- Dummy root entry for the example.com domain that encloses the forest domains --> <entry dn="dc=example,dc=com"> <oc>dcObject</oc> <oc>organization</oc> <at name="dc" rdn="true"> <constant>example</constant> </at> <at name="o"> <constant>example</constant> </at> <aci> <permission>rs</permission> </aci> </entry> <!-- Proxy entry for the 'vc.example.com' forest root (and its child domains) --> <entry dn="dc=vc,dc=example,dc=com"> <entry-class>org.safehaus.penrose.directory.ProxyEntry</entry-class> <source> <source-name>vc</source-name> </source> <aci> <permission>rs</permission> </aci> </entry> <!-- Proxy entry for the 'xx.example.com' forest root --> <entry dn="dc=xx,dc=example,dc=com"> <entry-class>org.safehaus.penrose.directory.ProxyEntry</entry-class> <source> <source-name>xx</source-name> </source> <aci> <permission>rs</permission> </aci> </entry> </directory>
This is a great example, but driving me nuts as I can’t get it to work in my environment.
I have multiple forests in AD. The primary is dc=companya,dc=local The secondary is dc=companyb,dc=local
I tried to modify your example so I’d end up with ou=companya,dc=companies,dc=local and ou=companyb,dc=companies,dc=local – but I’m not getting anywhere. Is that possible with your config?
The config does merge two forests, but it might not do it 100% into the structure that you have there. I’m confident that Penrose *can* do what you want, but its configuration mechanism can be a bit obscure.