Thursday, March 3, 2022

[389-users] multi-supplier replication with certificate-based authentication

#!/bin/bash

#### install 389-ds
if ! type -p dbscan >/dev/null ; then
if [[ -f /etc/redhat-release ]]; then
! yum module list 389-ds | grep -q '389-ds.*\[e]' && \
yum module enable -y 389-ds
yum install -y 389-ds-base
elif [[ -f /etc/SUSE-brand ]]; then
zypper in -y 389-ds
else
echo "unknown distribution"
exit 1
fi
fi

#### reset
if [[ $1 = -reset ]]; then
rm -f /etc/openldap/ldap-cacert.crt
sed -i '/^BASE/d;/^URI/d;/^TLS_CACERT/d' /etc/openldap/ldap.conf
DS=$(dsctl -l)
[[ "$DS" ]] && dsctl $DS remove --do-it
echo "directory server reset completed"
exit
fi

#### preset values
H1=host1.example.com
H2=host2.example.com
DSINSTANCE=ldaptest
DBBASE='dc=example,dc=com'
DSPASSWORD=password

#### check the ip addresses and /etc/hosts
[[ $1 ]] || exec echo 'missing ip for host1'
[[ $2 ]] || exec echo 'missing ip for host2'
[[ $1 = $2 ]] && exec echo 'the two ip must be different'
ping -c 1 $1 >/dev/null || exec echo 'cannot ping host with ip: $1'
ping -c 1 $2 >/dev/null || exec echo 'cannot ping host with ip: $2'

! grep -q "^${1//./\\.}[[:blank:]]" /etc/hosts && echo -e "$1\t$H1 ${H1%%.*}" >>/etc/hosts
! grep -q "^${1//./\\.}[[:blank:]]$H1 ${H1%%.*}" /etc/hosts && \
sed -i "/^${1//./\\.}[[:blank:]]/s@.*@$1\t$H1 ${H1%%.*}@" /etc/hosts
! grep -q "^${2//./\\.}[[:blank:]]" /etc/hosts && echo -e "$2\t$H2 ${H2%%.*}" >>/etc/hosts
! grep -q "^${2//./\\.}[[:blank:]]$H2 ${H2%%.*}" /etc/hosts && \
sed -i "/^${2//./\\.}[[:blank:]]/s@.*@$2\t$H2 ${H2%%.*}@" /etc/hosts

DEV=$(ip route list scope global | awk -v v=dev '$1=="default"{for(a=2;a<NF;a++)if($a==v){print $(a+1);exit}}')
ADDR=$(ip addr show dev $DEV scope global | awk '$1=="inet"{print $2;exit}' | cut -f1 -d/)

#### set SID LOCALSUPPLIER REMOTESUPPLIER
if [[ $ADDR = $1 ]]; then
SID=1 ; LOCALSUPPLIER=$H1 ; REMOTESUPPLIER=$H2
elif [[ $ADDR = $2 ]]; then
SID=2 ; LOCALSUPPLIER=$H2 ; REMOTESUPPLIER=$H1
else
echo 'my ip is not $1 or $2, it is $ADDR'
exit 1
fi

#### create and start the instance: $DSINSTANCE
if ! dsctl -l | grep -qFx slapd-$DSINSTANCE ; then
TMP=$(mktemp -p /dev/shm)
dscreate create-template >$TMP
sed -i "/;full_machine_name/s@.*@full_machine_name = $LOCALSUPPLIER@" $TMP
sed -i "/;instance_name/s@.*@instance_name = $DSINSTANCE@" $TMP
sed -i "/;sample_entries/s@.*@sample_entries = yes@" $TMP
sed -i "/;suffix/s@.*@suffix = $DBBASE@" $TMP
#set a default password for testing
sed -i "/;root_password/s@.*@root_password = $DSPASSWORD@" $TMP
dscreate from-file $TMP
rm $TMP
if systemctl is-active -q firewalld ; then
! firewall-cmd -q --query-service ldap && firewall-cmd --add-service=ldap && firewall-cmd --runtime-to-permanent
! firewall-cmd -q --query-service ldaps && firewall-cmd --add-service=ldaps && firewall-cmd --runtime-to-permanent
fi
dsconf $DSINSTANCE config replace nsslapd-require-secure-binds=on
dsconf $DSINSTANCE config replace nsslapd-force-sasl-external=on
dsctl $DSINSTANCE restart
fi

#### copy the CA certificate in /etc/openldap/ldap-cacert.crt
#### only on host2: syncronize Self-Signed-CA with host1, regenerate Server-Cert
if [[ ! -f /etc/openldap/ldap-cacert.crt ]]; then
if ((SID==2)); then
echo " INFO: importing Self-Signed-CA from $REMOTESUPPLIER"
rsync -avWHP --delete $REMOTESUPPLIER:/etc/dirsrv/ssca /etc/dirsrv
cp /etc/dirsrv/ssca/ca.crt /etc/dirsrv/slapd-$DSINSTANCE/ca.crt

certutil -F -n Server-Cert -d /etc/dirsrv/slapd-$DSINSTANCE -f /etc/dirsrv/slapd-$DSINSTANCE/pwdfile.txt
certutil -D -d /etc/dirsrv/slapd-$DSINSTANCE -n Self-Signed-CA
dsctl $DSINSTANCE tls generate-server-cert-csr -s "CN=$LOCALSUPPLIER" -8 $LOCALSUPPLIER
certutil -C -d /etc/dirsrv/ssca -f /etc/dirsrv/ssca/pwdfile.txt -v 24 -a -i /etc/dirsrv/slapd-$DSINSTANCE/Server-Cert.csr -o /etc/dirsrv/slapd-$DSINSTANCE/Server-Cert.crt -c Self-Signed-CA
openssl rehash /etc/dirsrv/slapd-$DSINSTANCE
certutil -A -n Self-Signed-CA -t CT,, -a -i /etc/dirsrv/slapd-$DSINSTANCE/ca.crt -d /etc/dirsrv/slapd-$DSINSTANCE -f /etc/dirsrv/slapd-$DSINSTANCE/pwdfile.txt
certutil -A -n Server-Cert -t ,, -a -i /etc/dirsrv/slapd-$DSINSTANCE/Server-Cert.crt -d /etc/dirsrv/slapd-$DSINSTANCE -f /etc/dirsrv/slapd-$DSINSTANCE/pwdfile.txt
dsctl $DSINSTANCE restart
fi
cp /etc/dirsrv/ssca/ca.crt /etc/openldap/ldap-cacert.crt
fi

#### configure /etc/openldap/ldap.conf
! grep -qi '^base' /etc/openldap/ldap.conf && \
echo "BASE $DBBASE" >>/etc/openldap/ldap.conf
! grep -qi '^uri' /etc/openldap/ldap.conf && \
echo "URI ldap://$LOCALSUPPLIER" >>/etc/openldap/ldap.conf
! grep -qi '^TLS_CACERT' /etc/openldap/ldap.conf && \
echo "TLS_CACERT /etc/openldap/ldap-cacert.crt" >>/etc/openldap/ldap.conf

#### on host1: create group1 and user1
#### on host2: create a temporary replication manager account
if ((SID==1)); then
LIST=$(dsidm -b $DBBASE $DSINSTANCE posixgroup list)
! echo "$LIST" | grep -qFx group1 && \
dsidm -b $DBBASE $DSINSTANCE posixgroup create --cn group1 --gidNumber 1000

LIST=$(dsidm -b $DBBASE $DSINSTANCE user list)
! echo "$LIST" | grep -qFx user1 && \
dsidm -b $DBBASE $DSINSTANCE user create \
--uid user1 \
--uidNumber 1000 \
--cn "User1" \
--displayName "User1 Test" \
--gidNumber 1000 \
--homeDirectory /home/user1
elif ((SID==2)); then
LIST=$(dsidm -b $DBBASE $DSINSTANCE user list)
if ! echo "$LIST" | grep -qFx $REMOTESUPPLIER ; then
! ldapsearch -Y EXTERNAL -Q -H ldapi://%2fvar%2frun%2fslapd-$DSINSTANCE.socket -b cn=config -LLL dn | grep -qF "cn=replication manager,cn=config" && \
dsconf $DSINSTANCE replication create-manager --passwd tmp1234
! dsconf $DSINSTANCE replication list | grep -qFx $DBBASE && \
dsconf $DSINSTANCE replication enable --suffix="$DBBASE" --role="supplier" --replica-id="$SID" --bind-dn="cn=replication manager,cn=config"
dsctl $DSINSTANCE restart
fi
fi
#!/bin/bash

#### set SID LOCALSUPPLIER REMOTESUPPLIER
DEV=$(ip route list scope global | awk -v v=dev '$1=="default"{for(a=2;a<NF;a++)if($a==v){print $(a+1);exit}}')
ADDR=$(ip addr show dev $DEV scope global | awk '$1=="inet"{print $2;exit}' | cut -f1 -d/)

case $(awk -v i=$ADDR '$1==i{print $3;exit}' </etc/hosts) in
host1)
SID=1
LOCALSUPPLIER=$(awk -v i=$ADDR '$1==i{print $2;exit}' </etc/hosts)
REMOTESUPPLIER=$(awk -v i=host2 '$3==i{print $2;exit}' </etc/hosts)
;;
host2)
SID=2
LOCALSUPPLIER=$(awk -v i=$ADDR '$1==i{print $2;exit}' </etc/hosts)
REMOTESUPPLIER=$(awk -v i=host1 '$3==i{print $2;exit}' </etc/hosts)
;;
*)
echo error this machine in not host1 or host2
exit 1
;;
esac

#### set DSINSTANCE DBBASE
DSINSTANCE=$(dsctl -l | sed 's@^slapd-@@')
DBBASE=$(awk '$1=="BASE"{print $2;exit}' </etc/openldap/ldap.conf)


#### on host1
if ((SID==1)); then
#### create the group repl_server for nsds5ReplicaBindDNGroup
! dsidm -b $DBBASE $DSINSTANCE group list | grep -qFx repl_server && \
dsidm -b $DBBASE $DSINSTANCE group create --cn repl_server

#### create accounts for both hosts
#### add the client certificates to the corresponding accounts
#### add both accounts to the group repl_server
for a in $LOCALSUPPLIER $REMOTESUPPLIER ; do
LIST=$(dsidm -b $DBBASE $DSINSTANCE user list)
! echo "$LIST" | grep -qFx $a && \
dsidm -b $DBBASE $DSINSTANCE user create --uid $a --uidNumber $((1000+SID)) --cn $a \
--displayName $a --gidNumber 2000 --homeDirectory /home/$a && \
TMP=$(mktemp -d --tmpdir=/dev/shm) && \
openssl req -subj "/DC=${DBBASE##*=}/DC=$(echo $DBBASE | cut -f1 -d, | cut -f2 -d=)/OU=people/UID=$a" -newkey rsa:2048 -nodes -keyout $TMP/cert.key -new -out $TMP/cert.csr && \
certutil -C -d /etc/dirsrv/ssca -f /etc/dirsrv/ssca/pwdfile.txt -v 24 -a -i $TMP/cert.csr -o $TMP/cert.crt -c Self-Signed-CA && \
openssl x509 -in $TMP/cert.crt -out $TMP/cert.der -outform DER && \
echo -e "dn: uid=$a,ou=people,$DBBASE\nchangetype: modify\nadd: userCertificate\nuserCertificate:< file://$TMP/cert.der" | ldapmodify -Y EXTERNAL -Q -H ldapi://%2fvar%2frun%2fslapd-$DSINSTANCE.socket && \
rm -r $TMP && \
echo -e "dn: cn=repl_server,ou=groups,$DBBASE\nchangetype: modify\nadd: member\nmember: uid=$a,ou=people,$DBBASE" | ldapmodify -Y EXTERNAL -Q -H ldapi://%2fvar%2frun%2fslapd-$DSINSTANCE.socket
done
#### create the replica entry
! dsconf $DSINSTANCE replication list | grep -qFx $DBBASE && \
dsconf $DSINSTANCE replication enable --suffix="$DBBASE" --role="supplier" --replica-id="$SID" --bind-group-dn="cn=repl_server,ou=groups,$DBBASE" && \
dsconf $DSINSTANCE replication set --suffix="$DBBASE" --repl-bind-group-interval=0

#### create the replication agreement
! dsconf $DSINSTANCE repl-agmt list --suffix=$DBBASE | grep -qFx 'cn: agreement' && \
dsconf $DSINSTANCE repl-agmt create --suffix="$DBBASE" --host="$REMOTESUPPLIER" --port=636 --conn-protocol=LDAPS --bind-method=SSLCLIENTAUTH --init --bootstrap-bind-dn="cn=replication manager,cn=config" --bootstrap-bind-passwd="tmp1234" --bootstrap-bind-method=SIMPLE --bootstrap-conn-protocol=LDAPS agreement
fi

Hi,
I'm trying to setup multi-supplier replication with certificate-based authentication.
The only documentation I have found on the subject is:
https://access.redhat.com/documentation/en-us/red_hat_directory_server/11/html/administration_guide/configuring_replication_partners_to_use_certificate-based_authentication
however it doesn't seems to be complete.

I have been doing my test with openSUSE 15.3 and RHEL/CentOS 8 with SELinux disabled.

Attached there are a couple of scripts (dmtest-init and dmtest-agmt) that will help
you reproduce my setup so you can tell me what I'm missing or doing wrong.

For testing you need two (virtual) machines and their ip addresses.
A minimum text/server installation will do.

Run on the first machine:
./dmtest-init <IP1> <IP2>

The script will configure /etc/hosts so that the machine with IP1
can be reached as host1.example.com and the other as host2.example.com.
389-ds will be installed if not present.
A new ds instance will be created, started (password is: password)
and configured.
/etc/openldap/ldap.conf is configured appropriately.
group1 and user1 are created.

Now run on the second machine the same command:
./dmtest-init <IP1> <IP2>
the script will setup the second machine in the same way
but with the following differences:
The CA database is imported with rsync from host1.
A new Server-Cert is created using the CA certificate from host1.
group1 and user1 are not created. They should appear on host2
after host1 will replicate his database.
A temporary replication manager account is created.

At this point the following commands should work on both machines:

ldapsearch -H ldaps://host1.example.com -D "cn=Directory Manager" -w password
ldapsearch -H ldaps://host2.example.com -D "cn=Directory Manager" -w password

Binding with a user certificate should also work:
openssl req -subj "/DC=com/DC=example/OU=people/UID=user1" -newkey rsa:2048 -nodes -keyout cert.key -new -out cert.csr
certutil -C -d /etc/dirsrv/ssca -f /etc/dirsrv/ssca/pwdfile.txt -a -i cert.csr -o cert.crt -c Self-Signed-CA
LDAPTLS_CERT=$PWD/cert.crt LDAPTLS_KEY=$PWD/cert.key ldapsearch -H ldaps://host1.example.com -D "uid=user1,ou=people,dc=example,dc=com"

The next step is the replication setup. Run on host1:
./dstest-agmt
this script will:
- create the group repl_server for nsds5ReplicaBindDNGroup
- create accounts for both hosts
- add the client certificates to the corresponding accounts
- add both accounts to the group repl_server
- create the replica entry
- create the replication agreement (with bootstrapt parameters)

These are essentially the steps described in the RedHat Directory Server 11 documentation: 15.6

Now a look at /var/log/dirsrv/slapd-ldaptest/errors shows that
the replication bind with the bootstrap parameters works (group1 and user1 are now present on host2)
but the replication bind with EXTERNAL auth fails.

- ERR - slapi_ldap_bind - Error: could not bind id [(anon)] authentication mechanism [EXTERNAL]: error 49 (Invalid credentials)
- ERR - NSMMReplicationPlugin - bind_and_check_pwp - agmt="cn=agreement" (host2:636) - Replication bind with EXTERNAL auth failed: LDAP error 49 (Invalid credentials) ()
- INFO - NSMMReplicationPlugin - bind_and_check_pwp - agmt="cn=agreement" (host2:636): Replication bind with SIMPLE auth resumed

Clearly there is something wrong with the client certificate setup, but
I could not figure out what.
Any help is appreciated.

Giacomo Comes

No comments:

Post a Comment