Skip to content

Commit

Permalink
Merge pull request #131 from SURFscz/add-dry-run
Browse files Browse the repository at this point in the history
Add dry-run
  • Loading branch information
baszoetekouw authored May 27, 2024
2 parents 5a521db + de5fbcc commit e6a840e
Show file tree
Hide file tree
Showing 17 changed files with 478 additions and 1 deletion.
3 changes: 3 additions & 0 deletions dry_run/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
backup.ldif*
result.ldif*
sync.json*
22 changes: 22 additions & 0 deletions dry_run/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---

services:
ldap:
image: ghcr.io/surfscz/sram-ldap:main
ports:
- 1389:1389
environment:
LDAP_ROOT: "${BASEDN}"
LDAP_ADMIN_USERNAME: "admin"
LDAP_ADMIN_PASSWORD: "changethispassword"
LDAP_CONFIG_ADMIN_USERNAME: "admin"
LDAP_CONFIG_ADMIN_PASSWORD: "changethispassword"
LDAP_CONFIG_ADMIN_ENABLED: "yes"
LDAP_CUSTOM_SCHEMA_DIR: "/opt/ldap/schema"
LDAP_SKIP_DEFAULT_TREE: "yes"
LDAP_ENABLE_TLS: "no"
LDAP_ENABLE_SYNCPROV: "yes"
volumes:
- ./schema:/opt/ldap/schema
- ./ldif:/opt/ldap/ldif
- ./backup.ldif:/backup.ldif:ro
126 changes: 126 additions & 0 deletions dry_run/dry-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/bin/bash

set -e
shopt -s extglob # for the string postfix matching below

cleanup() {
[ -n "$COMPOSE" ] && ${COMPOSE} rm --force --stop >/dev/null 2>&1 || true
[ -n "$SOCAT_PID" ] && kill "$SOCAT_PID" || true
[ -n "$TMPFILE" ] && rm -f "${TMPFILE}" || true
}
trap cleanup EXIT

# check if data file are present
if [ ! -f "backup.ldif" ] || [ ! -f "sync.json" ]; then
echo "Data files backup.ldif and/or sync.json not found"
echo "Copy ldap backup (slapcat -n1 output) to backup.ldif"
echo "Copy SBS plsc sync output to sync.json"
exit 1
fi

GREEN="\033[0;32m"
NORMAL="\033[0m"

# check if we're using a remote docker host
# in that case, we need to forward the local port 1389 to the real docker host
# because all scripts depend on the ldap being available locally
docker_host=$(docker context inspect -f '{{ .Endpoints.docker.Host }}')
docker_proto=${docker_host:0:6}
if [ "$docker_proto" == "tcp://" ]; then
# remove protocol
HOST=${docker_host:6}
# remove port number
HOST=${HOST%:+([[:digit:]])?(/)}

echo "Using remote docker host $HOST ($docker_host)"
socat "TCP4-LISTEN:1389,fork,reuseaddr" "TCP4:${HOST}:1389" 2>/dev/null &
SOCAT_PID=$!
fi


# find basedn
BASEDN=$( awk '/^dn: / { print $2; exit }' backup.ldif )
export BASEDN
echo "Found basedn '$BASEDN'"

COMPOSE_FILE="docker-compose.yml"
COMPOSE="docker compose --file ${COMPOSE_FILE}"

echo -n "Starting containers..."
${COMPOSE} rm --force --stop >/dev/null 2>&1 || true
${COMPOSE} up --detach >/dev/null 2>&1
echo

echo -n "Waiting for ldap to start"
while sleep 0.2
do
echo -n "."
if docker compose logs | grep -q '\*\* Starting slapd \*\*'
then
echo " Up!"
break
fi
done

echo "Configuring LDAP"
${COMPOSE} exec ldap ldapmodify -H ldap://localhost:1389/ -D cn=admin,cn=config -w changethispassword -f /opt/ldap/ldif/config_1.ldif > /dev/null 2>&1
${COMPOSE} exec ldap ldapadd -H ldap://localhost:1389/ -D cn=admin,cn=config -w changethispassword -f /opt/ldap/ldif/config_2.ldif > /dev/null 2>&1

echo "Loading data"
${COMPOSE} exec ldap slapadd -F /opt/bitnami/openldap/etc/slapd.d/ -n 2 -l /backup.ldif > /dev/null 2>&1

# generate plsc config
echo "Generating plsc config"
TMPFILE=$(mktemp -t plsc_XXXXXX.yml)
cat <<EOF | sed 's/^ //' > "${TMPFILE}"
---
ldap:
src:
uri: "ldap://localhost:1389/"
basedn: "${BASEDN}"
binddn: "cn=admin,${BASEDN}"
passwd: "changethispassword"
sizelimit: 5
dst:
uri: "ldap://localhost:1389/"
basedn: "${BASEDN}"
binddn: "cn=admin,${BASEDN}"
passwd: "changethispassword"
sizelimit: 5
sbs:
src:
host: "test"
sync: "dry_run/sync.json"
pwd: '{CRYPT}!'
uid: 1000
gid: 1000
EOF

# install venv
if ! test -d '../venv'
then
echo -n "Installing venv..."
python3 -mvenv ../venv
../venv/bin/pip install -q --upgrade pip wheel setuptools
../venv/bin/pip install -q -r ../requirements.txt
echo
fi


#export LOGLEVEL=DEBUG
echo "Running plsc"
(
cd ..
export PATH="$(pwd)/venv/bin:${PATH}"
./run.sh "${TMPFILE}"
)

echo Dumping result
docker-compose -f docker-compose.yml exec -ti ldap slapcat -F /opt/bitnami/openldap/etc/slapd.d/ -o ldif-wrap=no -n2 > result.ldif 2>/dev/null

echo Comparing result
../venv/bin/python ./ldifparser.py < backup.ldif > backup.ldif.parsed
../venv/bin/python ./ldifparser.py < result.ldif > result.ldif.parsed
diff --unified --text --color=always backup.ldif.parsed result.ldif.parsed && echo -e "${GREEN}No changes detected!${NORMAAL}"

exit 0
Empty file added dry_run/ldif/KEEP
Empty file.
32 changes: 32 additions & 0 deletions dry_run/ldif/config_1.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
dn: cn=config
changetype: Modify
add: olcAttributeOptions
olcAttributeOptions: time-

dn: cn=module{1},cn=config
changetype: Modify
add: olcModuleLoad
olcModuleLoad: {1}dynlist.so

dn: olcDatabase={2}mdb,cn=config
changetype: Modify
replace: olcDbIndex
olcDbIndex: objectClass eq,pres
olcDbIndex: ou,cn,mail,surname,givenname eq,pres,sub
olcDbIndex: entryUUID eq
olcDbIndex: o eq
olcDbIndex: dc eq
olcDbIndex: entryCSN eq

replace: olcDbMaxSize
olcDbMaxSize: 1073741824

replace: olcAccess
olcAccess: {0}to dn.regex="(([^,]+),dc=services,dc=vnet)$" by dn.exact="cn=adm
in,dc=services,dc=vnet" write by dn.exact=gidNumber=0+uidNumber=0,cn=peercred
,cn=external,cn=auth write by dn.exact,expand="cn=admin,$1" read by * break
olcAccess: {1}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external
,cn=auth manage by dn.regex="cn=[^,]+,dc=services,dc=vnet" read by dn.exact=
gidNumber=1000+uidNumber=1000,cn=peercred,cn=external,cn=auth manage by * br
eak
olcAccess: {2}to attrs=userPassword by self write by anonymous auth by * break
5 changes: 5 additions & 0 deletions dry_run/ldif/config_2.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dn: olcOverlay={1}dynlist,olcDatabase={2}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcDynListConfig
olcOverlay: {1}dynlist
olcDynListAttrSet: {0}voPerson labeledURI member+memberOf@groupOfMembers
41 changes: 41 additions & 0 deletions dry_run/ldifparser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
import sys
import ldif
from collections import OrderedDict


def kcmp(item):
(key, v) = item
parts = key.split(',')[::-1]
new_key = ','.join(parts)
return (new_key, v)


def freeze(o):
if isinstance(o, dict):
return OrderedDict({k: freeze(v) for k, v in sorted(o.items(), key=kcmp)}.items())
if isinstance(o, list):
return sorted([freeze(v) for v in o])
return o.decode('utf-8')


def my_print(o, depth):
if isinstance(o, OrderedDict):
for k, v in o.items():
my_print(k, depth)
my_print(v, depth + 2)
elif isinstance(o, list):
for v in o:
my_print(v, depth)
else:
print(f"{' ' * depth}{o}")


ldifparser = ldif.LDIFRecordList(sys.stdin)
ldifparser.parse()

data = {k: v for k, v in ldifparser.all_records}
f = freeze(data)

# print(json.dumps(f, indent=2))
my_print(f, 0)
27 changes: 27 additions & 0 deletions dry_run/schema/eduMember.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
dn: cn=eduMember,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: eduMember
# Internet X.500 Schema for Ldappc
# Includes the eduMember ObjectClass schema
#
#
# An auxiliary object class, "eduMember," is a convenient container
# for an extensible set of attributes concerning group memberships.
# At this time, the only attributes specified as belonging to the
# object class are "isMemberOf" and "hasMember."
#
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.5.1.1
NAME 'isMemberOf'
DESC 'identifiers for groups to which containing entity belongs'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.5.1.2
NAME 'hasMember'
DESC 'identifiers for entities that are members of the group'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcObjectClasses: ( 1.3.6.1.4.1.5923.1.5.2.1
NAME 'eduMember'
AUXILIARY
MAY ( isMemberOf $ hasMember )
)
83 changes: 83 additions & 0 deletions dry_run/schema/eduPerson.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
dn: cn=eduperson,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: eduperson
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.1
NAME 'eduPersonAffiliation'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.7
NAME 'eduPersonEntitlement'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.2
NAME 'eduPersonNickName'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.3
NAME 'eduPersonOrgDN'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY distinguishedNameMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.4
NAME 'eduPersonOrgUnitDN'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY distinguishedNameMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.5
NAME 'eduPersonPrimaryAffiliation'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.8
NAME 'eduPersonPrimaryOrgUnitDN'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY distinguishedNameMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.6
NAME 'eduPersonPrincipalName'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.12
NAME 'eduPersonPrincipalNamePrior'
DESC 'eduPersonPrincipalNamePrior per Internet2'
EQUALITY caseIgnoreMatch
SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.9
NAME 'eduPersonScopedAffiliation'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.10
NAME 'eduPersonTargetedID'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.11
NAME 'eduPersonAssurance'
DESC 'eduPerson per Internet2 and EDUCAUSE'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.13
NAME 'eduPersonUniqueId'
DESC 'eduPersonUniqueId per Internet2'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.5923.1.1.1.16
NAME 'eduPersonOrcid'
DESC 'ORCID researcher identifiers belonging to the principal'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcObjectClasses: ( 1.3.6.1.4.1.5923.1.1.2
NAME 'eduPerson'
AUXILIARY
MAY (
eduPersonAffiliation $ eduPersonNickname $ eduPersonOrgDN $
eduPersonOrgUnitDN $ eduPersonPrimaryAffiliation $
eduPersonPrincipalName $ eduPersonEntitlement $ eduPersonPrimaryOrgUnitDN $
eduPersonScopedAffiliation $ eduPersonTargetedID $ eduPersonAssurance $
eduPersonPrincipalNamePrior $ eduPersonUniqueId $ eduPersonOrcid )
)
19 changes: 19 additions & 0 deletions dry_run/schema/groupOfMembers.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Internet X.500 Schema for Ldappc
# Includes the groupOfMembers ObjectClass schema
#
# Taken from RFC2307bis draft 2
# https://tools.ietf.org/html/draft-howard-rfc2307bis-02
#
# An structural object class, "groupOfMembers" is a convenient container
# for an extensible set of attributes concerning group memberships.
#
dn: cn=groupOfMembers,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: groupOfMembers
olcObjectClasses: ( 1.3.6.1.1.1.2.18 SUP top STRUCTURAL
NAME 'groupOfMembers'
DESC 'A group with members (DNs)'
MUST cn
MAY ( businessCategory $ seeAlso $ owner $ ou $ o $
description $ member )
)
21 changes: 21 additions & 0 deletions dry_run/schema/ldapPublicKey.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
dn: cn=openssh-lpk-openldap,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: openssh-lpk-openldap
#
# LDAP Public Key Patch schema for use with openssh-ldappubkey
# useful with PKA-LDAP also
#
# Author: Eric AUGE <[email protected]>
#
# Based on the proposal of : Mark Ruijter
#
# octetString SYNTAX
olcAttributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
DESC 'MANDATORY: OpenSSH Public key'
EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
# printableString SYNTAX yes|no
olcObjectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
DESC 'MANDATORY: OpenSSH LPK olcObjectClasses:'
MUST ( sshPublicKey $ uid )
)
Loading

0 comments on commit e6a840e

Please sign in to comment.