Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dry-run #131

Merged
merged 18 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <eau@phear.org>
#
# 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
Loading