version: 1
dn: ou=JenkinsAdmins,ou=groups,dc=otm,dc=intra
objectclass: groupOfNames
objectclass: top
cn: JenkinsAdmins
member: cn=admin,ou=users,dc=otm,dc=intra
member: cn=alexander.friesen,ou=users,dc=otm,dc=intra
member: cn=boris.jelzin,ou=users,dc=otm,dc=intra
ou: JenkinsAdmins
dn: cn=alexander.friesen,ou=users,dc=otm,dc=intra
objectClass: person
objectClass: top
cn: alexander.friesen
sn: alexander.friesen
userPassword:: e1NIQX13dVFVZXE0NjJuODJnbWJRYzZ6VXFSV01XZlU9
dn: ou=users,dc=otm,dc=intra
objectclass: organizationalUnit
objectclass: top
ou: users
dn: cn=stas.archontov,ou=users,dc=otm,dc=intra
objectclass: person
objectclass: top
cn: stas.archotov
cn: stas.archontov
sn: stas.archontov
userPassword:: e1NIQX13dVFVZXE0NjJuODJnbWJRYzZ6VXFSV01XZlU9
dn: cn=boris.jelzin,ou=users,dc=otm,dc=intra
objectclass: person
objectclass: top
cn: boris.jelzin
sn: boris.jelzin
userPassword:: e1NIQX13dVFVZXE0NjJuODJnbWJRYzZ6VXFSV01XZlU9
dn: ou=JenkinsHotSyncer,ou=groups,dc=otm,dc=intra
objectclass: groupOfNames
objectclass: top
cn: JenkinsHotSyncer
member: cn=admin,ou=users,dc=otm,dc=intra
member: cn=stas.archontov,ou=users,dc=otm,dc=intra
ou: JenkinsHotSyncer
dn: ou=JenkinsSyncer,ou=groups,dc=otm,dc=intra
objectclass: groupOfNames
objectclass: top
cn: JenkinsSyncer
member: cn=admin,ou=users,dc=otm,dc=intra
member: cn=stas.archontov,ou=users,dc=otm,dc=intra
ou: JenkinsSyncer
dn: cn=admin,ou=users,dc=otm,dc=intra
objectClass: person
objectClass: top
cn: admin
sn: admin
userPassword:: e1NIQX13dVFVZXE0NjJuODJnbWJRYzZ6VXFSV01XZlU9
dn: dc=otm,dc=intra
objectclass: top
objectclass: domain
dc: otm
dn: ou=groups,dc=otm,dc=intra
objectclass: organizationalUnit
objectclass: top
ou: groups
Configure Jenkins to use your LDAP server
{{http://i520.photobucket.com/albums/w327/schajtan/2017-04-13_17-47-45_zpsqiq2rxwr.png}}
===== Privilidges via ACLs - Access Control Lists =====
[[https://youtu.be/gNc3NcHOGC8?t=6m56s]]
===== Beispiel =====
=== Attributes ===
uid User id
cn Common Name
sn Surname
l Location
ou Organisational Unit
o Organisation
dc Domain Component
st State
c Country
dn distinguished name, like cn=sadique5,ou=people,dc=ldap,dc=example,dc=com
=== Search Scope ===
3 types of scope:
base limits to just the base object
onelevel limits to just the immediate children
sub search the entire subtree from base down
=== Filling LDAP manually without file from script ===
ldapadd -H "ldap://localhost" -c -x -D "cn=admin,dc=ldap,dc=example,dc=com" -w "Jpk66g63ZifGYIcShSGM" << EOF
dn: cn=sadique5,ou=people,dc=ldap,dc=example,dc=com
cn: sadique5
sn: sadique
uid: sadique
displayName: Sadique Puthen Peedikayil
givenName: Sadique
mail: sadique@vanillanetworks.com
mobile: 9895643639
homePhone: 0466-2254274
objectClass: inetOrgPerson
userPassword: Jpk66g63ZifGYIcShSGM
EOF
===== OpenLDAP and phpldapadmin =====
Starting both in docker:
docker network create ldapnetwork
sudo docker run --restart=always -td --net ldapnetwork -h "opendj" --env ROOT_USER_DN="cn=Directory Manager" --env OPENDJ_USER="opendj" --env BASE_DN="dc=project,dc=intra" --env ROOT_PASSWORD="123abc" -p 1389:1389 -p 1636:1636 -p 3000:4444 --name opendj openidentityplatform/opendj
sudo docker run --restart=always --env PHPLDAPADMIN_LDAP_HOSTS="#PYTHON2BASH:[{'opendj': [{'server': [{'tls': False}, {'port': 1389}]}, {'login': [{'bind_id': 'cn=Directory Manager'}, {'bind_pass': '123abc'}]}]}]" --net ldapnetwork -p 4200:80 --env PHPLDAPADMIN_HTTPS=false --detach --name php osixia/phpldapadmin:0.7.2 --loglevel debug
Navigate to http://localhost:4200
Login with
Login DN:
cn=Directory Manager
Pass:
123abc
== display the configs ==
slapcat -b cn=config
Or within contianer
docker exec ldap slapcat -b cn=config
===== LDAP with Spring =====
Spring LDAP reference: https://docs.spring.io/autorepo/docs/spring-ldap/current/reference/#dns-as-attribute-values
Examples:
* https://github.com/jopek/spring-boot-ldap-useradmin
* https://github.com/ivangfr/springboot-ldap
===== Schema extension =====
This is an example for an extended schema
stored as local.schema
# local.schema -- aaainetOrgPerson
# $OpenLDAP$
## This work is part of OpenLDAP Software .
##
## Copyright 1998-2014 The OpenLDAP Foundation.
## All rights reserved.
##
## Redistribution and use in source and binary forms, with or without
## modification, are permitted only as authorized by the OpenLDAP
## Public License.
##
## A copy of this license is available in the file LICENSE in the
## top-level directory of the distribution or, alternatively, at
## .
#
# aaainetOrgPerson
#
# Defines additional attributes required by the SSP
# TODO: request and add unique Private Enterprise Number from IANA Services
#
attributetype ( 5.12.345.1.123456.3.1.321
NAME 'x-company-auth0userId'
DESC 'the id identifying ofthe auth0 user'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
attributetype ( 5.12.345.1.123456.3.1.322
NAME 'x-company-auth0clientId'
DESC 'the clientId of the machine to machine user'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
attributetype ( 5.12.345.1.123456.3.1.323
NAME 'x-company-tags'
DESC 'the tags field containing all the tags without empty spaces'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
attributetype ( 5.12.345.1.123456.3.1.324
NAME 'x-company-b360id'
DESC 'the building 360 identifier of a customer'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
objectclass ( 5.12.345.1.123456.3.1.2
NAME 'x-company-auth0subject'
DESC 'auth0 service registered person or machine'
AUXILIARY
MAY ( x-company-auth0userId $ x-company-auth0clientId ) )
objectclass ( 5.12.345.1.123456.3.1.3
NAME 'x-company-hastags'
DESC 'an object which has a tag'
AUXILIARY
MAY ( x-company-tags ) )
objectclass ( 5.12.345.1.123456.3.1.4
NAME 'x-company-b360customer'
DESC 'b360 customer'
AUXILIARY
MAY ( x-company-b360id ) )
Here is how to build an OpenLdap container with extended schema
FROM osixia/openldap:1.2.4
RUN mkdir -p /container/service/slapd/assets/config/bootstrap/ldif/custom
COPY ./ldif/ /container/service/slapd/assets/config/bootstrap/ldif/custom
RUN mkdir -p /container/service/slapd/assets/config/bootstrap/schema
COPY ./schema/local.schema /container/service/slapd/assets/config/bootstrap/schema/
COPY ./containerscripts/start.sh /usr/bin/start.sh
ENTRYPOINT start.sh && /container/tool/run
===== Pooling with Spring Boot =====
The following code can be used, to activate Pooling (https://docs.spring.io/spring-ldap/docs/1.3.2.RELEASE/reference/html/pooling.html) in a
- SpringBoot application
- using Spring Data to talk to the Ldap server
The important part missing up there - was to pass the `PoolingContextSource` to the `LdapTemplate`
see `new LdapTemplate(poolingLdapContextSource());` in
@PropertySource("ldap-${spring.profiles.active}.properties")
@Configuration
public class LdapConfiguration {
private static Logger LOG = LoggerFactory.getLogger(LdapConfiguration.class);
@Autowired
private Environment env;
@Bean
public LdapContextSource contextSource() {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(env.getRequiredProperty("ldap.url"));
contextSource.setBase(env.getRequiredProperty("ldap.base"));
contextSource.setUserDn(env.getRequiredProperty("ldap.user"));
contextSource.setPassword(env.getRequiredProperty("ldap.password"));
contextSource.setPooled(false); // pooling is enabled on the wrapper
Map environment = new HashMap<>();
// DEBUGGING
environment.put("com.sun.jndi.ldap.connect.pool.debug", "fine"); // many debug infos
contextSource.setBaseEnvironmentProperties(environment);
return contextSource;
}
/**
* Configure the LDAP connection pool to
* validate connections https://docs.spring.io/spring-ldap/docs/1.3.2.RELEASE/reference/html/pooling.html
*
* so that the connections do not expire in pool
*
* @return
*/
@Bean
public ContextSource poolingLdapContextSource() {
PoolingContextSource poolingContextSource = new PoolingContextSource();
poolingContextSource.setDirContextValidator(new DefaultDirContextValidator());
poolingContextSource.setContextSource(contextSource());
poolingContextSource.setTestOnBorrow(true);
poolingContextSource.setTestWhileIdle(true);
poolingContextSource.setTimeBetweenEvictionRunsMillis(60000);
poolingContextSource.setNumTestsPerEvictionRun(3);
TransactionAwareContextSourceProxy proxy = new TransactionAwareContextSourceProxy(poolingContextSource);
return proxy;
}
@Bean
public LdapTemplate ldapTemplate() {
LdapTemplate ldapTemplate = new LdapTemplate(poolingLdapContextSource());
ldapTemplate.setIgnoreNameNotFoundException(true);
return ldapTemplate;
}
}
===== BaseClass for Spring Data =====
* The length of the original - must be larger, than that of the snippet
*
* @param original - the name where to replace a piece by a snippet
* @param origIndexStart - the index where to start replacement
* @param replacementSnippet - the snippet to insert into the original
* @return
*/
private Name replaceBySnippetInName(LdapName original, int origIndexStart, LdapName replacementSnippet) {
Assert.isTrue(original != null, "Name must be not null");
Assert.isTrue(replacementSnippet != null, "Name must be not null");
Assert.isTrue(original.size() >= replacementSnippet.size(), "Name must be not null");
if (!replacementSnippet.isEmpty()) {
final List
* In "update" setting an argument to null is for removing it.
* In "bind" setting an argument to null causes a org.springframework.ldap.InvalidAttributeValueException
*
* @param buildAttributes
* @return
*/
protected Attributes filterNullOrEmptyAttributes(Attributes buildAttributes) {
if (!(buildAttributes instanceof BasicAttributes)){
return buildAttributes;
}
final BasicAttributes result = (BasicAttributes) buildAttributes.clone();
final Iterator extends Attribute> attributesIterator = buildAttributes.getAll().asIterator();
while (attributesIterator.hasNext()) {
final Attribute attribute = attributesIterator.next();
boolean invalidAttribute = true;
try {
invalidAttribute = (attribute.get() == null); // not null
} catch (java.util.NoSuchElementException e) {
invalidAttribute = false; // invalid, because the value was empty
} catch (NamingException e) {
// rethrow, because the error was in the wrong name of the attribure - user should know
throw new RuntimeException(e);
}
if (invalidAttribute) {
result.remove(attribute.getID());
}
}
return result;
}
/**
* Apply the attributes in the new object - to the id.
* Here you get the chance to adopt the LDAP id - if the properties have changed.
*
* @param oldId
* @param objectWithId
* @return
*/
protected abstract Name applyAttributesToId(Name oldId, T objectWithId);
/**
* The object must exist already.
* Save, when the id has changed.
* May fall back to the creation of a new one, if no explicit id is provided.
* Will clean up the old object, if the
*
* @param originalId the existing id of an existing role.
* @param modified the role, populated with new data. May contain a new id.
* @return the updated entry
*/
public final T update(final Name originalId, T modified) {
final Name newId = modified.getId();
Assert.isTrue(newId != null, String.format("The new object %s is expected to have a new id in it.", modified));
if (originalId != null && !originalId.equals(newId)) {
Assert.isTrue(exists(originalId), String.format("The object with id %s is expected to exist already, as this method sujest renaming of ids.", originalId));
// renaming the objects id
rename(originalId, newId);
// now update the membership
updateMembershipInLdapObjectsOnIdChange(tools.toAbsoluteDn(originalId), tools.toAbsoluteDn(newId));
}
// persist
update(modified);
return modified;
}
public T rename(Name oldId, Name newId) {
Assert.isTrue(newId != null, "Can not rename to null");
Assert.isTrue(exists(oldId), String.format("The object with the id %s does not exist. Cant rename it.", oldId));
if (!newId.equals(oldId)) {
ldapTemplate.rename(oldId, newId);
}
return findByDn(newId).orElseThrow();
}
/**
* The object must exist already.
*
* @param t
* @return
*/
protected T update(T t) {
final Name anyDn = t.getId();
final LdapName absLdapId = tools.toRelativeDn(anyDn);
final Iterator extends Attribute> attributesIterator = buildAttributes(t).getAll().asIterator();
while (attributesIterator.hasNext()) {
final Attribute attribute = attributesIterator.next();
ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute);
ldapTemplate.modifyAttributes(absLdapId, new ModificationItem[]{item});
}
return t;
}
protected void initNewObjectBeforeSaving(T objectWithId) {
// nothing on default
}
/**
* Generates a new id.
*
* @param objectWithId - the objectWithId used as data container. Probably will not have an id yet but all the information to generate one.
* @return
*/
public abstract Name generateId(T objectWithId);
/**
* Update the membership in the ldap objects on id change.
* Implementation depends on the type of the objects.
* Users will have to update membership in Groups, roles.
* Groups will have to update membership in roles only etc.
*
* @param oldMemberAbsDn
* @param newMemberAbsDn
*/
protected abstract void updateMembershipInLdapObjectsOnIdChange(LdapName oldMemberAbsDn, LdapName newMemberAbsDn);
/**
* Validate a modified object here. E.g. when it is updated.
* Especially check, if the cn/ou or other field participating in the id - must be aligned with the current state of the object
*
* @param modifiedObject
* @throws IllegalArgumentException - when the validation fails. User the Asser.* style for the checks and catch them in the RestController
*/
protected void validate(T modifiedObject) throws IllegalArgumentException {
// no default validation
}
}
* See LdapEntryIdentification.class comment
*
* @param ldapIds
* @return
*/
public List