Copyright 2015-2017, Tremolo Security, Inc.
MyVD LDAP Virtual Directory
Welcome to the home of MyVD, a Java based LDAP Virtual Directory.
What is a virtual directory?
A virtual directory is a service (typically provied via LDAP) that allows for the integration of multiple data repositories in a transparent manner to an application. There are several use cases for a virtual directory including (but not limmited to):
-
Creating a view of a user with attributes stored in multiple repositories
-
Formatting user and group object for an application
-
Creating a larger directory from smaller ones
To learn how a virtual directory can help integrate your applications with your identity data view some virtual directory use cases.
Virtual Directory Use Cases
Virtual Directories have several use cases. In general a virtual directory is most easily used to help integrate an application into an existing environment. Each of the below usecases is examined in more detail with a description of how MyVD might solve the given problem.
-
Namespace Integration - Integrating multiple directories to form a larger single directory
-
Delegated Authentication - Delegating authentication to an enterprise directory while using an edge directory for identity data
-
Data Transformation - Transform existing directory data for use by an application
-
Edge Directory Integration - Integrate application specific directory data into an enterprise directory without making any changes to the enterprise directory.
-
Web Services Integration - Many organizations have web services for the creation and update of user information
Namespace Integration
A typical situation in many enterprise architectures is to maintain a seperate directory for internal and external users. Internal users being employees while the external directory may store contractors and supliers or even customers. In situations where all users need access to an applicaiton (such as a portal) the application may not support multiple directories. In this situation a virtual directory can be used to integrate the directories into a single namespace.
In the above example MyVD is used with two LDAP Inserts to create a single view of the two directories.
Delegated Authentication
In certain situations its desirable to have a local directory for user information while an external directory is used for authentication. For instance a local directory for user data and ActiveDirectory for authentication. In order to achieve this there are two options:
-
Synchronize Passwords from ActiveDirectory
-
Use a virtual directory to delegate authentication to the ActiveDirectory while delegating all other requests to the local directory
Its not allways acceptable to utilize a password synchronization system for both policy and technical reasons. In this case a virtual directory can be used to delegate the authentication. In the below diagram the virtual directory sits as a proxy between the application and the local directory delegating all bind requests to the corporate ActiveDirectory utilizing Kerberos. This is not the only way to delegate authenticaiton but is one of the simplest.
Data Transformation
While LDAP v3 is an IETF standard, not all applications abide by the standards. In addition to requiring certain attributes, some applications will not work if your directory does not conform to its requirements. Again as above either a virtual directory can be utilized or a new directory can be created based on a synchronization solution. In this example an application requires that users id attribute be called uid while your directory is a Microsoft ActiveDirectory (which stores the user’s id in the samAccountName attribute). In order to support this application MyVD can be configured to "rename" uid to samAccountName.
The above diagram shows the application performing a search with the filter "(uid=username)" that is transformed to "(samAccountName=username)". When the object is returned the attribute samAccountName is renamed to uid. This is a very basic but powerful example of how a virtual directory can perform data transformation.
Edge Directory Integration
Many organizations have a single enterprise directory that stores common user attributes and may be used for authentication. If an application requires additional attributes it’s generally very dificult to add these attributes to the enterprise directory. In this situation a virtual directory can be used to extend the enterprise directory by creating an edge directory that joins the enterprise directory to application specific attributes.
In the above example application attributes are stored in a relational database (as it may be easier to get an application specific database then application specific directory). The Joiner insert combines the database and the enterprise directory based the uid attribute.
Web Services Integration
In order to control the provisioning of users some organizations require users are updated via a common API or web service. A virtual directory can be used to call this web service when users are updated to integrate with the existing infrastructure. The below image depicts the integration of a web service through a custom insert to call the web sevice and a routing insert to redirect updates to the custom insert.
Introduction to MyVD
MyVD is a java based virtual directory that utilizes a series of layers and routing to create an identity infrastructure.
Components of MyVD
-
Server - The server is the top level component that controls all of the parts of the virtual directory.
-
Inserts - An insert is a module used to act on a request and optionally generate a response. All functionality is derived from inserts.
-
Chains - Chains are a sequential set of inserts that comprise a single data flow.
-
Namespace - An area of the directory based on a DN. For instance there is a global namespace for all namespaces and there may be additional namespaces such as "ou=people,dc=domain,dc=com". Namepaces are used to route requests and contain chains of inserts.
-
Router - The router combines the global namespace with local namespaces. The router is not directly configurable but can be manipulated via inserts.
How The Pieces Fit
The above pieces of MyVD fit together to create a "flow" data. To illustrate this lets show how the following virtual directory would work:
-
User data is stored in a relational database
-
Authentication is provided via ActiveDirectory
-
Write operations are performed using a custom web service
In order to fulfill the above requirements a virtual directory will need to be built with:
-
A routing insert to redirect all write operations
-
Two namespaces to represent user data
-
Master - ou=people,dc=domain,dc=com with a single insert
-
Custom insert to call the web service needed to update the database
-
-
DB - ou=people,dc=domain,dc=com with two inserts
-
Kerberos insert for authenticating against ActiveDirectory
-
Database insert to expose the database
-
-
We aren’t going to cover the specific configuration in this section, but we will show how these pieces fit together:
The above image shows all write operations (add, modify, delete rename) are directed towards the "Master" namespace while all read operations (search, modify) are sent to the DB namespace. This decision is made by the routing insert. Notice that the two namespaces have the same base (ou=people,dc=domain,dc=com). MyVD allows for namespaces to overlap. In this case there is no conflict because the routing plugin determines which namespace is utilized.
Once a namespace is determined, it’s chain is executed. The "Master" namespace has only a single insert which calls a custom web service to update a user’s profile. The "DB" namespace has two inserts on it’s chain. The first insert enables the use of Kerberos during the LDAP "bind" operation. Since this insert handles the bind operation, execution stops there. On searches and compares however the Kerberos insert "ignores" the request by passing it down through the chain to the database insert which is configured to work with the database storing user data.
Next Steps
The above example shows how a complex identity requirement could be implemented using MyVD. From here you can explore the various inserts that are currently provided or how to configure MyVD.
Configuring MyVD
MyVD is configured using a properties file. The properties file defines what port the server will listen on, which inserts will run on the global and local namespaces.
Basic Configuration Concepts
The MyVD configuration file is broken into three parts:
-
Listener
-
Global Namespace
-
Local namespaces
The listener portion defines what port the server will listen on and if there is a secure port. The global namespace defines what inserts will run for all requests and the local namespaces defines what inserts will run for individual namespaces.
Configuring the Listener
The listener can be configured to listen on a secure port, a non secure
port or both. To configure a non secure port simply supply a port
number. In addition to the port number, you can specify a maximum number
of entries or a maximum time limit (in milli-seconds). If not
configured, the default is unlimited.
The listener can be limited only on a particular host (default is 0.0.0.0
).
If authentication is required the simple authentication credential are
needed to bind the listener (default false
).
#Listen on port 389
server.listener.host=localhost
server.listener.port=389
server.listener.authRequired=true
server.listener.maxSizeLimit=1000
server.listener.maxTimeLimit=60000
To open a secure port you need to know the port, have a keystore to provide a key for encryption and the password for that keystore.
#Listen on 636 using SSL
server.secure.listener.port=636
server.secure.keystore=/var/keystores/myvd.ks
server.secure.keypass=secret
Configuring Namespaces
Namespaces are configured by first specifying a list of inserts and then configuring each interceptor. To illustrate how to configure MyVD we will configure the virtual directory described in the MyVD overview. This virtual directory uses a relational database to store users while utilizing ActiveDirectory for authentication and a custom webservice for updating user information. Bellow is a diagram describing the virtual directory.
Configuring the Global Namespace
The global namespace is setup by first listing the inserts that are used in the global chain and then each insert is configured.
#First specify the inserts in the global namespace's chain
server.globalChain=insert1,insert2,insert3
#Configure insert1
server.globalChain.insert1.className=com.package.class1
server.globalChain.insert1.config.option1=value1
server.globalChain.insert1.config.option2=value2
#Configure insert2
server.globalChain.insert2.className=com.package.class2
server.globalChain.insert2.config.option1=value1
server.globalChain.insert2.config.option2=value2
#Configure insert3
server.globalChain.insert3.className=com.package.class3
server.globalChain.insert3.config.option1=value1
server.globalChain.insert3.config.option2=value2
In the above diagram there a single "global" insert: the RoutingPlugin. This insert is used to instruct the router how to route certain requests and is described in the "Insert Reference" section. Based on the above diagram the global namespace configuration would be as follows.
#first configure the global chain
server.globalChain=router
#routing plugin to forward all writes to the master and reads to the database
server.globalChain.router.className=net.sourceforge.myvd.inserts.routing.MasterReplicaRouter
server.globalChain.router.config.specifyToInclude=false
server.globalChain.router.config.readOnly=DB
server.globalChain.router.config.master=Master
Configuring the Local Namespaces
MyVD is configured to have multiple "namespaces" which determine the flow of data through the system. Namespaces are separated by LDAP DNs. Like the global namespace, local namespaces contain chains of inserts. Unlike the global namespaces, local namespaces are separated by an LDAP DN and a weight to determine which namespace takes priority when there is a conflict.
To configure local namespaces:
#First, list all the namespaces
server.namespaces=ns1,ns2
#configure the ns1 namespace
server.ns1.chain=insert1,insert2
server.ns1.nameSpace=ou=People,dc=domain,dc=com
server.ns1.weight=100
server.ns1.enabled=true
#Configure insert1
server.ns1.insert1.className=com.package.class1
server.ns1.insert1.config.option1=value1
server.ns1.insert1.config.option2=value2
#Configure insert2
server.ns1.insert2.className=com.package.class2
server.ns1.insert2.config.option1=value1
server.ns1.insert2.config.option2=value2
#configure the ns2 namespace
server.ns2.chain=insert2
server.ns2.nameSpace=ou=Groups,dc=domain,dc=com
server.ns2.weight=100
server.ns2.enabled=true
#Configure insert2
server.ns1.insert2.className=com.package.class2
server.ns1.insert2.config.option1=value1
server.ns1.insert2.config.option2=value2
In the above virtual directory there are two namespaces : Master & DB. The Master namespace has a custom built insert for updating identity data via a webservice. The DB namespace is configured with the kerberos insert and a database insert.
server.namespaces=Master,DB
server.Master.chain=webservice
server.Master.nameSpace=ou=People,dc=domain,dc=com
server.Master.weight=100
server.Master.enabled=true
server.Master.webservice.className=com.mycompany.webservice.Insert
server.Master.webservice.config.url=http://someserver.somehost.com/myservice
server.DB.chain=kerberos,db
server.DB.nameSpace=ou=People,dc=domain,dc=com
server.DB.weight=100
server.DB.kerberos.className=net.sourceforge.myvd.inserts.kerberos.KerberosInterceptor
server.DB.db.className=net.sourceforge.myvd.inserts.jdbc.JdbcInsert
server.DB.db.config.driver=com.db.driver.Driver
server.DB.db.config.url=jdbc:db://server/db
server.DB.db.config.user=user
server.DB.db.config.pass=secret
server.DB.db.config.rdn=uid
server.DB.db.config.mapping=uid=id,cn=name
server.DB.db.config.objectClass=inetOrgPerson
server.DB.db.config.sql=SELECT id,name FROM users
The first line defines the which namespaces will exist. Then each namespace and it’s chain are defined. There are two things to note about this configuration:
-
The custom web service insert is configured in the same fashion as the pre-built inserts. This is because all functionality is derived from inserts.
-
Both namespaces have the same weight, but there are no conflicts. This is because the routing insert in the global plugin explictly sets the namespace to be used.
#List all the namespaces
server.namespaces=ns1,ns2
#Configure the ns1 namespace
server.ns1.chain=insert1
server.ns1.nameSpace=ou=People,dc=mycomp1,dc=com
server.ns1.weight=100
server.ns1.enabled=true
#Configure insert1
server.ns1.insert1.className=com.package.class1
server.ns1.insert1.config.option1=value1
server.ns1.insert1.config.option2=value2
#Configure the ns2 namespace
server.ns2.chain=insert2
server.ns2.nameSpace=ou=Users,dc=mycomp2,dc=org
server.ns2.weight=100
server.ns2.enabled=true
#Configure insert2
server.ns1.insert2.className=com.package.class2
server.ns1.insert2.config.option1=value1
server.ns1.insert2.config.option2=value2
Complete Config
Below is the complete server’s configuration.
#Listen on port 389
server.listener.port=389
#Listen on 636 using SSL
server.secure.listener.port=636
server.secure.keystore=/var/keystores/myvd.ks
server.secure.keypass=secret
#first configure the global chain
server.globalChain=router
#routing plugin to forward all writes to the master and reads to the database
server.globalChain.router.className=net.sourceforge.myvd.inserts.routing.MasterReplicaRouter
server.globalChain.router.config.specifyToInclude=false
server.globalChain.router.config.readOnly=DB
server.globalChain.router.config.master=Master
server.namespaces=Master,DB
server.Master.chain=webservice
server.Master.nameSpace=ou=People,dc=domain,dc=com
server.Master.weight=100
server.Master.enabled=true
server.Master.webservice.className=com.mycompany.webservice.Insert
server.Master.webservice.config.url=http://someserver.somehost.com/myservice
server.DB.chain=kerberos,db
server.DB.nameSpace=ou=People,dc=domain,dc=com
server.DB.weight=100
server.DB.enabled=true
server.DB.kerberos.className=net.sourceforge.myvd.inserts.kerberos.KerberosInterceptor
server.DB.db.className=net.sourceforge.myvd.inserts.jdbc.JdbcInsert
server.DB.db.config.driver=com.db.driver.Driver
server.DB.db.config.url=jdbc:db://server/db
server.DB.db.config.user=user
server.DB.db.config.pass=secret
server.DB.db.config.rdn=uid
server.DB.db.config.mapping=uid=id,cn=name
server.DB.db.config.objectClass=inetOrgPerson
server.DB.db.config.sql=SELECT id,name FROM users
Next Steps
At this point you have been shown how to configure MyVD. From here you can look at the available inserts or how to build your own.
Services and Access Management Inserts
These inserts are primarily concerned with the running of MyVD including access management, server management and server tracking.
The RootObject insert creates a single object meant to be a placeholder. It’s most useful when combining two data sources but want to create a full tree for browsing.
Class Name |
net.sourceforge.myvd.inserts.RootObject |
|
Scope |
Search |
Access Controls
The access controls for MyVD are based on a draft RFC. The RFC can be read [[www.cnn.com] here]. The access control system utilizes a list of access control items. Each item is structured as:
Configuring The Access Control Insert
Class Name |
net.sourceforge.myvd.inserts.ldap.LDAPInterceptor |
|
Scope |
Search,Compare,Bind,Add,Modify,Delete,Rename |
|
Configuration Options |
numACIs |
The number of ACIs to be processed |
aci.X |
An ACI to process based on the below configuration. "X" is the number of the ACI and should begin with 0. |
Specifying an ACI
scope-base#scope#grant/deny:permisions#scope#users
The below table explains each part of the ACI:
Component |
Description |
Example |
scope-domain |
The base of the scope this ACI applies to. This ACI will only be considered for operations that are subordinates of this DN |
ou=people,dc=domain,dc=com |
scope |
The scope of this ACI. The scope can either be "subtree" to apply to all subordinates of the scope-base or "entry" to apply only to the scope-domain. |
subtree OR entry |
grant/deny |
Determines if the outcome of this ACI should result in a grant or deny |
grant OR deny |
permisions |
Which permisions this ACI grants or denys. See the below table for a list of permisions |
|
scope |
Whether this applies to entry results or attributes. The below table explains the different options |
[entry] OR [all] OR a comma sepperated list of attribute names |
users |
Which users this ACI applies to. The below table lists the possible values for this option |
See the below table |
Entry Permissions
Permission |
Letter |
Description |
Create |
c |
Determines if entries may be created |
Delete |
d |
Determines if the entry may be deleted |
View |
v |
Determines if the entry may be viewed |
Rename |
n |
Determines if the entry can be renamed |
Attribute Permissions
Permission |
Letter |
Description |
Read |
r |
Determines if the attribute can be read |
Write |
w |
Determines if the attribute can be written to |
Obliterate |
o |
Determines if the attribute can be removed from the entry |
Search |
s |
Determines if the attribute can be included in a search filter |
Compare |
c |
Determines if the attribute can be included in a compare operation |
Presence Search |
p |
Determines if a filter on the presence of the attribute in the entry |
Users Options
Users Option |
Description |
Syntax |
Example |
Sub Tree |
This ACI applies to all users beneath the given DN |
subtree:DN |
subtree:ou=users,dc=domain,dc=com |
DN |
This ACI applies to the specified DN only |
dn:DN |
dn:cn=admin,ou=admins,dc=domain,dc=com |
This |
The ACI applies to the currently bound user. This is useful for permisions such as letting a user rest their own password |
this: |
|
Group |
An LDAP static group is used to apply this ACI |
group:DN_OF_GROUP |
group:cn=admins,ou=groups,dc=domain,dc=com |
Dynamic Group |
An LDAP dynamic group is used to apply this ACI |
dynamic-group:DN_OF_DYNAMIC_GROUP |
dynamic-group:cn=admins,ou=groups,dc=domain,dc=com |
Public |
This ACI applies to all users, bound an anonymous |
Access Log
The access log provides information about who is accessing MyVirtualDirectory. The access log provides the ability to roll when the log reaches a certain size or at a certain time.
Class Name |
net.sourceforge.myvd.inserts.AccessLog |
|
Scope |
All |
|
Configuration Options |
||
fileName |
The path and name of the file, ie /var/log/myvd.log |
|
type |
rolling or periodic, based on if the log files should roll over based on the size of the log file or the time of the day. If periodic the log will roll at midnight every nights |
|
maxFileSile |
When rolling, the size when to roll. Default is 100MB |
|
backupIndex |
The maximum number of files to keep |
DumpTransaction
This insert dumps the details of a request to the log. DumpTransaction is very useful when debugging a MyVD deployment. By placing this insert at various points in the server it’s possible to see how requests and responses are effected. Note that this insert can have an adverse effect on performance.
Class Name |
net.sourceforge.myvd.inserts.DumpTransaction |
|
Scope |
All |
|
Configuration Options |
logLevel |
One of info,debug,error,warn |
label |
A label to describe this DumpTransaction insert in the logs |
RootDSE Insert
This insert should be placed in a namespace with an empty "nameSpace" property in order to construct a root dse object.
Class Name |
net.sourceforge.myvd.inserts.RootDSE |
|
Scope |
Search |
|
Configuration Options |
supportedFeatures |
Comma separated list |
namingContexts |
Pipe " u007C" separated list of DNs |
|
supportedControls |
Comma separated list |
|
supportedSaslMechanisms |
Comma separated list |
|
supportedExtensions |
Comma separated list |
Password Change Operation
This insert is an example of how an extended operation could be implemented in MyVD. The insert reconstructs the operation in order to make sure that the request is routed properly.
Class Name |
net.sourceforge.myvd.inserts.extensions.PasswordChangeOperation |
|
Scope |
Extended Operation |
|
Configuration Options |
remoteBase |
The final base of the operation |
localBase |
The base to expect in the operation |
Master Replica Router
The Master Replica Router is an insert that should be added to the global chain when you wish to seperate write operations from search operations, such as when utilizing MyVD in a master/replica environment.
Class Name |
net.sourceforge.myvd.inserts.routing.MasterReplicaRouter |
|
Scope |
Add, Modify, Rename, Delete, Compare, Search, Bind, Extended Operations |
|
Configuration Options |
specifyToInclude |
true or false, if the servers included by the readOnly option specifies the namespaces to be read only or those NOT to be considered read only. |
readOnly |
The names of namespaces configured that will be used for search operations |
|
master |
The nams of a namespace used to write operations |
Schema Insert
This insert allows for a schema ldif file to be used as a schema entry
Class Name |
net.sourceforge.myvd.inserts.SchemaInsert |
|
Scope |
Search |
|
Configuration Options |
schemaLDIF |
Path to the ldif file. NOTE: this ldap file MUST contain a full schema entry, including DN and objectClasses. The DN should match the root of this insert. The first line of the ldif file MUST be "version: 1" |
Route by Attribute Value
The Route by Attribute Value Router is an insert that should be added to the global chain when requests must be routed to a particular namespace based on an attribute value. An example would be when using MyVD to route requests to different directories based on an email suffix. Additionaly, a default route can be used when no other routes are available. This router is only executed on searches who’s filters are equality ("="). This filter is not applied to substring filters.
Class Name |
net.sourceforge.myvd.inserts.routing.RouteByAttributeValue |
|
Scope |
Search |
|
Configuration Options |
attributeName |
The name of the attribute to check |
useDefault |
true or false, If true then if no route is found a default one is used. If false then if not route is found then MyVD will no route the request to a specific namespace |
|
defaultNameSpace |
The namespace from MyVD’s configuration (the name of the namespace, not the DN base) to route to if no routing rules match |
|
ignorePattern |
A pattern that identifies requests that should be ignored by this router |
|
ignoreNegative |
If set to true, then this insert will ignore anything that DOESN’T match the ignorePattern configuration option |
|
dontRouteBelow |
If set, all routeing below the given dn is ignored |
|
numRoutes |
The number of routing rules being configured |
|
route.X |
Where X is a number between 0 and (numRoutes - 1) inclusively. The value is namepsace=regex. The namespace is the name of the namespace in the MyVD configuration. The regex is a Java regular expression that will be tested against the values for the named attribute in the search’s LDAP filter. Here is the Java pattern language. |
Required For Search
This insert should be installed on a namespace that should be allowed to fail when performing searches without causing an error to be returned to the client. When an error does occur, it is logged as a warning.
Class Name |
net.sourceforge.myvd.inserts.routing.RequiredForSearch |
|
Scope |
Search |
|
Configuration Options |
required |
true or false, If false, any errors that occur during a search are logged as a warning. If true, this insert is not run. |
Mapping Inserts
These inserts are general mapping inserts Certain mapping inserts are specialized for databases or directories. These specialized inserts are documented with their respective categories.
Attribute Cleaner
This insert ensures that the attributes requested by a search request are the only attributes returned to the user. This insert is usefull when additional attributes are added to an entry for meta data purposes but should not be returned to the user.
Class Name |
net.sourceforge.myvd.inserts.mapping.AttributeCleaner |
|
Scope |
Search |
|
Configuration Options |
clearAttributes |
If set to true after storing the names of the requested attributes all attributes are requested |
Add Static Attribute
This insert allows for the addition of a static attribute value for a particular objectClass. It is useful for adding attributes to identify users based on their directory or namespace.
Class Name |
net.sourceforge.myvd.inserts.mapping.AddAttribute |
|
Scope |
Search |
|
Configuration Options |
attributeName |
The name of the attribute to create |
attributeValue |
The value of the attribute to create |
|
objectClass |
The objectClass to create this attribute on |
Attribute Mapper
The attribute mapper insert is used to map the names of attributes. For instance it could map uid to samaccountname. This insert works on attribute names in both entries, dns and filters.
Class Name |
net.sourceforge.myvd.inserts.mapping.AttributeMapper |
|
Scope |
Search,Add,Modify,Delete,Compare,Rename |
|
Configuration Options |
mapping |
Comma seperated list of attribute maps. For instance "uid=samaccountname" |
Attribute Value Mapper
The attribute value mapper is used to map one entry value to another. For instance mapping "objectClass: inetOrgPerson" → "objectClass: user".
Class Name |
net.sourceforge.myvd.inserts.mapping.AttributeValueMapper |
|
Scope |
Search,Add,Modify,Compare |
|
Configuration Options |
mapping |
Comma sepperated list of mappings in the form of "attribute.curvalue=newvalue". For instance "objectClass.inetOrgPerson=user |
Composite Attribute Insert
The composite attribute insert is designed to create a singe attribute from multiple attributes. Uses include creating a CN attribute from the givenName and sn as well as creating a uid from the same two attributes.
Class Name |
net.sourceforge.myvd.inserts.jdbc.CompositeAttrib |
|
Scope |
Search |
|
Configuration Options |
attribute |
The name of the attribute that will be created (ie uid or cn) |
components |
Comma seperated list of component attributes with a colon to determine how much of the attribute. The length of the compoenent can be a number, fs for the first space, ls for the last space and * for the entire attribute. ie "givenName:1,sn:*" |
|
properCase |
true or false. This configurate determines if the first letter of a component should be upper case on inbound transactions and lowercase in outbound transactions. |
|
objectClass |
The objectClass that this insert will apply to. |
DN Attribute Mapper
The DN attribute map the bases of DN attributes (such as uniqueMember or manager)
Class Name |
net.sourceforge.myvd.inserts.mapping.DNAttributeMapper |
|
Scope |
Add,Modify,Search |
|
Configuration Options |
dnAttribs |
The name of all attributes that are DNs |
urlAttribs |
The name of all the attributes that are LDAP URLs |
|
remoteBase |
The remote base to be mapped from |
|
localBase |
The local base to be mapped to |
SetRDN Insert
This insert allows for the RDN of an entry to be changed. For instance if the current RDN is the "CN" attribute this insert can change it to the "uid" attribute.
Class Name |
net.sourceforge.myvd.inserts.setrdn.SetRDN |
|
Scope |
Search,Add,Modify,Delete,Rename,Bind |
|
Configuration Options |
internalRDN |
The current RDN attribute name. |
externalRDN |
The RDN attribute that will be presented |
|
objectClass |
The object class to act on, defaults to inetOrgPerson |
|
dnattributes |
List of DN attributes to also map |
Dn2Attribute
This insert will map an attribute storing a DN to an attribute value on the referenced object. For instance this insert could map the uniqueMember DN to the uid of the user object pointed to be the DN. This would be useful when mapping from a groupOfUniqueNames to a posixGroups object.
Class Name |
net.sourceforge.myvd.inserts.mapping.Dn2Attribute |
|
Scope |
Search |
|
Configuration Options |
newValueAttribute |
The attribute that will contain the attribute values |
sourceAttribute |
The attribute to pull the DNs from |
FormatDate
This insert will format directory value stored as the number of milliseconds since epoch.
Class Name |
com.tremolosecurity.proxy.myvd.inserts.format.FormatDate |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
attributeName |
The name of the attribute to create |
format |
Format to use (see below) |
|
sourceAttributeName |
The name of the attribute to get the date from |
Symbol |
Meaning |
Presentation |
Examples |
G |
era |
text |
AD |
C |
century of era (>=0) |
number |
20 |
Y |
year of era (>=0) |
year |
1996 |
x |
weekyear |
year |
1996 |
w |
week of weekyear |
number |
27 |
e |
day of week |
number |
2 |
E |
day of week |
text |
Tuesday; Tue |
y |
year |
year |
1996 |
D |
day of year |
number |
189 |
M |
month of year |
month |
July; Jul; 07 |
d |
day of month |
number |
10 |
a |
halfday of day |
text |
PM |
K |
hour of halfday (0~11) |
number |
0 |
h |
clockhour of halfday (1~12) |
number |
12 |
H |
hour of day (0~23) |
number |
0 |
k |
clockhour of day (1~24) |
number |
24 |
m |
minute of hour |
number |
30 |
s |
second of minute |
number |
55 |
S |
fraction of second |
millis |
978 |
z |
time zone |
text |
Pacific Standard Time; PST |
Z |
time zone offset/id |
zone |
-0800; -08:00; America/Los_Angeles |
' |
escape for text |
delimiter |
|
'' |
single quote |
literal |
' |
Attribute2DN
The Attribute2DN insert is useful for translating an attribute value to a full DN by doing a search. This is useful when using MongoDB for storing groups where a user identifier is stored in a document instead of a full DN. This insert will then translate that attribute into a full DN by searching for the user named in the attribute.
Class Name |
com.tremolosecurity.myvd.inserts.mapping.Attribute2DN |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
attributeName |
The name of the attribute in the document to map to a DN |
searchBase |
The DN of the base to start the search for the user account |
|
searchAttribute |
The name of the attribute to search for |
VirtualMemberOf
This insert creates a virtual attribute for storing the DNs of objects that reference this object, such as the common memberof attribute. This attribute is read-only and can be searched on in a filter.
Class Name |
net.sourceforge.myvd.inserts.mapping.VirtualMemberOf |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
searchBase |
Where to start search for referencing objects |
applyToObjectClass |
The name of the objectClass to add the virtual attribute to |
|
attributeName |
The name of the virtual attribute |
|
searchObjectClass |
The objectClass of referencing objects |
|
searchAttribute |
The referencing attribute |
|
replace |
If true (default) and existing attribute is replaced |
DeleteAttribute
This insert will delete an attribute from an entry.
Class Name |
net.sourceforge.myvd.inserts.mapping.DeleteAttribute |
|
Scope |
Add, Post Search Entry |
|
Configuration Options |
attributes |
Comma "," sepperated list of attributes to remove |
Joining Inserts
The joining inserts can be combined to join entries across several directories and databases. In addition to the joiner insert there are several inserts that assist with the utilization of joining.
Joiner
The joiner is used to combine two LDAP namespaces (note that this means two DN namespace, not two configuration namespaces). The joiner combines entries from each namespace.
Class Name |
net.sourceforge.myvd.inserts.join.Joiner |
|
Scope |
Search |
|
Configuration Options |
primaryNamespace |
LDAP DN that represents a configured namespace that will drive the namespace of the joiner |
joinedNamespace |
LDAP DN that represents a configured namespace that will be joined with the primaryNamespace. The namespace of the primaryNamespace drives the joiner’s namespace |
|
joinedAttributes |
Attributes to be included in the join |
|
joinFilter |
A filter that is used to combine objects. When including an attribute from the joining entry prepend the attribute with "ATTR.". For instance, to combine a user based on the uid attribute the filter would be "(uid=ATTR.uid)" |
|
bindPrimaryFirst |
If set to true, MyVD will first try to bind the primary insert. If set to false it will try the joined insert |
Joiner - Simple Join Modify
The simple join modify insert is configured AFTER a joiner on a chain in order to support the modification of joined attributes
Class Name |
net.sourceforge.myvd.inserts.join.SimpleJoinModify |
|
Scope |
Modify |
|
Configuration Options |
joinerName |
The name of the joiner insert configured on this chain |
Joiner - Join Add Flat NameSpace
The join add flat namespace insert is designed to allow the addition of entries to a joiner namespace. NOTE: this insert MUST be configured as the last insert on a chain.
Class Name |
net.sourceforge.myvd.inserts.join.JonAddFlatNS |
|
Scope |
Add |
|
Configuration Options |
joinerName |
The name of the joiner insert configured on this chain |
joinedObjectClass |
The objectclass for joined entries |
|
sharedAttributes |
Comma seperated list of attributes shared by both the primary and joined namespaces |
Directory Inserts
Directory inserts relate to the connection to directory and directory-like services and mapping the data from those services.
LDAP Insert
The LDAP insert is one of the core inserts. This insert allows MyVD to communicate with any LDAPv3 compliant directory. In addtion to LDAP directories, this insert supports DSMLv2 and SPML. In order to take advantage of the SPML feautures you must download the OpenSPML toolkit (http://www.openspml.org/) and it’s dependencies.
Class Name |
net.sourceforge.myvd.inserts.ldap.LDAPInterceptor |
|
Scope |
Add, Modify, Rename, Delete, Compare, Search, Bind, Extended Operations |
|
Configuration Options |
host |
The host of the remote server. If this insert is using DSMLV2 or SPML then this is the URL of the end point. |
port |
The port the remote ldap server is listening on. Ignored for DSMLV2 and SPML |
|
remoteBase |
The base of the remote LDAP server that the insert would start at |
|
minimumConnections |
The minimum number of connections to keep in the pool |
|
maximumConnections |
The maximum number of connections to keep in the pool |
|
proxyDN |
The DN of the user to connect as |
|
proxyPass |
The password of the remote directory |
|
type |
One of "ldap","ldaps","dsmlv2" or "spml" |
|
spmlImpl |
When using SPML, the class name for the com.novell.ldap.spml.SPMLImpl interface |
|
passBindOnly |
"true" or "false" if the user’s credentials should be utilized for only the "bind" process. "false" if the credentials should be used on all operations. |
|
ignoreRefs |
"true" if referrals should be ignored. |
|
maxMillis |
The maximum number of millis seconds an operation can take. Defaults to 30,000 (30 seconds) |
|
maxStaleTimeMillis |
The maximum number of milliseconds a connection can be locked before its considered stale. Default is 60,000 (60 seconds) |
|
heartbeatIntervalMillis |
The interval in milliseconds to perform a heart beet for pool connections. 0 means do not run a heartbeat |
Kerberos Authenticator
The kerberos authenticator can be used to authenticate users via the Kerberos protocol, such as against Active Directory. Note that all configuration is based on the Java JNDI kerberos authentication file. (need to add a link)
Class Name |
net.sourceforge.myvd.inserts.kerberos.KerberosInterceptor |
|
Scope |
Bind |
|
Configuration Options |
None |
NTLM Authenticator
The kerberos authenticator can be used to authenticate users via the NTLM protocol, such as against Active Directory or an NT4 Domain Controller.
Class Name |
net.sourceforge.myvd.inserts.jcifs.NTLMAuthenticator |
|
Scope |
Bind |
|
Configuration Options |
host |
The host of the NTLM server (AD or an NTLM file share) |
Dynamic Groups Insert
The Dynamic Groups Insert allows for a Dyanmic Group (groupOfUrls) to act like a static group in the following ways:
-
All dynamic members can be listed as static members
-
You can test if a user is a member of the group by testing if the user is a static member
This all allows for applications that are not capable of using dynamic groups while still providing the maintenance benefits of static groups.
Class Name |
net.sourceforge.myvd.inserts.ldap.Dynamicgroups |
|
Scope |
Search |
|
Configuration Options |
dynamicObjectClass |
The object class for dynamic groups (typically groupOfUrls) |
staticObjectClass |
The object class for static groups (typically groupOfUniqueNames) |
|
urlAttribute |
The attribute that stores the LDAP url (typically memberUrl) |
|
staticAttribute |
The attribute that stores static members (typically uniqueMember) |
Embedded Groups
The Embedded Groups insert allows for static groups to contain members that are other groups to be tested with a user’s DN. For instance:
dn: cn=Child Group,ou=groups,dc=domain,dc=com
objectClass: groupOfUniqueNames
cn: Child Group
uniqueMember: cn=Test User,ou=people,dc=domain,dc=com
.
.
.
dn: cn=Parent Group,ou=groups,dc=domain,dc=com
objectClass: groupOfUniqueNames
cn: Parent Group
uniqueMember: cn=Child Group,ou=groups,dc=domain,dc=com
.
.
.
The above example would typically require two searches in order to test cn=Test User,ou=people,dc=domain,dc=com:
-
Retrieve the group
-
Determine which members are groups, and search each group for the user cn=Test User,ou=people,dc=domain,dc=com
The Embedded Groups does this work for you, tracking which members are groups to determine what groups need to be searched. The insert can track which members are groups either by polling the remote directory periodically or (if the directory supports it) by using persistent search to constantly track the addition or removal of groups.
Class Name |
net.sourceforge.myvd.inserts.ldap.EmbeddedGroups |
|
Scope |
Search |
|
Configuration Options |
groupSearchBase |
The search base in the remote directory to look for groups |
staticObjectClass |
The object class for static groups (typically groupOfUniqueNames) |
|
staticAttribute |
The attribute that stores static members (typically uniqueMember) |
|
userDN |
The DN used to connect to the remote directory |
|
userPwd |
The password used to connect to the remote directory |
|
useSync |
true or false / True if MyVD should use persistent search to determine group DNs, false if the directory should be polled |
Static Bind DN Map
The Static Bind DN Map insert can perform a static mapping from an external DN to a DN for a remote directory. Typically this insert would be used to map from DNs that are specific for an insert’s base to a non-specific base. Such as when using Sun/Fedora Directory the root user is typically cn=Directory Manager which has no base, so this insert could map uid=admin,dc=domain,dc=com→cn=Directory Manager.
Class Name |
net.sourceforge.myvd.inserts.ldap.StaticDNMap |
|
Scope |
Add,Modify,Delete,Bind,Search,Extended Operation |
|
Configuration Options |
dnmap |
Pipe u007C separated list of carrot separated DNs, external to internal (ie uid=admin,dc=domain,dc=comcn=Directory Manager) |
Database Inserts
These inserts are meant for integrating MyVD with relational databases. The primary insert for integrating with databases is the JDBC Insert with additional inserts being used for mapping and database updates.
JDBC Insert
The JDBC insert is used to expose a relational database through MyVD. This insert only supports a single objectClass and rdn for each configuration of the insert.
Class Name |
net.sourceforge.myvd.inserts.jdbc.JdbcInsert |
|
Scope |
Search |
|
Configuration Options |
driver |
The JDBC driver to use |
url |
The JDBC connection URL |
|
user |
The database user to connect as |
|
password |
The password to connect with |
|
rdn |
The LDAP atribute used as the rdn for all objects generated |
|
mapping |
Comma sepperated list of ldap=db mapping of attributes |
|
objectClass |
The objectClass for all octs created by this insert |
|
sql |
The SQL use to determine what objects this insert will generate. This SQL must include all attributes included in the mapping configuration |
|
maxCons |
The maximum number of connections, default is 5 |
|
useSimple |
true or false, utilizes the "simple" SQL, mutch better performance but only the value of multivalued attributes in the filter are returned |
|
validationQuery |
(optional) A SQL query that will be used to validate connections. If specified, this query MUST be an SQL SELECT statement that returns at least one row. |
|
addBaseToFilter |
(optional) |
|
idleConnectionTestPeriod |
(optional - default is 30) Number of seconds unchecked out connections are tested |
|
unreturnedConnectionTimeout |
(optional - defaults to 0) Number of seconds a connection can checked out before the connection is closed and returned to the pool. 0 means it will not be forced. |
|
checkoutTimeout |
(optional - defaults to 30000 milliseconds) Number of milliseconds to wait for a connection to be available. |
Database Table Update
This insert contains the logic to update a single database table. It is configured BEHIND a database insert in order to utilize that insert’s connection pool and mapping.
Class Name |
net.sourceforge.myvd.inserts.jdbc.DBTableUpdate |
|
Scope |
Add,Modify,Delete,Rename |
|
Configuration Options |
tableName |
The name of the table to update in the database |
dbInsertName |
The name of the insert used by this insert |
DB Groups
This insert is used when groups stored in a relational database are stored with a name and not a full DN. This plugin will create the DN to make it LDAP compliant.
Class Name |
net.sourceforge.myvd.inserts.jdbc.DBGroups |
|
Scope |
Search |
|
Configuration Options |
memberAttribute |
The name of the attribute that represents members (ie uniqueMember) |
suffix |
The member suffix (ie ou=groups,dc=domain,dc=com |
|
rdn |
The name of the rdn attribute (ie uid) |
Composite Attribute Insert
Often with relational databases a full, or "common", name is not stored as a single field but is rather stored as a first, or given, name and a last, or sur, name. Since LDAP typically expects there to be a composite attribute that has both pieces in a single attribute. This insert can also be used to create user ids based on a person’s name.
Class Name |
net.sourceforge.myvd.inserts.jdbc.CompositeAttrib |
|
Scope |
Search |
|
Configuration Options |
attribute |
The name of the final composite attribute |
components |
Comma sperated list with the following format : "attribute:[# |
|
properCase |
true/false, true if the composite attribute should be forced to lowercase |
|
objectClass |
The objectClass for objects that the mapping should be performed on |
Simple DB Authentication
By default the JdbcInsert does not support authentication. This insert, configured BEFORE the JdbcInsert allows a a simple authentication using an un-salted one-way hash.
Class Name |
net.sourceforge.myvd.inserts.jdbc.SimpleDBAuth |
|
Scope |
Bind |
|
Configuration Options |
sql |
A SQL SELECT statement with two paramters. The first parameter maps the bind DN’s RDN and the second is the hashed password |
hashFunction |
Eith "SHA" or "MD5" without quotes |
PBKDF2 DB Authentication
By default the JdbcInsert does not support authentication. This insert,
configured BEFORE the JdbcInsert supports LDAP binds against a password stored using the PBKDF2 format created by net.sourceforge.myvd.util.PBKDF2.generateHash
.
Class Name |
net.sourceforge.myvd.inserts.jdbc.Pbkdf2Auth |
|
Scope |
Bind |
|
Configuration Options |
sql |
A SQL SELECT statement with one parameter, the user’s rdn, and returns a the password in the only column to be returned. Example |
Web Services Inserts
These inserts assist with integration with web services. Currently the only web services supported by MyVD are DSMLv2 and SPML via the LDAP Insert. The below inserts assist with integrating with those services.
Create SPML Identity
The SPML identity insert is designed to create an LDAP compliant DN for use with the LDAP insert in SPML mode. SPML does not require a full DN as a name (it can for instance use a userid or an email address), but LDAP does. This insert ensures that the DN for the user object has the correct RDN for SPML while being LDAP compliant.
Class Name |
net.sourceforge.myvd.inserts.idm.CreateSPMLIdentity |
|
Scope |
Add, Modify, Delete |
|
Configuration Options |
type |
The SPML identity type (ie GenericString |
attribute |
The attribute that will act as the source for the RDN data |
|
keepNameAsAttribute |
true or false if the rdn’s attribute will remain on the user object |
Flatten Namespace
This insert removes components from an LDAP DN and can optionally store them in the user entry.
Class Name |
net.sourceforge.myvd.inserts.idm.FlattenNamespace |
|
Scope |
Add, Delete |
|
Configuration Options |
attribsToRemove |
Comma sepperated list of attributes to remove from the DN |
attributesToStore |
Comma sepperated list of attributes in the DN to store in the user entry |
Active Directory Inserts
These inserts help integrate MyVD with Active Directroy. They provide services to help integrate applications with Active Directory to best utilize Active Directory and simplify integration utilizing MyVirtualDirectory.
ObjectGUID & ObjectSID To String
Nearly every object in Active Directory contains both an Object GUID and an Object SID. The GUID is a unique identifier in Active Directory, with the SID being a security identifier. These are both binary attributes that are easiest to represent as strings. This insert takes the binary version of the these attributes and re0works them to present the string based representation. It also takes the string based representation in filters and translates it to a binary version.
Class Name |
net.sourceforge.myvd.inserts.ad.ObjectGuidToString |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
Primary Group ID
Every user has a primary group. This group is noted in the Active Directory attribute primaryGroupID, which references the last component of the of the primary group’s objectsid attribute. Users are not listed as members of their primary group. This insert, when combined with the "ObjectGUID & ObjectSID to String" insert beneath it and the DynamicGroups insert above it will list all members of a group, including those who note the group as its primary group.
Class Name |
net.sourceforge.myvd.inserts.ad.PrimaryGroup |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
searchBase |
The base to search for users, included in the url appended to all group entries |
groupObjectClass |
The object class for groups, typically "group" |
Generate Posix GID
This insert helps to integrate ActiveDirectory with posix systems. It is configured on the same chain as a joiner and provides the following functions:
-
Generate a users gidnumber based on the primaryGroupID
-
If a user or group exists in Active Directory but not in the database used to store posix attributes it creates the entry
Class Name |
net.sourceforge.myvd.inserts.ad.GeneratePosixGID |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
userIdAttribute |
The user id number attribute, by default uidNumber |
groupIdAttribute |
The group id number attribute, by default gidNumber |
|
userObjectClass |
The object class that identifies users |
|
groupObjectClass |
The object class that identifies groups |
|
userAddBase |
The base for joined users |
|
groupAddBase |
The base for joined adds |
|
homeDirTemplate |
The template for home directories. Enclose attributes in @@. For instance to have the directory be /home/USERS_SAMACCOUNTNAME it would be /home/@samaccountname@ |
Active Directory Insert
The active directory insert takes a Microsoft Active Directory and makes it appear to be a standard inetOrgPerson type directory. This insert utilizes the LDAP insert in order to communicate with Active Directory and includes additional mapping functions:
-
DN Mapping
-
ObjectGUID and ObjectSID mapping
-
Embedded Groups
-
Primary Group Listings
Class Name |
net.sourceforge.myvd.inserts.ad.ADInsert |
|
Scope |
Add, Modify, Delete, Rename, Search, Post Search Entry |
|
Configuration Options |
ADBase |
The domain’s base |
searchDn |
The user used to search AD. This user should not have an search limit restrictions |
|
searchPwd |
The password for the search user |
|
remoteBase |
The remote base for dn mapping |
|
userAddBase |
The base for joined users |
|
groupAddBase |
The base for joined adds |
|
homeDirTemplate |
The template for home directories. Enclose attributes in @@. For instance to have the directory be /home/USERS_SAMACCOUNTNAME it would be /home/@samaccountname@ |
|
host |
The host of the remote server. If this insert is using DSMLV2 or SPML then this is the URL of the end point. |
|
port |
The port the remote ldap server is listening on. Ignored for DSMLV2 and SPML |
|
remoteBase |
The base of the remote LDAP server that the insert would start at |
|
minimumConnections |
The minimum number of connections to keep in the pool |
|
maximumConnections |
The maximum number of connections to keep in the pool |
|
proxyDN |
The DN of the user to connect as |
|
proxyPass |
The password of the remote directory |
|
type |
One of "ldap","ldaps","dsmlv2" or "spml" |
|
spmlImpl |
When using SPML, the class name for the com.novell.ldap.spml.SPMLImpl interface |
|
passBindOnly |
"true" or "false" if the user’s credentials should be utilized for only the "bind" process. "false" if the credentials should be used on all operations. |
|
ignoreRefs |
"true" if referrals should be ignored. |
Posix DB
While this insert isn’t directly associated with Active Directory, when combined with the Active Directory Joiner and the Active Directory insert this insert provides additional posix attributes.
Class Name |
net.sourceforge.myvd.inserts.ad.PosixDB |
|
Scope |
Add, Modify, Delete, Rename, Search, Post Search Entry |
|
Configuration Options |
tableName |
The name of the posix table |
type |
user or group |
|
Configuration Options |
driver |
The JDBC driver to use |
url |
The JDBC connection URL |
|
user |
The database user to connect as |
|
password |
The password to connect with |
|
maxCons |
The maximum number of connections, default is 5 |
|
maxIdleCon |
The maximum amount of time a connection can remain idle before closing |
AD Posix Joiner
The Active Directory / Posix Joiner is an insert that combines the general joiner with the mapping inserts and Active directory inserts in order to create a view for posix integration. This joiner is meant to join a namespace configured with the AD Insert and another namespace configured for users and groups using the PosixDB inserts.
Class Name |
net.sourceforge.myvd.inserts.ad.ADPosixJoiner |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
activeDirectoryBase |
The base for the AD inserts |
dnAttribs |
Optional, AD attributes. Default uniqueMember,member,memberOf,distinguishedname,objectcategory |
|
userAddBase |
The RDN of the user base |
|
groupAddBase |
The object class that identifies groups |
|
homeDirTemplate |
The template for home directories. Enclose attributes in @@. For instance to have the directory be /home/USERS_SAMACCOUNTNAME it would be /home/@samaccountname@ |
|
dbBase |
The base for the PosixDB inserts |
CorruptObjectGUID
This insert allows a client that tries to search on an ObjectGUID that has been cast to text improperly.
Class Name |
com.tremolosecurity.proxy.myvd.inserts.util.CorruptObjectGUID |
|
Scope |
Search, Post Search Entry |
CreateUPN
Active Directory “user” objects don’t all have user principal name objects which can interfere with directory based systems that expect them. This insert will create a userPrincipalName object based on a directory attribute and suffix.
Class Name |
com.tremolosecurity.proxy.myvd.inserts.ad. CreateUPN |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
prefixAttributeName |
The name of the attribute that’s used as the source for the UPN; generally uid |
suffix |
The domain name to use as a suffix for the UPN |
UUIDtoText
The objectGUID attribute is a binary attribute that is often corrupted by translation to text. This insert will translate a binary attribute to text properly.
Class Name |
com.tremolosecurity.proxy.myvd.inserts.util.UUIDtoText |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
attributeName |
The name of the attribute to map |
AddActiveDirectoryEnabledFlag
This insert will check the userAccountControl in AD for the value of "2" and set an attribute to "true" if the user is enabled and "false" if the user is disabled.
Class Name |
net.sourceforge.myvd.inserts.mapping.AddActiveDirectoryEnabledFlag |
|
Scope |
Search, Post Search Entry |
|
Configuration Options |
nameOfAttributeToCreate |
The name of the attribute to store the flag in |
Creating Custom Inserts
MyVD supports the creation of custom inserts. Technically speaking, there is no difference between a "custom" insert and one that comes with MyVD. Inserts are pretty straight forward to build and only require some knowledge of LDAP, Java and whatever you are looking to integrate. Inserts work on a similar premesise as Java Servlet filters. A single method is used for both pre, post and operation control.
Why Create a Custom Insert?
There are many reasons why you would create a custom insert. These range from integrating a resource that is not currently supported by MyVD (such as a web service) to needing to implement more complex routing rules then existing Inserts provide.
Creating a Development Environment
MyVD requires the following for developing a custom insert:
-
JDK 1.5+
-
myvd.jar
-
ldap.jar
-
jdbcldap.jar
As for how you develop and integrate your custom insert, thats up to you.
Getting Started
Once your development environment is setup, creating an insert only requires that you implement the net.sourceforge.myvd.inserts.Insert interface. A bare bones insert might look like this:
package net.sourceforge.myvd;
import java.util.ArrayList;
import java.util.Properties;
import com.novell.ldap.LDAPConstraints;
import com.novell.ldap.LDAPException;
import com.novell.ldap.LDAPModification;
import com.novell.ldap.LDAPSearchConstraints;
import net.sourceforge.myvd.chain.AddInterceptorChain;
import net.sourceforge.myvd.chain.BindInterceptorChain;
import net.sourceforge.myvd.chain.CompareInterceptorChain;
import net.sourceforge.myvd.chain.DeleteInterceptorChain;
import net.sourceforge.myvd.chain.ExetendedOperationInterceptorChain;
import net.sourceforge.myvd.chain.ModifyInterceptorChain;
import net.sourceforge.myvd.chain.PostSearchCompleteInterceptorChain;
import net.sourceforge.myvd.chain.PostSearchEntryInterceptorChain;
import net.sourceforge.myvd.chain.RenameInterceptorChain;
import net.sourceforge.myvd.chain.SearchInterceptorChain;
import net.sourceforge.myvd.core.NameSpace;
import net.sourceforge.myvd.inserts.Insert;
import net.sourceforge.myvd.types.Attribute;
import net.sourceforge.myvd.types.Bool;
import net.sourceforge.myvd.types.DistinguishedName;
import net.sourceforge.myvd.types.Entry;
import net.sourceforge.myvd.types.ExtendedOperation;
import net.sourceforge.myvd.types.Filter;
import net.sourceforge.myvd.types.Int;
import net.sourceforge.myvd.types.Password;
import net.sourceforge.myvd.types.Results;
public class WebServiceInsert implements Insert {
public void add(AddInterceptorChain chain, Entry entry,
LDAPConstraints constraints) throws LDAPException {
//TODO Add pre-operation code
chain.nextAdd(entry, constraints);
//TODO Add post-operation code
}
public void bind(BindInterceptorChain chain, DistinguishedName dn,
Password pwd, LDAPConstraints constraints) throws LDAPException {
//TODO Add pre-operation code
chain.nextBind(dn, pwd, constraints);
//TODO Add post-operation code
}
public void compare(CompareInterceptorChain chain, DistinguishedName dn,
Attribute attrib, LDAPConstraints constraints) throws LDAPException {
//TODO Add pre-operation code
chain.nextCompare(dn, attrib, constraints);
//TODO Add post-operation code
}
public void configure(String name, Properties props, NameSpace nameSpace)
throws LDAPException {
//TODO Write Configuration Code Here
}
public void delete(DeleteInterceptorChain chain, DistinguishedName dn,
LDAPConstraints constraints) throws LDAPException {
//TODO Add pre-operation code
chain.nextDelete(dn, constraints);
//TODO Add post-operation code
}
public void extendedOperation(ExetendedOperationInterceptorChain chain,
ExtendedOperation op, LDAPConstraints constraints)
throws LDAPException {
//TODO Add pre-operation code
chain.nextExtendedOperations(op, constraints);
//TODO Add post-operation code
}
public void modify(ModifyInterceptorChain chain, DistinguishedName dn,
ArrayList<LDAPModification> mods, LDAPConstraints constraints)
throws LDAPException {
//TODO Add pre-operation code
chain.nextModify(dn, mods, constraints);
//TODO Add post-operation code
}
public void postSearchComplete(PostSearchCompleteInterceptorChain chain,
DistinguishedName base, Int scope, Filter filter,
ArrayList<Attribute> attributes, Bool typesOnly,
LDAPSearchConstraints constraints) throws LDAPException {
//TODO Add pre-operation code
chain.nextPostSearchComplete(base, scope, filter, attributes, typesOnly, constraints);
//TODO Add post-operation code
}
public void postSearchEntry(PostSearchEntryInterceptorChain chain,
Entry entry, DistinguishedName base, Int scope, Filter filter,
ArrayList<Attribute> attributes, Bool typesOnly,
LDAPSearchConstraints constraints) throws LDAPException {
//TODO Add pre-operation code
chain.nextPostSearchEntry(entry, base, scope, filter, attributes, typesOnly, constraints);
//TODO Add post-operation code
}
public void rename(RenameInterceptorChain chain, DistinguishedName dn,
DistinguishedName newRdn, Bool deleteOldRdn,
LDAPConstraints constraints) throws LDAPException {
//TODO Add pre-operation code
chain.nextRename(dn, newRdn, deleteOldRdn, constraints);
//TODO Add post-operation code
}
public void rename(RenameInterceptorChain chain, DistinguishedName dn,
DistinguishedName newRdn, DistinguishedName newParentDN,
Bool deleteOldRdn, LDAPConstraints constraints)
throws LDAPException {
//TODO Add pre-operation code
chain.nextRename(dn, newRdn, newParentDN, deleteOldRdn, constraints);
//TODO Add post-operation code
}
public void search(SearchInterceptorChain chain, DistinguishedName base,
Int scope, Filter filter, ArrayList<Attribute> attributes,
Bool typesOnly, Results results, LDAPSearchConstraints constraints)
throws LDAPException {
//TODO Add pre-operation code
chain.nextSearch(base, scope, filter, attributes, typesOnly, results, constraints);
//TODO Add post-operation code
}
}
Each method has essentially the same pattern:
\1. Pre-operation work (ie adding attributes or routing)
\2. Continue down the operations chain
\3. Post-operation work
If this insert is going to handle the request then don’t call the methods "nextXXX" method. The "configure" method is called when the insert is initiated. The "props" object contains all properties configured for this insert.
Accessing and Updating the Virtual Directory
Sometimes it’s desirable to perform a search or update an object in the directory inside of an insert. Inserts may create new chains of execution via the "createXXX" methods on the chain object.
chain.createAddChain().nextAdd(entry, constraints);
This code will execute every insert below the current one. So for instance if this insert is the 3rd in a chain of five inserts the above code would execute inserts four and five.
Using SQL inside of MyVD
One feature that only exists in MyVD is to utilize the JDBC-LDAP bridge to use SQL inside of MyVD instead of coding what is needed uing the above method. This is useful for Java developers who are more familiar with SQL then LDAP. A connection object can be retrieved via the chain object.
java.sql.Connection con = chain.createJdbcLdapConnection();
Deploying Custom Inserts
Once the insert is complete, jar it and copy it to the "lib" directory of your MyVD installation. Then you can configure your insert like any other insert.
Deploying MyVD
MyVD Prerequisites
MyVD require JRE 1.8 or higher. All other pre-requisites come with MYVD.
The MyVD Install Directory
Deploying MyVD is fairly straight forward. The download bundle has several directories:
Directory |
Contents |
bin |
Contains all startup scripts |
conf |
Comtains the myvd.conf file and the logging.conf files |
logs |
Default log location |
lib |
Stores all additional libraries. This is where to copy jar files containing custom inserts |
jar |
Contains the myvd.jar file |
Starting MyVD
The bin directory contains unix and windows startup scripts. To start MyVD on windows, double click (or create a shortcut to) myvd.vbs. On unix run the myvd.sh script with either the parameters "start" or "stop".
If you wish to create a custom start script the MyVD startup command is
java -server net.sourceforge.myvd.server.Server /path/to/config/myvd.conf
The Default MyVD Configuration
The myvd.conf file in the conf directory of the MyVD bundle has a very basic default configuration. This configuration is setup to have the DumpTransaction insert on the global chain and a sample RootDSE configuration.
Configuring Logging
MyVD utilizes the Log4j2 (http://logging.apache.org/log4j/2.x/) logging system. By default MyVD will log to the "logs" directory in the MyVD bundle at an "informational" level. The full default Log4J configuration is
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<RollingFile name="server-log" fileName="../logs/myvd.log"
append="true" filePattern="../logs/myvd.log.%d">
<PatternLayout pattern="[%d][%t] %-5p %c{1} - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="server-log" />
</Root>
</Loggers>
</Configuration>
To customize logging log4j properties may be set in a file called log4j2.xml in the same directory as the myvd.conf file used to configure your instance. If you only wish to make a change to any of the above properties you only need to set that property. For instance to enable "debug" mode the log4j2.xml file should look like:
l<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<RollingFile name="server-log" fileName="../logs/myvd.log"
append="true" filePattern="../logs/myvd.log.%d">
<PatternLayout pattern="[%d][%t] %-5p %c{1} - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="server-log" />
</Root>
</Loggers>
</Configuration>
Utilizing The Start Scripts
MyVD comes with start scripts for both *nix and Windows. In order to use these scripts two environment variables need to be defined:
JAVA_HOME |
The path to the JRE |
MYVD_HOME |
The path to where MyVD has been installed |
Integrating Directories
Directory integration is one of the simplest tasks in MyVD. In that MyVD is built and organized like a directory, integrating a directory is very simple and straight forward. There are several possible use cases for integrating directories such as:
-
Directory Integration - Combining multiple directories into one larger directory
-
DN Re-writing - A directory needs to be re-organized for a specific application or the RDN needs to be changed but the underlying data can not be changed
-
Attribute Mapping - An application requires attribute names to be changed or attributes to be created
-
Master / Replica Management - Many directory environments have a master/replica setup where writes should be sent to a replica while reads are executed against a replica transparently to applications
MyVD supplies several inserts to support these use cases. The primary insert for directory integration is the LDAP Insert.
Configuring a basic LDAP Proxy
The LDAP Insert is the most important insert for integrating directories as it acts as the primary interface between a remote directory and MyVD. In order to setup a simple proxy all one must do is create an LDAP insert. Below is the configuration information for the LDAP insert:
LDAP Insert
The LDAP insert is one of the core inserts. This insert allows MyVD to communicate with any LDAPv3 compliant directory. In addtion to LDAP directories, this isnert supports DSMLv2 and SPML. In order to take advantage of the SPML feautures you must download the OpenSPML toolkit (http://www.openspml.org/) and it’s dependencies.
Class Name |
net.sourceforge.myvd.inserts.ldap.LDAPInterceptor |
|
Scope |
Add, Modify, Rename, Delete, Compare, Search, Bind, Extended Operiations |
|
Configuration Options |
host |
The host of the remote server. If this insert is using DSMLV2 or SPML then this is the URL of the end point. |
port |
The port the remote ldap server is listening on. Ignored for DSMLV2 and SPML |
|
remoteBase |
The base of the remote LDAP server that the insert would start at |
|
minimumConnections |
The minimum number of connections to keep in the pool |
|
maximumConnections |
The maximum number of connections to keep in the pool |
|
proxyDN |
The DN of the user to connect as |
|
proxyPass |
The password of the remote directory |
|
type |
One of "ldap","dsmlv2" or "spml" |
|
spmlImpl |
When using SPML, the class name for the com.novell.ldap.spml.SPMLImpl interface |
|
passBindOnly |
"true" or "false" if the user’s credentials should be utilized for only the "bind" process. "false" if the credentials should be used on all operations. |
|
ignoreRefs |
"true" if referals should be ignored. |
The above configuration parameters are for the most part very straight forward. The host and port configs are the host and port the directory is running on. The remoteBase config is to determine where in the remote directory MyVD should connect to. THe minimumConnections and maximumConnections are for connection pooling. The proxyDN and proxyPass configuration options may be supplied if you do not want to proxy the user’s credentials. When these are set they are used for all operations except for the bind operation. The type parameter is used to determine the protocol type. The LDAP Insert is based on Novel’s JLDAP implementation which supports LDAPv3, DSMLv2 (LDAP Web Service) and SPML (provisioning Web Service). Finaly, the spmlImpl property is used to determine how an SPML insert should interact with the remote server. By simply seting up an LDAP insert using the above parameters you can easily setup a remote proxy.
The below configuration is from the unit tests and shows a very simple proxy:
server.listener.port=50983
#No global chain
server.globalChain=
#Setup a single proxy
server.nameSpaces=BaseServer
#Simple LDAP insert
server.BaseServer.chain=LDAPBaseServer
server.BaseServer.nameSpace=o=mycompany,c=us
server.BaseServer.weight=0
server.BaseServer.LDAPBaseServer.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.BaseServer.LDAPBaseServer.config.host=localhost
server.BaseServer.LDAPBaseServer.config.port=10983
server.BaseServer.LDAPBaseServer.config.remoteBase=dc=domain,dc=com
server.BaseServer.LDAPBaseServer.config.proxyDN=cn=admin,dc=domain,dc=com
server.BaseServer.LDAPBaseServer.config.proxyPass=manager
The above configuration is very straight forward. A single insert is created for an ldap server running on the local server with administrative credentials. While the above example is simple, it is only the tip of the iceberg.
Integrating Multiple Directories
It’s common for an IT infrastructure to have multiple directories, not just a single directory. In many situations there are at least two directories, one used for internal users (such as employees) and another for external users such as contractors and customers. While this may make maintenance easier, most applications can only utilize a single directory however. MyVD can be used to integrate these multiple directories into a single directory without copying data. The below diagram shows how this may be accomplished:
Integrating multiple directories is very easy with MyVD. In order to perform this integration simply create seperate inserts for each directory with seperate DNs for the base. The below example configuration shows how to accomplish this with three inserts:
-
Base - For the user base (required by some applications)
-
Internal Users - For all internal users
-
External Users - For all external users
server.listener.port=50983
server.globalChain=
#Define a namespace for each part of the DIT
server.nameSpaces=BaseServer,InternalServer,ExternalServer
#define the base server to provide an entry to connect internal and external users
server.BaseServer.chain=LDAPBaseServer
server.BaseServer.nameSpace=o=mycompany,c=us
server.BaseServer.weight=0
server.BaseServer.LDAPBaseServer.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.BaseServer.LDAPBaseServer.config.host=localhost
server.BaseServer.LDAPBaseServer.config.port=10983
server.BaseServer.LDAPBaseServer.config.remoteBase=dc=domain,dc=com
server.BaseServer.LDAPBaseServer.config.proxyDN=cn=admin,dc=domain,dc=com
server.BaseServer.LDAPBaseServer.config.proxyPass=manager
#Create the chain for internal users
server.InternalServer.chain=LDAPInternalServer
server.InternalServer.nameSpace=ou=internal,o=mycompany,c=us
server.InternalServer.weight=10
server.InternalServer.LDAPInternalServer.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.InternalServer.LDAPInternalServer.config.host=localhost
server.InternalServer.LDAPInternalServer.config.port=11983
server.InternalServer.LDAPInternalServer.config.remoteBase=ou=internal,dc=domain,dc=com
server.InternalServer.LDAPInternalServer.config.proxyDN=cn=admin,ou=internal,dc=domain,dc=com
server.InternalServer.LDAPInternalServer.config.proxyPass=manager
#Create the chain for external users
server.ExternalServer.chain=LDAPExternalServer
server.ExternalServer.nameSpace=ou=external,o=mycompany,c=us
server.ExternalServer.weight=15
server.ExternalServer.LDAPExternalServer.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.ExternalServer.LDAPExternalServer.config.host=localhost
server.ExternalServer.LDAPExternalServer.config.port=12983
server.ExternalServer.LDAPExternalServer.config.remoteBase=ou=external,dc=domain,dc=com
server.ExternalServer.LDAPExternalServer.config.proxyDN=cn=admin,ou=external,dc=domain,dc=com
server.ExternalServer.LDAPExternalServer.config.proxyPass=manager
The above configuration is fairly straight forward. Once created, a virtual directory representing both internal and external users is created. This configuration can further be extended by adding replica routing inserts, access controls and joiners.
Directory Routing
Many directory environments include seperate servers for read operations and write operations. Because applications aren’t generaly coded to work in this way, MyVD can be setup as a router to route requests to difference name spaces based on certain criteria (in this case the type of operation being performed). An insert called the MasterReplicaInsert may be used to automatically route write requests to masters while routing all search and bind requests to replicas. The below configuration shows how this concept would be implemented.
server.listener.port=50983
server.globalChain=routingPlugin
#routing plugin to forward all writes to the master and reads to the "replicas"
server.globalChain.routingPlugin.className=net.sourceforge.myvd.inserts.routing.MasterReplicaRouter
server.globalChain.routingPlugin.config.specifyToInclude=false
server.globalChain.routingPlugin.config.readOnly=BaseServer
server.globalChain.routingPlugin.config.master=MasterServer
server.nameSpaces=MasterServer,BaseServer
#Define the master
server.MasterServer.chain=LDAPBaseServer
server.MasterServer.nameSpace=o=mycompany,c=us
server.MasterServer.weight=0
server.MasterServer.LDAPBaseServer.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.MasterServer.LDAPBaseServer.config.host=localhost
server.MasterServer.LDAPBaseServer.config.port=13983
server.MasterServer.LDAPBaseServer.config.remoteBase=dc=domain,dc=com
server.MasterServer.LDAPBaseServer.config.proxyDN=cn=admin,dc=domain,dc=com
server.MasterServer.LDAPBaseServer.config.proxyPass=manager
#Define the replica with the same namespace as the master
server.BaseServer.chain=LDAPBaseServer
server.BaseServer.nameSpace=o=mycompany,c=us
server.BaseServer.weight=10
server.BaseServer.LDAPBaseServer.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.BaseServer.LDAPBaseServer.config.type=LDAP
server.BaseServer.LDAPBaseServer.config.host=localhost
server.BaseServer.LDAPBaseServer.config.port=10983
server.BaseServer.LDAPBaseServer.config.remoteBase=dc=domain,dc=com
server.BaseServer.LDAPBaseServer.config.proxyDN=cn=admin,dc=domain,dc=com
server.BaseServer.LDAPBaseServer.config.proxyPass=manager
By using the master/replica routing insert applications can transparently interact with the directory environment even if the application is not built to handle it.
Using Databases
Its very common to utilize a virtual directory to integrate relational databases into a directory infrastructure. There are two primary ways to integrate a database:
-
Store additional application specific attributes
-
Expose existing data in a relational database as a directory
In both of these situations integrating your existing database infrastructure into MyVirtualDirectory is very easy. Before going over how to integrate a database, lets cover some of the fundamentals of integrating with databases:
This first thing to understand is how MyVD integrates a database. There are three primary configuration points:
-
DB Configuration Information - How does MyVD connect to the database?
-
Data SQL - What data will MyVD be serving? This is configured as an SQL statement. It can be as simple as "SELECT * FROM table" and can be as complex as you want. Its not recomended that a very complex SQL statement is used for performance reasons.
-
Mapping - Map database column names to ldap attribute names.
Once you have decided what data and how it will be mapped, you need to decide if you want writes to be sent to the database. The database insert alone does not support writes. We will cover to perform writes later in the doc.
Setting Up a Simple Database Insert
First lets setup a simple database insert. The database insert has the following configuration
Class Name |
net.sourceforge.myvd.inserts.jdbc.JdbcInsert |
|
Scope |
Search |
|
Configuration Options |
driver |
The JDBC driver to use |
url |
The JDBC connection URL |
|
user |
The database user to connect as |
|
password |
The password to connect with |
|
rdn |
The LDAP atribute used as the rdn for all objects generated |
|
mapping |
Comma sepperated list of ldap=db mapping of attributes |
|
objectClass |
The objectClass for all octs created by this insert |
|
sql |
The SQL use to determine what objects this insert will generate. This SQL must include all attributes included in the mapping configuration |
|
maxCons |
The maximum number of connections, default is 5 |
|
maxIdleCon |
The maximum amount of time a connection can remain idle before closing |
|
useSimple |
true or false, utilizes the "simple" SQL, mutch better performance but only the value of multivalued attributes in the filter are returned |
We are going to integrate a simple database table that will contain the following columns
Column |
Type |
Description |
id |
integer |
The unique id of the user |
firstName |
varchar(50) |
The use’s first name |
lastName |
varchar(50) |
The user’s last name |
varchar(50) |
The user’s email address |
|
location |
varchar(50) |
The user’s home city |
You will notice that there are three fields missing that are typically a included in any directory object: uid, cn and userPassword. Each of these fields have been left out because they generally do not exist in databases. Databases are rarely used for authentication so there is rarely a uid and password. Because databases tend to normalize data, they don’t store a full name in a single field. If this is a requirement, we will cover how to handle these cases later on.
The table we are going to work with is called "employees" with the following data:
id |
firstname |
lastname |
location |
|
1 |
John |
Doe |
Boston |
|
2 |
Jen |
Burnstein |
Boston |
|
3 |
Josh |
Bora |
New York |
|
4 |
Alice |
Raden |
Los Angeles |
The first step is to download MySQL and the MySQL jdbc drivers. Thisguide doesn’t cover setting up MySQL. Once you downloaded MySQL and have setup the database, copy mysql-connector-java-VERSION-bin.jar to the lib directory of your myvd installation. If you are using the MyVD startup script MyVD will automatically pickup the drivers. Once deployed we can create our configuration.
Once downloaded, the myvd.conf file needs to be configured to utilize the employees table. Below is the configuration we are going to use:
#Listen on port 389
server.listener.port=10389
#Listen on 636 using SSL
#server.secure.listener.port=636
#server.secure.keystore=/var/keystores/myvd.ks
#server.secure.keypass=secret
#Configure global chains
server.globalChain=LogAllTransactions
server.globalChain.LogAllTransactions.className=net.sourceforge.myvd.inserts.DumpTransaction
server.globalChain.LogAllTransactions.config.logLevel=info
server.globalChain.LogAllTransactions.config.label=Global
#Configure namespaces
server.nameSpaces=Root,DBEmployees
#Define RootDSE
server.Root.chain=RootDSE
server.Root.nameSpace=
server.Root.weight=0
server.Root.RootDSE.className=net.sourceforge.myvd.inserts.RootDSE
server.Root.RootDSE.config.namingContexts=ou=employees,o=mycompany,c=us
server.DBEmployees.chain=DB
server.DBEmployees.nameSpace=ou=employees,o=mycompany,c=us
server.DBEmployees.weight=0
server.DBEmployees.DB.className=net.sourceforge.myvd.inserts.jdbc.JdbcInsert
server.DBEmployees.DB.config.driver=com.mysql.jdbc.Driver
server.DBEmployees.DB.config.url=jdbc:mysql://localhost/myvdtest
server.DBEmployees.DB.config.user=root
server.DBEmployees.DB.config.password=
server.DBEmployees.DB.config.rdn=empid
server.DBEmployees.DB.config.mapping=empid=id,givenName=firstname,sn=lastname,mail=email,l=location
server.DBEmployees.DB.config.objectClass=employeePerson
server.DBEmployees.DB.config.sql=SELECT id,firstname,lastname,email,location FROM employees
This configuration has four major parts:
-
LDAP Listen Configuration - Determines what port MyVD will listen on
-
Global Server Configuration - Configures the global chanin, in this case the global chain only contains the LogAllTransactions insert
-
Root DSE Configuration - Configures the root DSE, in this case the RootDSE insert is configured to generate naming contexts
-
Database Configuration - The database configuration
The first three configuration points are a part of the standard sample myvd.conf file included in the download. The final configuration point is the heart of the matter. The driver, url, user and password configuration options general options. The rdn option is the LDAP name of the database field, in this case the empid LDAP attribute is mapped to the id database field. The mapping option is the ldap=db mapping. The left hand side of the equals is the LDAP name and the right side of the equals is the database field name. The objectClass configuration is what object class entries from this insert will have. This can be any object class. Finally the sql option is an SQL statement that provides all of the database fields. This can be a simple SELECT as in the above case or can be something more complex as long as every column listed in the mapping configuration is included.
Thats it. Thats all thats involved. Below are the more advanced topics involved in exposing a database via MyVD.
Composite Attributes
Unlike LDAP directories, relational databases do not allways store information in a final "view" state. The simplest such example of this is the common name, or "cn" attribute. Another such example is the "uid" attribute. In the below example, we are going to add to our simple DB insert to include both a uid an cn attribute.
When mapping attributes its important to understand how the mapping process works. There are two directions to every LDAP request. The first is the inbound request. On the inbound request the attributes requested and the filter used must be updated. For instance if you are requesting the "uid" attribute, which in our case is a compound attribute of the givenName and sn attributes, then the request must instead include a request for the givenName and sn attribute. If the filter for the request is "(uid=sperson)" then the filter must be transformed to "(&(givenName=S*)(sn=Person))". The outbound result then needs to construct the attribute from the components. Here’s an example. The below table shows the initial request and the final request:
Inital Form |
Transformed |
|
Base |
ou=employees-uid_cn,o=mycompany,c=us |
ou=employees-uid_cn,o=mycompany,c=us |
Scope |
Subtree |
Subtree |
Attributes |
uid,cn,l |
givenName,sn,l |
Filter |
(&(l=New York)(uid=jbora))) |
(&(l=New York)(&(givenName=J*)(sn=Bora)))) |
MyVD includes an insert to perform all of this work for you called the composite attribute insert. This insert takes the attributes that are to be assembled and creates the attribute on outbound entries and adjusts all requests accordingly. Fist lets look at how teh composite attribute insert is configured:
Composite Attribute Insert
The composite attribute insert is designed to create a singe attribute from multiple attributes. Uses include creating a CN attribute from the givenName and sn as well as creating a uid from the same two attributes.
Class Name |
net.sourceforge.myvd.inserts.jdbc.CompositeAttrib |
|
Scope |
Search |
|
Configuration Options |
attribute |
The name of the attribute that will be created (ie uid or cn) |
components |
Comma seperated list of component attributes with a colon to determine how much of the attribute. The length of the compoenent can be a number, fs for the first space, ls for the last space and * for the entire attribute. ie "givenName:1,sn:*" |
|
properCase |
true or false. This configurate determines if the first letter of a component should be upper case on inbound transactions and lowercase in outbound transactions. |
|
objectClass |
The objectClass that this insert will apply to. |
The "attribute" parameter is pretty straight forward. The components configuration is a bit more comlex. The goal of this configuration option is to determine how an attribute is created. The value for this option is "attribute:length,attribute:length,…". The length can be either a number, "" to indicate the entire attribute, "fs" to indicate that the attribute goes until the first space of the final attribute and finally "ls" for the last space of the final attribute. For instance a uid would be constructed by the first letter of the first name and the last name, or "givenName:1,sn:". A cn would be the entire first name, a space and then the last name or "givenName:fs,sn*". Some organizations include the middle initial in the first name, which means you would want the last space or "givenName:ls,sn:*". The next configuration, "properCase", is used to automaticlly set the case from all lowercase to a proper case. This is useful when you want your uid’s to be lowercase. Finally the objectClass configuration is used to determine when the composite attribute should be built.
Now that we’ve covered how the composite insert works and how its configured, lets look at the configuration:
#Listen on port 389
server.listener.port=10389
#Listen on 636 using SSL
#server.secure.listener.port=636
#server.secure.keystore=/var/keystores/myvd.ks
#server.secure.keypass=secret
#Configure global chains
server.globalChain=LogAllTransactions
server.globalChain.LogAllTransactions.className=net.sourceforge.myvd.inserts.DumpTransaction
server.globalChain.LogAllTransactions.config.logLevel=info
server.globalChain.LogAllTransactions.config.label=Global
#Configure namespaces
server.nameSpaces=Root,DBEmployees,DBEmployeesUidCn
#Define RootDSE
server.Root.chain=RootDSE
server.Root.nameSpace=
server.Root.weight=0
server.Root.RootDSE.className=net.sourceforge.myvd.inserts.RootDSE
server.Root.RootDSE.config.namingContexts=ou=employees-uid_cn,o=mycompany,c=us
server.DBEmployeesUidCn.chain=CleanAttribs,MakeUID,MakeCN,DB
server.DBEmployeesUidCn.nameSpace=ou=employees-uid_cn,o=mycompany,c=us
server.DBEmployeesUidCn.weight=0
server.DBEmployeesUidCn.CleanAttribs.className=net.sourceforge.myvd.inserts.mapping.AttributeCleaner
server.DBEmployeesUidCn.MakeUID.className=net.sourceforge.myvd.inserts.jdbc.CompositeAttrib
server.DBEmployeesUidCn.MakeUID.config.attribute=uid
server.DBEmployeesUidCn.MakeUID.config.components=givenname:1,sn:*
server.DBEmployeesUidCn.MakeUID.config.properCase=true
server.DBEmployeesUidCn.MakeUID.config.objectClass=employeePerson
server.DBEmployeesUidCn.MakeCN.className=net.sourceforge.myvd.inserts.jdbc.CompositeAttrib
server.DBEmployeesUidCn.MakeCN.config.attribute=cn
server.DBEmployeesUidCn.MakeCN.config.components=givenname:ls,sn:*
server.DBEmployeesUidCn.MakeCN.config.properCase=false
server.DBEmployeesUidCn.MakeCN.config.objectClass=employeePerson
server.DBEmployeesUidCn.DB.className=net.sourceforge.myvd.inserts.jdbc.JdbcInsert
server.DBEmployeesUidCn.DB.config.driver=com.mysql.jdbc.Driver
server.DBEmployeesUidCn.DB.config.url=jdbc:mysql://localhost/myvdtest
server.DBEmployeesUidCn.DB.config.user=root
server.DBEmployeesUidCn.DB.config.password=
server.DBEmployeesUidCn.DB.config.rdn=empid
server.DBEmployeesUidCn.DB.config.mapping=empid=id,givenName=firstname,sn=lastname,mail=email,l=location
server.DBEmployeesUidCn.DB.config.objectClass=employeePerson
server.DBEmployeesUidCn.DB.config.sql=SELECT id,firstname,lastname,email,location FROM employees
The above configuration is very similar to the original DB configuration. We have added three new inserts. The first insert ensures that the attributes returned on a search are the ones requested in the search. The second insert creates the UID attribute while the third insert creates a CN attributes.
Changing The RDN Attribute
Some applications require a particuler RDN attribute to work properly. This is usually true of applications that require the user id to be a part of the RDN. In our above example the "empid" attribute is used as the RDN. For our database this makes sence because the id is the most unique identifier for our users. However, we are integrating with an application requires the uid we constructed in the last tutorial to be our user’s RDN. In order to do this we must translate every inbound request and outbound response. There is an insert that does this work for us however called the SetRDN insert. Here is the SetRDN insert’s configuration:
SetRDN Insert
This insert allows for the RDN of an entry to be changed. For instance if the current RDN is the "CN" attribute this insert can change it to the "uid" attribute.
Class Name |
net.sourceforge.myvd.inserts.setrdn.SetRDN |
|
Scope |
Search,Add,Modify,Delete,Rename,Bind |
|
Configuration Options |
internalRDN |
The current RDN attribute name. |
externalRDN |
The RDN attribute that will be presented |
The configuration is fairly straight forward. The internalRDN is the rdn stored in the directory while the externalRDN is what is presented to the user. The below configuration shows how the SetRDN insert can be added to our example:
#Listen on port 389
server.listener.port=10389
#Listen on 636 using SSL
#server.secure.listener.port=636
#server.secure.keystore=/var/keystores/myvd.ks
#server.secure.keypass=secret
#Configure global chains
server.globalChain=LogAllTransactions
server.globalChain.LogAllTransactions.className=net.sourceforge.myvd.inserts.DumpTransaction
server.globalChain.LogAllTransactions.config.logLevel=info
server.globalChain.LogAllTransactions.config.label=Global
#Configure namespaces
server.nameSpaces=Root,DBEmployees,DBEmployeesUidCn,DBEmployeesUidRDN
#Define RootDSE
server.Root.chain=RootDSE
server.Root.nameSpace=
server.Root.weight=0
server.Root.RootDSE.className=net.sourceforge.myvd.inserts.RootDSE
server.Root.RootDSE.config.namingContexts=ou=employees,o=mycompany,c=us|ou=employees-uid_cn,o=mycompany,c=us|ou=employees-uid_rdn,o=mycompany,c=us
,
,
,
server.DBEmployeesUidRDN.chain=CleanAttribs,SetRDN,MakeUID,MakeCN,DB
server.DBEmployeesUidRDN.nameSpace=ou=employees-uid_rdn,o=mycompany,c=us
server.DBEmployeesUidRDN.weight=0
server.DBEmployeesUidRDN.CleanAttribs.className=net.sourceforge.myvd.inserts.mapping.AttributeCleaner
server.DBEmployeesUidRDN.SetRDN.className=net.sourceforge.myvd.inserts.setrdn.SetRDN
server.DBEmployeesUidRDN.SetRDN.config.internalRDN=empid
server.DBEmployeesUidRDN.SetRDN.config.externalRDN=uid
server.DBEmployeesUidRDN.MakeUID.className=net.sourceforge.myvd.inserts.jdbc.CompositeAttrib
server.DBEmployeesUidRDN.MakeUID.config.attribute=uid
server.DBEmployeesUidRDN.MakeUID.config.components=givenname:1,sn:*
server.DBEmployeesUidRDN.MakeUID.config.properCase=true
server.DBEmployeesUidRDN.MakeUID.config.objectClass=employeePerson
server.DBEmployeesUidRDN.MakeCN.className=net.sourceforge.myvd.inserts.jdbc.CompositeAttrib
server.DBEmployeesUidRDN.MakeCN.config.attribute=cn
server.DBEmployeesUidRDN.MakeCN.config.components=givenname:ls,sn:*
server.DBEmployeesUidRDN.MakeCN.config.properCase=false
server.DBEmployeesUidRDN.MakeCN.config.objectClass=employeePerson
server.DBEmployeesUidRDN.DB.className=net.sourceforge.myvd.inserts.jdbc.JdbcInsert
server.DBEmployeesUidRDN.DB.config.driver=com.mysql.jdbc.Driver
server.DBEmployeesUidRDN.DB.config.url=jdbc:mysql://localhost/myvdtest
server.DBEmployeesUidRDN.DB.config.user=root
server.DBEmployeesUidRDN.DB.config.password=
server.DBEmployeesUidRDN.DB.config.rdn=empid
server.DBEmployeesUidRDN.DB.config.mapping=empid=id,givenName=firstname,sn=lastname,mail=email,l=location
server.DBEmployeesUidRDN.DB.config.objectClass=employeePerson
server.DBEmployeesUidRDN.DB.config.sql=SELECT id,firstname,lastname,email,location FROM employees
Thats it. We have now exposed our directory with a new RDN attribute.
Updating The Database
The JDBC insert is a read only insert. It is based on an SQL statement which can have almost anything in it, preventing any way of knowing how an update is processed. There are three main reasons why the JDBC insert does not manage updates to the database:
-
Because the SQL can contain joins, composite attributes and functions making it impossible to know how data should be sent to the database.
-
Modifications to multi-value attributes are difficult to know how to manage.
-
Relational databases maintain the relationships between lookup tables, which LDAP has no knowledge of.
In order to update user entries MyVD allows you to create an insert that sits behind the JDBC insert that can be passed both the database-ldap mapping and a connection from the JDBC insert allowing the insert to to update the database. This insert can access three request variables:
MYVD_DB_CON_INSERT-NAME |
The database connection from the connection pool. The "INSERT-NAME" is the name of the insert into the configuration. |
MYVD_DB_LDAP2DB_INSERT-NAME |
The ldap→db mapping, meaning that the key for this HashMap is the ldap attribute name with the value being the database name. The "INSERT-NAME" is the name of the insert into the configuration. |
MYVD_DB_DB2LDAP_INSERT-NAME |
The db→ldap mapping, meaning that the key for this HashMap is the db attribute name with the value being the ldap name. The "INSERT-NAME" is the name of the insert into the configuration. |
This setup allows for MyVD to allow for database updates while not forcing you to manage database connections or mappings, re-using the configuration from the JDBC insert.
Updating a Single Table
MyVD has a simple JBDC table update insert that can be used for updating a single table. This insert makes it easy to update the database you have integrated without writing custom code. Here is the configuration for the DBTableUpdate insert:
Database Table Update
This insert contains the logic to update a single database table. It is configured BEHIND a database insert in order to utilize that insert’s connection pool and mapping.
Class Name |
net.sourceforge.myvd.inserts.jdbc.DBTableUpdate |
|
Scope |
Add,Modify,Delete,Rename |
|
Configuration Options |
tableName |
The name of the table to update in the database |
dbInsertName |
The name of the insert used by this insert |
This insert utilizes the DB insert as described above by adding the DBTableUpdate insert AFTER the DB insert. The tableName is the name of the table being updated while the dbInsertName is the name of the insert on this chain whose database pool and mapping configuration will be used.
Configuring this insert is very easy. The below configuration adds this insert to our above example of creating a uid and setting the rdn:
.
.
.
server.DBEmployeesUidRDN.chain=CleanAttribs,SetRDN,MakeUID,MakeCN,DB,TableUpdate
.
.
.
server.DBEmployeesUidRDN.TableUpdate.className=net.sourceforge.myvd.inserts.jdbc.DBTableUpdate
server.DBEmployeesUidRDN.TableUpdate.config.tableName=employees
server.DBEmployeesUidRDN.TableUpdate.config.dbInsertName=DB
Now our example is updateable!
Authenticating Users
The JDBC Insert doesn’t provide any way to authenticate against the database. this is because there are no standards as far as databases are concerned on how to store passwords and authenticate users. Just as with performing writes to the database, MyVD can pass a custom insert the database connection and mapping information allowing the custom insert to perform the authentication in whatever manner is required by the database.
Using "Simple" SQL Mode
Databases and directories store data in very different ways. Databases store data in tables that "relate" to each other where as a directories store data as objects. This means that databases don’t store mutliple value for a field in a given row where a directory can. In order to handle this the jdbc insert utilizes a subquery to ensure that all attribute values are returned MyVD uses sub-selects to perform queries. The basic of the SQL is:
SELECT fields FROM (SQL) WHERE rdnField IN (SELECT id FROM ... WHERE ...)
Depending on how your tables are indexed this can be very slow. If you aren’t able to index the proper fields or there are only single value attributes comming from the database you can use the "useSimple" flag on the jdbc insert to utilize a simpler SQL statement. This will improve performance but may sacrifice data consistency based on the type of filters being used.
Joining Identities
One of the main features and use cases of a virtual directory is to join identities in disparate stores. For instance, an enterprise directory may store enterprise wide user data, but a new application requires additional data (such as role based data). While the attributes could be added to the enterprise directory or an edge directory can be created and synchronized with the enterprise directory a simpler solution would be to setup a virtual directory to join, in real time, a database used to store the user data with data from the enterprise directory. The basic design is as follows:
-
An OpenLDAP server that contains enterprise user data
-
A MySQL database storing the userid, location and application attributes
-
MyVD deployed joining the two sources
The enterprise data:
# users, domain.com
dn: ou=users,dc=domain,dc=com
ou: users
objectClass: organizationalUnit
# jsmith000, users, domain.com
dn: uid=jsmith000,ou=users,dc=domain,dc=com
userPassword:: c2VjcmV0
uid: jsmith000
sn: Smith
cn: Joe Smith
objectClass: inetOrgPerson
# jsmith002, users, domain.com
dn: uid=jsmith002,ou=users,dc=domain,dc=com
userPassword:: c2VjcmV0
uid: jsmith002
sn: Smith
cn: Jen Smith
objectClass: inetOrgPerson
# bdund003, users, domain.com
dn: uid=bdund003,ou=users,dc=domain,dc=com
userPassword:: c2VjcmV0
uid: bdund003
sn: Dund
cn: Briana Dund
objectClass: inetOrgPerson
# rsandburg015, users, domain.com
dn: uid=rsandburg015,ou=users,dc=domain,dc=com
userPassword:: c2VjcmV0
uid: rsandburg015
sn: Sandburg
cn: Robert Sandburg
objectClass: inetOrgPerson
The database table that will store the enterprise data :
Field |
Type |
Null |
Key |
Default |
Extra |
id |
int(11) |
NO |
PRI |
NULL |
auto_increment |
username |
varchar(50) |
YES |
NULL |
||
location |
varchar(50) |
YES |
NULL |
||
appAttrib1 |
varchar(50) |
YES |
NULL |
||
appAttrib2 |
varchar(50) |
YES |
NULL |
The data in the table:
username |
location |
appAttrib1 |
appAttrib2 |
jsmith000 |
Boston |
widgets |
doo-dads |
jsmith002 |
Boston |
wing-dings |
doo-dads |
bdund003 |
LA |
wing-dings |
thingamabobers |
rsandburg015 |
Chicago |
widgets |
thingamabobers |
Notice that "username" field in the database table and the uid LDAP attributes match. This will be how we join a record in MyVD using the joiner insert. Before we configure our joiner, first the database and the enterprise directory need to be configured in MyVD. Below is the MyVirtualDirectory configuration for the enterprise directory and the database.
#Listen on port 389
server.listener.port=10389
#Listen on 636 using SSL
#server.secure.listener.port=636
#server.secure.keystore=/var/keystores/myvd.ks
#server.secure.keypass=secret
#Configure global chains
server.globalChain=LogAllTransactions
server.globalChain.LogAllTransactions.className=net.sourceforge.myvd.inserts.DumpTransaction
server.globalChain.LogAllTransactions.config.logLevel=info
server.globalChain.LogAllTransactions.config.label=Global
#Configure namespaces
server.nameSpaces=Root,DBEmployees,LDAPEmployees
#Define RootDSE
server.Root.chain=RootDSE
server.Root.nameSpace=
server.Root.weight=0
server.Root.RootDSE.className=net.sourceforge.myvd.inserts.RootDSE
server.Root.RootDSE.config.namingContexts=o=db|o=ldap
server.DBEmployees.chain=DB
server.DBEmployees.nameSpace=o=db
server.DBEmployees.weight=0
server.DBEmployees.DB.className=net.sourceforge.myvd.inserts.jdbc.JdbcInsert
server.DBEmployees.DB.config.driver=com.mysql.jdbc.Driver
server.DBEmployees.DB.config.url=jdbc:mysql://127.0.0.1/myvdjoin
server.DBEmployees.DB.config.user=trenduser
server.DBEmployees.DB.config.password=secret
server.DBEmployees.DB.config.rdn=uid
server.DBEmployees.DB.config.mapping=uid=username,l=location,appAttrib1=appAttrib1,appAttrib2=appAttrib2
server.DBEmployees.DB.config.objectClass=dbPerson
server.DBEmployees.DB.config.sql=SELECT username,location,appAttrib1,appAttrib2 FROM userdata
server.DBEmployees.DB.config.useSimple=true
server.LDAPEmployees.chain=LDAP
server.LDAPEmployees.nameSpace=o=ldap
server.LDAPEmployees.weight=0
server.LDAPEmployees.LDAP.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.LDAPEmployees.LDAP.config.host=127.0.0.1
server.LDAPEmployees.LDAP.config.port=389
server.LDAPEmployees.LDAP.config.remoteBase=ou=users,dc=domain,dc=com
The above configuration is straight forward. A single ldap insert chain is created for the enterprise directory and a database insert chain for the application database table. Before configuring the joiner, lets go into the details of configuring the joiner:
The joiner is used to combine two LDAP namespaces (note that this means two DN namespace, not two configuration namespaces). The joiner combines entries from each namespace.
Class Name |
net.sourceforge.myvd.inserts.join.Joiner |
|
Scope |
Search |
|
Configuration Options |
primaryNamespace |
LDAP DN that represents a configured namespace that will drive the namespace of the joiner |
joinedNamespace |
LDAP DN that represents a configured namespace that will be joined with the primaryNamespace. The namespace of the primaryNamespace drives the joiner’s namespace |
|
joinedAttributes |
Attributes to be included in the join |
|
joinFilter |
A filter that is used to combine objects. When including an attribute from the joining entry prepend the attribute with "ATTR.". For instance, to combine a user based on the uid attribute the filter would be "(uid=ATTR.uid)" |
|
bindPrimaryFirst |
If set to true, MyVD will first try to bind the primary insert. If set to false it will try the joined insert |
The joiner insert is designed to work only on searches. There is support for add, modify delete and rename operations with addtional inserts, which will be covered below. The joiner configuration is fairly straight forward. There are a few of concepts that need to be understood:
-
Namepsace Joining - The joiner doesn’t join configuration namespaces (such as DBEmployees and LDAPEmployees in the above configuration) but instead joins directory namespaces (ie o=ldap, o=db). This means that you could join a single database to multiple directories with a single joiner if they were all in the same namepsace (ie join o=db with ou=people which may include a directory at ou=ineternal,ou=people and another directory at ou=external,ou=people).
-
Primary Namspace - The primary namespace drives the structure of the directory. This means that if an entry exists in the primary namespace but not in the joined namespace the entry will still be returned. If the opposite is true, an entry exists in the joined namespace but not the primary namespace the entry won’t be returned to the client. For instance lets say the primary namespace is ou=people and the joined namespace is o=db the entry ou=internal,ou=people would be returned to the client but the entry ou=people,o=db would not (assuming the join filter is on the uid attribute).
-
Joined Namespace - The joined namespace indicates which directory namspace to join the primary namspace too. Entries in the joined namespace are used only for adding additional attributes to entries in the primary namespace. The structure of the joined namespace is ignored.
-
Join Filter - Entries between the two namespaces are joined based on a join filter. This filter is used to find the joined entry of an entry returned by a search. It is the same as a regular LDAP filter, but instead of specifying a value for an attribute, you can spcify ATTRIB.attribute to indicate that the value should be replaced for the join. For instance if joining with the filter "(&(uid=ATTRIB.uid)(|(objectClass=dbPerson)(objectClass=inetOrgPerson))" and the following entry were returned:
# bdund003, ldap dn: uid=bdund003,o=ldap userPassword:: c2VjcmV0 uid: bdund003 sn: Dund cn: Briana Dund objectClass: inetOrgPerson
The virtual directory server would then perform a search on o=db with the filter "(&(uid=bdund003)(|(objectClass=dbPerson)(objectClass=inetOrgPerson))", replacing the attribute ATTRIB.uid with the uid from the returned entry.
Now that the basic concepts of joining have been covered, lets configure a joiner. The below configuration joins our enterprise directory and application database:
#Listen on port 389
server.listener.port=10389
#Listen on 636 using SSL
#server.secure.listener.port=636
#server.secure.keystore=/var/keystores/myvd.ks
#server.secure.keypass=secret
#Configure global chains
server.globalChain=LogAllTransactions
server.globalChain.LogAllTransactions.className=net.sourceforge.myvd.inserts.DumpTransaction
server.globalChain.LogAllTransactions.config.logLevel=info
server.globalChain.LogAllTransactions.config.label=Global
#Configure namespaces
server.nameSpaces=Root,DBEmployees,LDAPEmployees,Joiner
#Define RootDSE
server.Root.chain=RootDSE
server.Root.nameSpace=
server.Root.weight=0
server.Root.RootDSE.className=net.sourceforge.myvd.inserts.RootDSE
server.Root.RootDSE.config.namingContexts=o=db|o=ldap|ou=people,dc=domain,dc=com
#Application specific database
server.DBEmployees.chain=DB
server.DBEmployees.nameSpace=o=db
server.DBEmployees.weight=0
server.DBEmployees.DB.className=net.sourceforge.myvd.inserts.jdbc.JdbcInsert
server.DBEmployees.DB.config.driver=com.mysql.jdbc.Driver
server.DBEmployees.DB.config.url=jdbc:mysql://127.0.0.1/myvdjoin
server.DBEmployees.DB.config.user=trenduser
server.DBEmployees.DB.config.password=secret
server.DBEmployees.DB.config.rdn=uid
server.DBEmployees.DB.config.mapping=uid=username,l=location,appAttrib1=appAttrib1,appAttrib2=appAttrib2
server.DBEmployees.DB.config.objectClass=dbPerson
server.DBEmployees.DB.config.sql=SELECT username,location,appAttrib1,appAttrib2 FROM userdata
server.DBEmployees.DB.config.useSimple=true
#Enterprise directory
server.LDAPEmployees.chain=LDAP
server.LDAPEmployees.nameSpace=o=ldap
server.LDAPEmployees.weight=0
server.LDAPEmployees.LDAP.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.LDAPEmployees.LDAP.config.host=127.0.0.1
server.LDAPEmployees.LDAP.config.port=389
server.LDAPEmployees.LDAP.config.remoteBase=ou=users,dc=domain,dc=com
#Join the database and directory on the UID attribute
server.Joiner.chain=joiner
server.Joiner.nameSpace=ou=people,dc=domain,dc=com
server.Joiner.weight=0
server.Joiner.joiner.className=net.sourceforge.myvd.inserts.join.Joiner
server.Joiner.joiner.config.primaryNamespace=o=ldap
server.Joiner.joiner.config.joinedNamespace=o=db
server.Joiner.joiner.config.joinedAttributes=uid,l,appattrib1,appattrib2
server.Joiner.joiner.config.joinFilter=(uid=ATTR.uid)
Now when we perform the search "(objectClass=*)" on the base "ou=people,dc=domain,dc=com" the following results are returned:
# people, domain.com
dn: ou=people,dc=domain,dc=com
ou: users
objectClass: organizationalUnit
# jsmith000, people, domain.com
dn: uid=jsmith000,ou=people,dc=domain,dc=com
primaryDN: uid=jsmith000,o=ldap
joinedDNs: uid=jsmith000,o=db
userPassword:: c2VjcmV0
uid: jsmith000
l: Boston
appattrib2: doo-dads
appattrib1: widgets
sn: Smith
cn: Joe Smith
objectClass: inetOrgPerson
objectClass: dbPerson
# jsmith002, people, domain.com
dn: uid=jsmith002,ou=people,dc=domain,dc=com
primaryDN: uid=jsmith002,o=ldap
joinedDNs: uid=jsmith002,o=db
userPassword:: c2VjcmV0
uid: jsmith002
l: Boston
appattrib2: doo-dads
appattrib1: wing-dings
sn: Smith
cn: Jen Smith
objectClass: inetOrgPerson
objectClass: dbPerson
# bdund003, people, domain.com
dn: uid=bdund003,ou=people,dc=domain,dc=com
primaryDN: uid=bdund003,o=ldap
joinedDNs: uid=bdund003,o=db
userPassword:: c2VjcmV0
uid: bdund003
l: LA
appattrib2: thingamabobers
appattrib1: wing-dings
sn: Dund
cn: Briana Dund
objectClass: inetOrgPerson
objectClass: dbPerson
# rsandburg015, people, domain.com
dn: uid=rsandburg015,ou=people,dc=domain,dc=com
primaryDN: uid=rsandburg015,o=ldap
joinedDNs: uid=rsandburg015,o=db
userPassword:: c2VjcmV0
uid: rsandburg015
l: Chicago
appattrib2: thingamabobers
appattrib1: widgets
sn: Sandburg
cn: Robert Sandburg
objectClass: inetOrgPerson
objectClass: dbPerson
Notice that all of the users have attributes from both the enterprise directory and the application specific database. In addion there are two additional attributes:
-
primaryDN - The DN from the primary namespace
-
joinedDNs - The DN(s) from the joined namepsaces
These attributes may be useful for tracking how a user is brought together.
Thats it! A simplistic, yet very useful, join has been created to support a specific application without adding attributes to the enterprise directory or creating a synchronized copy.
Updating, Adding and Deleting Joined Entries
As stated above the joiner is built to only directly support searches. This is because modifications may follow seperate rules and adds & deletes require knowing and understanding namespaces involved in a join. There are several two ways to perform writes to a joined namespace:
-
If the joined namespace has a flat structure, a pre-built insert may be used
-
If the joined naemspace has a deep structure a custom insert may be built
We will configure our virtual directory to be able to update both the enterprise and application specific directory. In order to support adds, modifies, deletes and renames we will configure several additional inserts:
-
JoinAddFlatNS - Support the add and delete operations
The join add flat namespace insert is designed to allow the addition of entries to a joiner namespace. NOTE: this insert MUST be configured as the last insert on a chain.
Class Name
net.sourceforge.myvd.inserts.join.JonAddFlatNS
Scope
Add
Configuration Options
joinerName
The name of the joiner insert configured on this chain
joinedObjectClass
The objectclass for joined entries
sharedAttributes
Comma seperated list of attributes shared by both the primary and joined namespaces
-
SimpleJoinModify - Supports the modification of joined entries
The simple join modify insert is configured AFTER a joiner on a chain in order to support the modification of joined attributes
Class Name
net.sourceforge.myvd.inserts.join.SimpleJoinModify
Scope
Modify
Configuration Options
joinerName
The name of the joiner insert configured on this chain
-
DBTableUpdate - Supports the modification of the database table
This insert contains the logic to update a single database table. It is configured BEHIND a database insert in order to utilize that insert’s connection pool and mapping.
Class Name
net.sourceforge.myvd.inserts.jdbc.DBTableUpdate
Scope
Add,Modify,Delete,Rename
Configuration Options
tableName
The name of the table to update in the database
dbInsertName
The name of the insert used by this insert
The above inserts all have a few things in common. They are all configured AFTER their main insert and they relly on their main insert’s configuration. That is to say that the JoinAddFlatNS and SimpleJoinModify inserts are configured after the joiner insert and the DBTableUpdate insert is configured after the application database insert. In addition, they are configured to "know" about their main insert to eliminate double configurations. The below configuration will allow for the creation and modification of entries of the joiner we previously configured:
#Listen on port 389
server.listener.port=10389
#Listen on 636 using SSL
#server.secure.listener.port=636
#server.secure.keystore=/var/keystores/myvd.ks
#server.secure.keypass=secret
#Configure global chains
server.globalChain=LogAllTransactions
server.globalChain.LogAllTransactions.className=net.sourceforge.myvd.inserts.DumpTransaction
server.globalChain.LogAllTransactions.config.logLevel=info
server.globalChain.LogAllTransactions.config.label=Global
#Configure namespaces
server.nameSpaces=Root,DBEmployees,LDAPEmployees,Joiner
#Define RootDSE
server.Root.chain=RootDSE
server.Root.nameSpace=
server.Root.weight=0
server.Root.RootDSE.className=net.sourceforge.myvd.inserts.RootDSE
server.Root.RootDSE.config.namingContexts=o=db|o=ldap|ou=people,dc=domain,dc=com
server.DBEmployees.chain=DB,dbupdate
server.DBEmployees.nameSpace=o=db
server.DBEmployees.weight=0
server.DBEmployees.DB.className=net.sourceforge.myvd.inserts.jdbc.JdbcInsert
server.DBEmployees.DB.config.driver=com.mysql.jdbc.Driver
server.DBEmployees.DB.config.url=jdbc:mysql://127.0.0.1/myvdjoin
server.DBEmployees.DB.config.user=trenduser
server.DBEmployees.DB.config.password=secret
server.DBEmployees.DB.config.rdn=uid
server.DBEmployees.DB.config.mapping=uid=username,l=location,appAttrib1=appAttrib1,appAttrib2=appAttrib2
server.DBEmployees.DB.config.objectClass=dbPerson
server.DBEmployees.DB.config.sql=SELECT username,location,appAttrib1,appAttrib2 FROM userdata
server.DBEmployees.DB.config.useSimple=true
#The database update insert
server.DBEmployees.dbupdate.className=net.sourceforge.myvd.inserts.jdbc.DBTableUpdate
server.DBEmployees.dbupdate.config.tableName=userdata
server.DBEmployees.dbupdate.config.dbInsertName=DB
server.LDAPEmployees.chain=LDAP
server.LDAPEmployees.nameSpace=o=ldap
server.LDAPEmployees.weight=0
server.LDAPEmployees.LDAP.className=net.sourceforge.myvd.inserts.ldap.LDAPInterceptor
server.LDAPEmployees.LDAP.config.host=127.0.0.1
server.LDAPEmployees.LDAP.config.port=389
server.LDAPEmployees.LDAP.config.remoteBase=ou=users,dc=domain,dc=com
server.LDAPEmployees.LDAP.config.proxyDN=cn=admin,dc=domain,dc=com
server.LDAPEmployees.LDAP.config.proxyPass=secret
#The Joiner
server.Joiner.chain=joiner,joinmod,joinadd
server.Joiner.nameSpace=ou=people,dc=domain,dc=com
server.Joiner.weight=0
server.Joiner.joiner.className=net.sourceforge.myvd.inserts.join.Joiner
server.Joiner.joiner.config.primaryNamespace=o=ldap
server.Joiner.joiner.config.joinedNamespace=o=db
server.Joiner.joiner.config.joinedAttributes=uid,l,appattrib1,appattrib2
server.Joiner.joiner.config.joinFilter=(uid=ATTR.uid)
#The Join modification insert
server.Joiner.joinmod.className=net.sourceforge.myvd.inserts.join.SimpleJoinModify
server.Joiner.joinmod.config.joinerName=joiner
#The join add insert
server.Joiner.joinadd.className=net.sourceforge.myvd.inserts.join.JoinAddFlatNS
server.Joiner.joinadd.config.joinerName=joiner
server.Joiner.joinadd.config.joinedObjectClass=dbPerson
server.Joiner.joinadd.config.sharedAttributes=uid
Now you can create users and modify them seamlessly through your virtual directory.