diff --git a/gen/ldap_it4i b/gen/ldap_it4i new file mode 100755 index 00000000..97d1d1ed --- /dev/null +++ b/gen/ldap_it4i @@ -0,0 +1,124 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use perunServicesInit; +use perunServicesUtils; + +our $SERVICE_NAME = "ldap_it4i"; +local $::PROTOCOL_VERSION = "3.0.0"; +my $SCRIPT_VERSION = "3.0.0"; + +perunServicesInit::init; +my $DIRECTORY = perunServicesInit::getDirectory; +my $fileName = "$DIRECTORY/$::SERVICE_NAME".".ldif"; +my $baseDnFileName = "$DIRECTORY/baseDN"; + +my $data = perunServicesInit::getHashedHierarchicalData; + +# constants +our $A_F_BASE_DN; *A_F_BASE_DN = \'urn:perun:facility:attribute-def:def:ldapBaseDN'; +our $A_USER_LOGIN_EINFRA; *A_USER_LOGIN_EINFRA = \'urn:perun:user:attribute-def:def:login-namespace:einfra'; +our $A_FIRST_NAME; *A_FIRST_NAME = \'urn:perun:user:attribute-def:core:firstName'; +our $A_LAST_NAME; *A_LAST_NAME = \'urn:perun:user:attribute-def:core:lastName'; +our $A_DISPLAY_NAME; *A_DISPLAY_NAME = \'urn:perun:user:attribute-def:core:displayName'; +our $A_USER_PREFERRED_MAIL; *A_USER_PREFERRED_MAIL = \'urn:perun:user:attribute-def:def:preferredMail'; +our $A_SSHKEYS; *A_SSHKEYS = \'urn:perun:user:attribute-def:def:sshPublicKey'; +our $A_MEMBER_STATUS; *A_MEMBER_STATUS = \'urn:perun:member:attribute-def:core:status'; + +our $STATUS_VALID; *STATUS_VALID = \'VALID'; +our $STATUS_EXPIRED; *STATUS_EXPIRED = \'EXPIRED'; +our $STATUS_DISABLED; *STATUS_DISABLED = \'DISABLED'; + +# check facility attribute +my $ldapBaseDN = $data->getFacilityAttributeValue( attrName => $A_F_BASE_DN ); +if (!defined($ldapBaseDN)) { + exit 1; +} + +# gather user data +my $users; + +foreach my $resourceId ( $data->getResourceIds() ) { + foreach my $memberId ( $data->getMemberIdsForResource( resource => $resourceId )) { + + my $login = $data->getUserAttributeValue( member => $memberId, attrName => $A_USER_LOGIN_EINFRA ); + $users->{$login}->{$A_FIRST_NAME} = $data->getUserAttributeValue( member => $memberId, attrName => $A_FIRST_NAME ); + $users->{$login}->{$A_LAST_NAME} = $data->getUserAttributeValue( member => $memberId, attrName => $A_LAST_NAME ); + $users->{$login}->{$A_DISPLAY_NAME} = $data->getUserAttributeValue( member => $memberId, attrName => $A_DISPLAY_NAME ); + $users->{$login}->{$A_USER_PREFERRED_MAIL} = $data->getUserAttributeValue( member => $memberId, attrName => $A_USER_PREFERRED_MAIL ); + $users->{$login}->{$A_SSHKEYS} = $data->getUserAttributeValue( member => $memberId, attrName => $A_SSHKEYS ); + + my $status = $data->getMemberAttributeValue( member => $memberId, attrName => $A_MEMBER_STATUS ); + if ($status eq $STATUS_VALID) { + $status = 'ACTIVE'; + } elsif ($status eq $STATUS_EXPIRED) { + $status = 'INACTIVE'; + } elsif ($status eq $STATUS_DISABLED) { + $status = 'ARCHIVED'; + } + $users->{$login}->{$A_MEMBER_STATUS} = $status; + } +} + +# print BASE_DN file +open FILE,">:encoding(UTF-8)","$baseDnFileName" or die "Cannot open $baseDnFileName: $! \n"; +print FILE $ldapBaseDN; +close(FILE); + +# print user data LDIF +open FILE,">:encoding(UTF-8)","$fileName" or die "Cannot open $fileName: $! \n"; + +for my $login (sort keys %$users) { + + my $givenName = $users->{$login}->{$A_FIRST_NAME}; + my $sn = $users->{$login}->{$A_LAST_NAME}; + + print FILE "dn: uid=" . $login . "," . $ldapBaseDN . "\n"; + print FILE "uid: " . $login . "\n"; + + if (defined $givenName and length $givenName) { + print FILE "givenName: " . $givenName . "\n"; + } + + if (defined $sn and length $sn) { + print FILE "sn: " . $sn . "\n"; + } + + if (defined $sn and length $sn and defined $givenName and length $givenName) { + print FILE "cn: " . $givenName . " " . $sn . "\n"; + } elsif (defined $sn and length $sn) { + print FILE "cn: " . $sn . "\n"; + } elsif (defined $givenName and length $givenName) { + print FILE "cn: " . $givenName . "\n"; + } else { + print FILE "cn: N/A\n"; + } + + print FILE "displayName: " . $users->{$login}->{$A_DISPLAY_NAME} . "\n"; + print FILE "mail: " . $users->{$login}->{$A_USER_PREFERRED_MAIL} . "\n"; + print FILE "userPassword: {SASL}" . $login . '@EINFRA' . "\n"; + print FILE "status: " . $users->{$login}->{$A_MEMBER_STATUS} . "\n"; + + my $sshKeys = $users->{$login}->{$A_SSHKEYS}; + if (defined $sshKeys and length $sshKeys) { + foreach my $sshKey (sort @$sshKeys) { + print FILE "sshPublicKey: " . $sshKey . "\n"; + } + } + + # print classes + print FILE "objectclass: top\n"; + print FILE "objectclass: person\n"; + print FILE "objectclass: einfraPerson\n"; + print FILE "objectclass: inetOrgPerson\n"; + print FILE "objectclass: ldapPublicKey\n"; + + # there must be empty line after each entry + print FILE "\n"; + +} + +close FILE; + +perunServicesInit::finalize; diff --git a/gen/vmware_ldap b/gen/vmware_ldap new file mode 100755 index 00000000..36e88836 --- /dev/null +++ b/gen/vmware_ldap @@ -0,0 +1,203 @@ +#!/usr/bin/perl +use strict; +use warnings; +use perunServicesInit; +use perunServicesUtils; +use Text::Unidecode; + +local $::SERVICE_NAME = "vmware_ldap"; +local $::PROTOCOL_VERSION = "1.0.0"; +my $SCRIPT_VERSION = "1.0.0"; + +perunServicesInit::init; +my $DIRECTORY = perunServicesInit::getDirectory; +my $fileName_users = "$DIRECTORY/$::SERVICE_NAME"."_users".".ldif"; +my $fileName_groups = "$DIRECTORY/$::SERVICE_NAME"."_groups".".ldif"; + +my $baseDnFileName = "$DIRECTORY/baseDN"; + +my $data = perunServicesInit::getHashedHierarchicalData; + +#Constants +our $A_F_BASE_DN; *A_F_BASE_DN = \'urn:perun:facility:attribute-def:def:ldapBaseDN'; +our $A_R_VO_SHORT_NAME; *A_R_VO_SHORT_NAME = \'urn:perun:resource:attribute-def:virt:voShortName'; +our $A_R_NAME; *A_R_NAME = \'urn:perun:resource:attribute-def:core:name'; + +# User attributes +our $A_USER_LOGIN; *A_USER_LOGIN = \'urn:perun:user:attribute-def:def:login-namespace:einfra'; +our $A_FIRST_NAME; *A_FIRST_NAME = \'urn:perun:user:attribute-def:core:firstName'; +our $A_LAST_NAME; *A_LAST_NAME = \'urn:perun:user:attribute-def:core:lastName'; +our $A_MAIL; *A_MAIL = \'urn:perun:user:attribute-def:def:preferredMail'; + +# CHECK ON FACILITY ATTRIBUTES +my $ldapBaseDN = $data->getFacilityAttributeValue( attrName => $A_F_BASE_DN ); +if (!defined($ldapBaseDN)) { + exit 1; +} + +# GATHER USERS +my $users; # $users->{$login}->{ATTR} = $attrValue; +my $usersVos; # $users->{$login}->{$voShortName} = 1; +# GATHER VOS and RESOURCES +my $allVosAndResources; # $allVosAndResources->{$voShortName}->{$resourceName} = 1; +# GATHER USERS FROM RESOURCES +my $usersByResource; # $usersByResource->{$resourceName}->{$login}->{ATTR} = $attrValue; + +# FOR EACH RESOURCE +foreach my $resourceId ( $data->getResourceIds() ) { + + my $voShortName = $data->getResourceAttributeValue( resource => $resourceId, attrName => $A_R_VO_SHORT_NAME ); + my $resourceName = $data->getResourceAttributeValue( resource => $resourceId, attrName => $A_R_NAME ); + + # Fill all VOs reference + $allVosAndResources->{$voShortName}->{$resourceName} = 1; + + # FOR EACH MEMBER ON RESOURCE + foreach my $memberId ($data->getMemberIdsForResource( resource => $resourceId )) { + + my $login = $data->getUserAttributeValue( member => $memberId, attrName => $A_USER_LOGIN ); + my $firstName = $data->getUserAttributeValue( member => $memberId, attrName => $A_FIRST_NAME ); + my $lastName = $data->getUserAttributeValue( member => $memberId, attrName => $A_LAST_NAME ); + my $mail = $data->getUserAttributeValue( member => $memberId, attrName => $A_MAIL ); + + # Store users in Resources structure + $usersByResource->{$resourceName}->{$login}->{$A_FIRST_NAME} = $firstName; + $usersByResource->{$resourceName}->{$login}->{$A_LAST_NAME} = $lastName; + if (defined $mail and length $mail) { + $usersByResource->{$resourceName}->{$login}->{$A_MAIL} = $mail; + } + + # Store same user in flat structure + $users->{$login}->{$A_FIRST_NAME} = $firstName; + $users->{$login}->{$A_LAST_NAME} = $lastName; + $users->{$login}->{$A_MAIL} = $mail; + + # Store VOS for member + $usersVos->{$login}->{$voShortName} = 1; + + } + +} + +# +# PRINT BASE_DN FILE +# +open FILE,">:encoding(UTF-8)","$baseDnFileName" or die "Cannot open $baseDnFileName: $! \n"; +print FILE $ldapBaseDN; +close(FILE); + +# +# PRINT LDIF FILE +# +open FILE,">:encoding(UTF-8)","$fileName_users" or die "Cannot open $fileName_users: $! \n"; + +# print base entry + +print FILE "dn: ou=perun,ou=users," . $ldapBaseDN . "\n"; +print FILE "ou: perun\n"; +print FILE "objectClass: top\n"; +print FILE "objectClass: organizationalUnit\n"; +print FILE "\n"; + +# FLAT structure is stored in ou=perun,ou=users + base DN +for my $login (sort keys %$users) { + + print FILE "dn: cn=" . $login . ",ou=perun,ou=users," . $ldapBaseDN . "\n"; + print FILE "cn: " . $login . "\n"; + print FILE "uid: " . $login . "\n"; + + my $givenName = $users->{$login}->{$A_FIRST_NAME}; + if (defined $givenName and length $givenName) { + print FILE "givenname: " . $givenName . "\n"; + } + my $sn = $users->{$login}->{$A_LAST_NAME}; + if (defined $givenName and length $givenName) { + print FILE "sn: " . $users->{$login}->{$A_LAST_NAME} . "\n"; + } + + my $mail = $users->{$login}->{$A_MAIL}; + if (defined $mail and length $mail) { + print FILE "mail: " . $mail . "\n"; + } + + print FILE "userpassword: {SASL}" . $login . '@EINFRA' . "\n"; + + # print VO membership information + my @vos = keys %{$usersVos->{$login}}; + for my $vo (@vos) { + print FILE "ou: " . $vo . "\n"; + } + + # print classes + print FILE "objectclass: top\n"; + print FILE "objectclass: inetOrgPerson\n"; + + # There MUST be an empty line after each entry, so entry sorting and diff works on slave part + print FILE "\n"; + +} + +close(FILE); + +# +# PRINT LDIF FILE +# +open FILE,">:encoding(UTF-8)","$fileName_groups" or die "Cannot open $fileName_groups: $! \n"; + +# print base entry + +print FILE "dn: ou=perun,ou=groups," . $ldapBaseDN . "\n"; +print FILE "ou: perun\n"; +print FILE "objectClass: top\n"; +print FILE "objectClass: organizationalUnit\n"; +print FILE "\n"; + +# Print VOs entries like "ou=voShortName,ou=perun,ou=groups + baseDN" +# Then Print also Resources inside VOs like: "cn=resourceName,ou=voShortName,ou=groups + baseDN" and their uniqueMembers + +# PRINT ALL VOS +my @vos = sort keys %{$allVosAndResources}; +for my $vo (@vos) { + + # PRINT VO + print FILE "dn: ou=" . $vo . ",ou=perun,ou=groups," . $ldapBaseDN . "\n"; + print FILE "ou: " . $vo . "\n"; + print FILE "objectclass: top\n"; + print FILE "objectclass: organizationalUnit\n"; + + # There MUST be an empty line after each entry, so entry sorting and diff works on slave part + print FILE "\n"; + + # PRINT ALL RESOURCES + my @resources = sort keys %{$allVosAndResources->{$vo}}; + for my $resource (@resources) { + + my @usrs = sort keys %{$usersByResource->{$resource}}; + + # skip printing resource and it's users, if there are no users + unless (@usrs) { + next; + } + + # PRINT RESOURCE + print FILE "dn: cn=" . $resource . ",ou=" . $vo . ",ou=perun,ou=groups," . $ldapBaseDN . "\n"; + print FILE "cn: " . $resource . "\n"; + print FILE "ou: " . $vo . "\n"; + print FILE "objectclass: top\n"; + print FILE "objectclass: groupOfUniqueNames\n"; + + # PRINT ALL USERS FROM RESOURCE + for my $u (@usrs) { + print FILE "uniquemember: cn=" . $u . ",ou=perun,ou=users," . $ldapBaseDN . "\n"; + } + + # There MUST be an empty line after each entry, so entry sorting and diff works on slave part + print FILE "\n"; + + } + +} + +close(FILE); + +perunServicesInit::finalize; diff --git a/send/ADConnector.pm b/send/ADConnector.pm index 691180be..f776bbc3 100644 --- a/send/ADConnector.pm +++ b/send/ADConnector.pm @@ -319,7 +319,7 @@ sub load_perun($){ ldap_log('ad_connection', "Error read Perun ldif: " . $entry->get_value('cn') . " | " . $ldif->error()); } else { # push valid entry - push(@perun_entries, $entry) if (defined $entry and ($entry->get_value('cn') or $entry->get_value('ou'))); + push(@perun_entries, $entry) if (defined $entry and ($entry->get_value('cn') or $entry->get_value('ou') or $entry->get_value('uid'))); } } @@ -363,7 +363,7 @@ sub load_ad($$$$) { for my $entry ($mesg->entries) { # store only valid entry from AD - push(@ad_entries,$entry) if ($entry->get_value('cn') or $entry->get_value('ou')); + push(@ad_entries,$entry) if ($entry->get_value('cn') or $entry->get_value('ou') or $entry->get_value('uid')); } # Paging Control diff --git a/send/ldap_it4i b/send/ldap_it4i new file mode 100644 index 00000000..e8800350 --- /dev/null +++ b/send/ldap_it4i @@ -0,0 +1,165 @@ +#!/usr/bin/perl +use strict; +use warnings; +no if $] >= 5.017011, warnings => 'experimental::smartmatch'; +use Net::LDAPS; +use Net::LDAP::Entry; +use Net::LDAP::Message; +use Net::LDAP::LDIF; +use ScriptLock; +use ADConnector; + +sub process_update; +sub process_archive; + +# Constants and counters +my $counter_add = 0; +my $counter_update = 0; +my $counter_archive = 0; +my $counter_fail = 0; + +# Define service +my $service_name = "ldap_it4i"; + +# GEN folder location +my $facility_name = $ARGV[0]; +chomp($facility_name); +my $service_files_base_dir="../gen/spool"; +my $service_files_dir="$service_files_base_dir/$facility_name/$service_name"; +my $service_file = "$service_files_dir/$service_name.ldif"; + +# BASE DN +open my $file, '<', "$service_files_dir/baseDN"; +my $base_dn = <$file>; +chomp($base_dn); +close $file; + +# propagation destination +my $namespace = $ARGV[1]; +chomp($namespace); + +# create service lock +my $lock = ScriptLock->new($facility_name . "_" . $service_name . "_" . $namespace); +($lock->lock() == 1) or die "Unable to get lock, service propagation was already running."; + +# init configuration +my @conf = init_config($namespace); +# we cannot use a function to resolve PDC from DNS since the LDAP instance is not an AD, therefore, we use the raw value +my $ldap_location = $conf[0]; +my $ldap = Net::LDAPS->new( "$ldap_location" , onerror => 'warn' , timeout => 15, ciphers => 'AES256-GCM-SHA384' ); +my $filter = '(objectClass=person)'; + +# connect +ldap_bind($ldap, $conf[1], $conf[2]); + +# load all data +my @users = load_perun($service_file); +my @previousUsers = load_ad($ldap, $base_dn, $filter, ['uid','givenName','sn','cn','displayName','mail','status','sshPublicKey']); + +my %previousUsersMap = (); +foreach my $previousUser (@previousUsers) { + my $login = $previousUser->get_value('uid'); + $previousUsersMap{ $login } = $previousUser; +} + +my %usersMap = (); +foreach my $user (@users) { + my $login = $user->get_value('uid'); + $usersMap{ $login } = $user; +} + +# process data +process_update(); +process_archive(); + +# disconnect +ldap_unbind($ldap); + +# log results +ldap_log($service_name, "Added: " . $counter_add . " entries."); +ldap_log($service_name, "Updated: " . $counter_update. " entries."); +ldap_log($service_name, "Archived: " . $counter_archive. " entries."); +ldap_log($service_name, "Failed: " . $counter_fail. " entries."); + +$lock->unlock(); + +if ($counter_fail > 0) { die "Failed to process: " . $counter_fail . " entries.\nSee log at: ~/perun-engine/send/logs/$service_name.log";} + +# private methods +sub process_update() { + + foreach my $user (@users) { + + if (exists $previousUsersMap{$user->get_value('uid')}) { + + my $previousUser = $previousUsersMap{$user->get_value('uid')}; + + my @attrs = ('givenName','sn','cn','displayName','mail','status','sshPublicKey'); + + # stored log messages to check if entry should be updated + my @entry_changed = (); + + # check each attribute + foreach my $attr (@attrs) { + if (compare_entry( $previousUser, $user, $attr ) == 1) { + my $entry = $previousUser->get_value($attr); + my $perun_entry = $user->get_value($attr); + + push(@entry_changed, + "$attr | " . join(", ", $entry) . " => " . join(", ", $perun_entry)); + # replace value + $previousUser->replace( + $attr => \$perun_entry + ); + } + } + + if (@entry_changed) { + # Update entry in AD + my $response = $previousUser->update($ldap); + unless ($response->is_error()) { + # SUCCESS + ldap_log($service_name, "Updated: " . $previousUser->dn()); + $counter_update++; + } else { + # FAIL + ldap_log($service_name, "NOT updated: " . $previousUser->dn() . " | " . $response->error()); + ldap_log($service_name, $previousUser->ldif()); + $counter_fail++; + } + } + } else { + my $response = $user->update($ldap); + unless ($response->is_error()) { + # SUCCESS + ldap_log($service_name, "Added: " . $user->dn()); + $counter_add++; + } else { + # FAIL + ldap_log($service_name, "NOT added: " . $user->dn() . " | " . $response->error()); + ldap_log($service_name, $user->ldif()); + $counter_fail++; + } + } + } +} + +sub process_archive() { + + foreach my $previousUser (@previousUsers) { + unless (exists $usersMap{$previousUser->get_value('uid')}) { + $previousUser->replace( + 'status' => 'ARCHIVED' + ); + my $response = $previousUser->update($ldap); + unless ($response->is_error()) { + ldap_log($service_name, "Archived: " . $previousUser->dn()); + $counter_archive++; + } else { + ldap_log($service_name, "NOT archived: " . $previousUser->dn() . " | " . $response->error()); + ldap_log($service_name, $previousUser->ldif()); + $counter_fail++; + } + } + } +} diff --git a/send/vmware_ldap b/send/vmware_ldap new file mode 100755 index 00000000..ae8a5cca --- /dev/null +++ b/send/vmware_ldap @@ -0,0 +1,4 @@ +#!/bin/bash +SERVICE_NAME="vmware_ldap" + +. generic_send diff --git a/slave/process-vmware-ldap/bin/process-vmware_ldap.sh b/slave/process-vmware-ldap/bin/process-vmware_ldap.sh new file mode 100755 index 00000000..87e681d0 --- /dev/null +++ b/slave/process-vmware-ldap/bin/process-vmware_ldap.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +# Original update script created by (C) funny from ZCU +# 2007-08-05 handle bug in ldapsearch +# 2011-08-10 generification of script +# 2013-06-20 prevent concurrent run + +PROTOCOL_VERSION='1.0.0' + +function process { + + # Available error codes + E_CANNOT_READ_LDAP=(50 'Cannot read current state of LDAP.') + E_WHEN_UPDATING_LDAP=(51 'Error when updating LDAP.') + + # sort & diff scripts from CPAN + LDIFDIFF="${LIB_DIR}/${SERVICE}/ldifdiff.pl" + LDIFSORT="${LIB_DIR}/${SERVICE}/ldifsort.pl" + + # work files location + INFILE="${WORK_DIR}/${SERVICE}_users.ldif" + ACFILE="${WORK_DIR}/actual_users.ldif" + INFILE_GROUPS="${WORK_DIR}/${SERVICE}_groups.ldif" + ACFILE_GROUPS="${WORK_DIR}/actual_groups.ldif" + + # sorted work files + SINFILE="${WORK_DIR}/sorted-new_users.ldif" + SACFILE="${WORK_DIR}/sorted-actual_users.ldif" + SINFILE_GROUPS="${WORK_DIR}/sorted-new_groups.ldif" + SACFILE_GROUPS="${WORK_DIR}/sorted-actual_groups.ldif" + + # diff file used to modify ldap + MODFILE="${WORK_DIR}/mod" + MODFILE_GROUPS="${WORK_DIR}/mod_groups" + + BASE_DN=`head -n 1 "${WORK_DIR}/baseDN"` + BASE_DN_USERS="ou=perun,ou=users,$BASE_DN" + BASE_DN_GROUPS="ou=perun,ou=groups,$BASE_DN" + + # Create lock + create_lock + + # ============ process USERS =============== + + # get actual state of ldap + catch_error E_CANNOT_READ_LDAP ldapsearch -x -H "$LDAP_URL" -D "$LDAP_LOGIN" -w "$LDAP_PASSWORD" -LLL -b "$BASE_DN_USERS" "$LDAP_FILTER_USERS" '*' > $ACFILE + + sed '/^[^ ].*/N; s/\n //g' $ACFILE > $ACFILE.2 + + if test -s $ACFILE.2; then + # LDAP is not empty under base DN + + # SORT LDIFs + $LDIFSORT -k dn $ACFILE.2 >$SACFILE + $LDIFSORT -k dn $INFILE >$SINFILE + + # DIFF LDIFs + $LDIFDIFF -k dn $SINFILE $SACFILE | sed '/^[^ ].*/N; s/\n //g' > MODFILE + + # Update LDAP based on changes + catch_error E_WHEN_UPDATING_LDAP ldapmodify -x -H "$LDAP_URL" -D "$LDAP_LOGIN" -w "$LDAP_PASSWORD" -c < MODFILE + + else + # LDAP is empty under base DN + + $LDIFSORT -k dn $INFILE >$SINFILE + + # All entries are new, use ldapadd + catch_error E_WHEN_UPDATING_LDAP ldapadd -x -H "$LDAP_URL" -D "$LDAP_LOGIN" -w "$LDAP_PASSWORD" -c < $SINFILE + + fi + + # ============ process GROUPS =============== + + # get actual state of ldap + catch_error E_CANNOT_READ_LDAP ldapsearch -x -H "$LDAP_URL" -D "$LDAP_LOGIN" -w "$LDAP_PASSWORD" -LLL -b "$BASE_DN_GROUPS" "$LDAP_FILTER_GROUPS" '*' > $ACFILE_GROUPS + + sed '/^[^ ].*/N; s/\n //g' $ACFILE_GROUPS > $ACFILE_GROUPS.2 + + if test -s $ACFILE_GROUPS.2; then + # LDAP is not empty under base DN + + # SORT LDIFs + $LDIFSORT -k dn $ACFILE_GROUPS.2 >$SACFILE_GROUPS + $LDIFSORT -k dn $INFILE_GROUPS >$SINFILE_GROUPS + + # DIFF LDIFs + $LDIFDIFF -k dn $SINFILE_GROUPS $SACFILE_GROUPS | sed '/^[^ ].*/N; s/\n //g' > MODFILE_GROUPS + + # Update LDAP based on changes + catch_error E_WHEN_UPDATING_LDAP ldapmodify -x -H "$LDAP_URL" -D "$LDAP_LOGIN" -w "$LDAP_PASSWORD" -c < MODFILE_GROUPS + + else + # LDAP is empty under base DN + + $LDIFSORT -k dn $INFILE_GROUPS >$SINFILE_GROUPS + + # All entries are new, use ldapadd + catch_error E_WHEN_UPDATING_LDAP ldapadd -x -H "$LDAP_URL" -D "$LDAP_LOGIN" -w "$LDAP_PASSWORD" -c < $SINFILE_GROUPS + + fi + +} diff --git a/slave/process-vmware-ldap/changelog b/slave/process-vmware-ldap/changelog new file mode 100644 index 00000000..deaf6318 --- /dev/null +++ b/slave/process-vmware-ldap/changelog @@ -0,0 +1,5 @@ +perun-slave-process-vmware-ldap (1.0.0) stable; urgency=low + + * New service for provisioning of LDAP for VMware. + + -- Pavel Zlamal Tue, 14 Dec 2021 10:40:00 +0100 diff --git a/slave/process-vmware-ldap/conf/example-pre_10_setup_connection b/slave/process-vmware-ldap/conf/example-pre_10_setup_connection new file mode 100644 index 00000000..5f0f7bd5 --- /dev/null +++ b/slave/process-vmware-ldap/conf/example-pre_10_setup_connection @@ -0,0 +1,16 @@ +#!/bin/bash +# +# Example pre-file for LDAP service providing authz information. +# For production use remove "example-" prefix for file and fill the values. +# +# LDAP_LOGIN=[login] +# LDAP_PASSWORD=[password] +# LDAP_URL=[LDAP server location with optional ":port" part] +# LDAP_FILTER_USERS=[filter for query in standard LDAP syntax used to read current state of ldap (users)] +# LDAP_FILTER_GROUPS=[filter for query in standard LDAP syntax used to read current state of ldap (groups)] +# +LDAP_LOGIN= +LDAP_PASSWORD= +LDAP_URL= +LDAP_FILTER_USERS="(objectclass=*)" +LDAP_FILTER_GROUPS="(objectclass=*)" \ No newline at end of file diff --git a/slave/process-vmware-ldap/dependencies b/slave/process-vmware-ldap/dependencies new file mode 100644 index 00000000..9c8f3865 --- /dev/null +++ b/slave/process-vmware-ldap/dependencies @@ -0,0 +1 @@ +perun-slave-base, libnet-ldap-perl diff --git a/slave/process-vmware-ldap/lib/ldifdiff.pl b/slave/process-vmware-ldap/lib/ldifdiff.pl new file mode 100644 index 00000000..948b84ef --- /dev/null +++ b/slave/process-vmware-ldap/lib/ldifdiff.pl @@ -0,0 +1,327 @@ +#! /usr/bin/perl + +=head1 NAME + +ldifdiff.pl -- Generates LDIF change diff between two sorted LDIF files. + +=head1 DESCRIPTION + +ldifdiff.pl takes as input two sorted LDIF files, source and target, and +generates on standard output the LDIF changes needed to transform the target +into the source. + +=head1 SYNOPSIS + +ldifdiff.pl B<-k|--keyattr keyattr> [B<-a|--sourceattrs attr1,attr2,...>] [B<-c|--ciscmp attr1,...>] [B<-n|--numcmp attr1,...>] [B<--dnattrs attr1,...>] [B<--sharedattrs attr1,...>] B B + +=head1 OPTIONS + +=over 4 + +=item B<-k|--keyattr> keyattr + +Specifies the key attribute to use when comparing source and target entries. +Entries in both LDIF files must be sorted by this attribute for comparisons to +be meaningful. F can be used to sort LDIF files by a given +attribute. + +=item B<-a|--sourceattrs attr1,attr2,...> + +(Optional) Specifies a list of attributes to consider when comparing +source and target entries. By default, all attributes are considered. + +=item B<-c|--ciscmp attr1,...> + +(Optional) Compare values of the specified attributes case-insensitively. The +default set is: mail manager member objectclass owner uid uniqueMember + +=item B<-n|--numcmp attr1,...> + +(Optional) Compare values of the specified attributes numerically. The +default set is: employeeNumber + +=item B<--dnattrs attr1,...> + +(Optional) Specifies a list of attributes to be treated as DNs when being +compared. The default set is: manager member owner uniqueMember + +=item B<--sharedattrs attr1,...> + +(Optional) Specifies a list of attribues to be treated as "shared" attributes, +where the source may not be a sole authoritative source. When modifying +these attributes, separate "delete" and "add" LDIF changes are generated, +instead of a single "replace" change. The default set is objectClass. + +=item B + +Specifies the source LDIF file. + +=item B + +Specifies the target LDIF file. + +=back + +=cut + +use Net::LDAP; +use Net::LDAP::LDIF; +use Net::LDAP::Util qw(canonical_dn); +use Getopt::Long; + +use strict; + +my @sourceattrs; +my (%ciscmp, %numcmp, %dnattrs, %sharedattrs); +my $keyattr; +GetOptions('a|sourceattrs=s' => sub { @sourceattrs = split(/,/, $_[1]) }, + 'c|ciscmp=s' => sub { my @a = split(/,/,lc $_[1]); @ciscmp{@a} = (1) x @a }, + 'dnattrs=s' => sub { my @a = split(/,/,lc $_[1]); @dnattrs{@a} = (1) x @a }, + 'k|keyattr=s' => \$keyattr, + 'n|numcmp=s' => sub { my @a = split(/,/,lc $_[1]); @numcmp{@a} = (1) x @a }, + 'sharedattrs=s' => sub {my @a=split(/,/,lc $_[1]);@sharedattrs{@a}=(1) x @a} + ); +unless (keys %ciscmp) { + foreach (qw(cn mail manager member o ou objectclass owner uid uniquemember)) + { $ciscmp{$_} = 1 } +} +unless (keys %numcmp) { + foreach (qw(employeenumber)) + { $numcmp{$_} = 1 } +} +unless (keys %dnattrs) { + foreach (qw(manager member owner uniquemember)) + { $dnattrs{$_} = 1 } +} +%sharedattrs = (objectclass => 1) + unless keys %sharedattrs; + + +my ($sourcefile, $targetfile); +$sourcefile = shift; $targetfile = shift; + +die "usage: $0 -k|--keyattr keyattr [-a|--sourceattrs attr1,attr2,...] [-c|--ciscmp attr1,...] [--dnattrs attr1,...] [--sharedattrs attr1,...] sourcefile targetfile\n" + unless $keyattr && $sourcefile && $targetfile; + +my $source = Net::LDAP::LDIF->new($sourcefile) + or die "Can't open LDIF file $sourcefile: $!\n"; + +my $target = Net::LDAP::LDIF->new($targetfile) + or die "Can't open LDIF file $targetfile: $!\n"; + +my $ldifout = Net::LDAP::LDIF->new('-', 'w'); +$ldifout->{change} = 1; +$ldifout->{wrap} = 78; + +diff($source, $target); +exit; + + +# Gets the relative distinguished name (RDN) attribute +sub rdnattr { ($_[0] =~ /^(.*?)=/)[0] } + +# Gets the relative distinguished name (RDN) value +sub rdnval { my $rv = ($_[0] =~ /=(.*)/)[0]; $rv =~ s/(? 'lower'); + my $cbdn = canonical_dn($bdn, casefold => 'lower'); + my $rdnattr = lc rdnattr($cadn); + if ($ciscmp{$rdnattr}) { $cadn = lc($cadn), $cbdn = lc($cbdn) } + + return $numcmp{$rdnattr} ? $cadn <=> $cbdn : $cadn cmp $cbdn; +} + +sub cmpEntries +{ + my ($a, $b) = @_; + my $dncmp = cmpDNs($a->dn, $b->dn); + + if (lc($keyattr) eq 'dn') { + return ($dncmp, $dncmp); + } + else { + my $aval = $a->get_value($keyattr); + my $bval = $b->get_value($keyattr); + if ($ciscmp{$keyattr}) { + $aval = lc($aval); + $bval = lc($bval); + } + return($numcmp{$keyattr} ? $aval <=> $bval : $aval cmp $bval, $dncmp); + } +} + + +# Diffs two LDIF data sources +sub diff +{ + my ($source, $target) = @_; + my ($sourceentry, $targetentry, $incr_source, $incr_target, @ldifchanges); + + $sourceentry = $source->read_entry(); + $targetentry = $target->read_entry(); + + while () { + # End of all data + last if !$sourceentry && !$targetentry; + + # End of source data, but more target data. Delete. + if (!$sourceentry && $targetentry) { + $targetentry->delete; + $ldifout->write_entry($targetentry); + $incr_target = 1, next; + } + + # End of target data, but more data in source. Add. + if ($sourceentry && !$targetentry) { + $ldifout->write_entry($sourceentry); + $incr_source = 1, next; + } + + my ($entrycmp, $dncmp) = cmpEntries($sourceentry, $targetentry); + + # Check if the current source entry has a higher sort position than + # the current target. If so, we interpret this to mean that the + # target entry no longer exists on the source. Issue a delete to LDAP. + if ($entrycmp > 0) { + $targetentry->delete; + $ldifout->write_entry($targetentry); + $incr_target = 1, next; + } + # Check if the current source entry has a lower sort position than + # the current target entry. If so, we interpret this to mean that the + # source entry doesn't exist on the target. Issue an add to LDAP. + elsif ($entrycmp < 0) { + $ldifout->write_entry($sourceentry); + $incr_source = 1, next; + } + + # When we get here, we're dealing with the same person in $sourceentry + # and $targetentry. Compare the data and generate the update. + + # If a mod{R}DN is necessary, it needs to happen before other mods + if ($dncmp) { + my $rdnattr = rdnattr($sourceentry->dn); + my $rdnval = rdnval($sourceentry->dn); + my $newsuperior = dnsuperior($sourceentry->dn); + my $oldsuperior = dnsuperior($targetentry->dn); + my $changetype; + + if (cmpDNs($oldsuperior, $newsuperior)) { + $changetype = 'moddn'; + $targetentry->add(newsuperior => $newsuperior); + } + else { $changetype = 'modrdn' } + $targetentry->{changetype} = $changetype; + $targetentry->add(newrdn => "$rdnattr=$rdnval", + deleteoldrdn => '1'); + $ldifout->write_entry($targetentry); + $targetentry->delete('newrdn'); + $targetentry->delete('deleteoldrdn'); + $targetentry->delete('newsuperior') if $changetype eq 'moddn'; + delete($targetentry->{changetype}); + + $targetentry->dn($sourceentry->dn); + $targetentry->replace($rdnattr, $sourceentry->get_value($rdnattr)) + if $sourceentry->exists($rdnattr); + } + + # Check for differences and generate LDIF as appropriate + updateFromEntry($sourceentry, $targetentry, @sourceattrs); + $ldifout->write_entry($targetentry) if @{$targetentry->{changes}}; + $incr_source = 1, $incr_target = 1, next; + + } continue { + if ($incr_source) { + $sourceentry = $source->read_entry(); $incr_source = 0; + } + if ($incr_target) { + $targetentry = $target->read_entry(); $incr_target = 0; + } + } +} + +# Generate LDIF to update $target with information in $source. +# Optionally restrict the set of attributes to consider. +sub updateFromEntry +{ + my ($source, $target, @attrs) = @_; + my ($attr, $val, $ldifstr); + + unless (@attrs) { + # add all source entry attributes + @attrs = $source->attributes; + # add any other attributes we haven't seen from the target entry + foreach my $tattr ($target->attributes) { + push(@attrs, $tattr) unless grep(/^$tattr$/i, @attrs) + } + } + + $target->{changetype} = 'modify'; + + foreach $attr (@attrs) { + my $lcattr = lc $attr; + next if $lcattr eq 'dn'; # Can't handle modrdn here + + # Build lists of unique values in the source and target, to + # speed up comparisons. + my @sourcevals = $source->get_value($attr); + my @targetvals = $target->get_value($attr); + my (%sourceuniqvals, %targetuniqvals); + foreach (@sourcevals) { + my ($origval, $val) = ($_, $_); + $val = lc $val if $ciscmp{$lcattr}; + # Get rid of spaces after non-escaped commas in DN attrs + $val =~ s/(?delete($attr => [ values(%targetuniqvals) ]) + if keys(%targetuniqvals); + $target->add($attr => [ values(%sourceuniqvals) ]) + if keys(%sourceuniqvals); + } + else { + # Issue a replace or delete as needed + if (@sourcevals) { $target->replace($attr => [ @sourcevals ]) } + else { $target->delete($attr) } + } + } + + # Get rid of the "changetype: modify" if there were no changes + delete($target->{changetype}) unless @{$target->{changes}}; +} + + +=head1 AUTHOR + +Kartik Subbarao Esubbarao@computer.orgE + +=cut + diff --git a/slave/process-vmware-ldap/lib/ldifsort.pl b/slave/process-vmware-ldap/lib/ldifsort.pl new file mode 100644 index 00000000..2965c600 --- /dev/null +++ b/slave/process-vmware-ldap/lib/ldifsort.pl @@ -0,0 +1,122 @@ +#! /usr/bin/perl +# $Id: ldifsort.pl,v 1.9 2005/04/03 20:20:24 subbarao Exp $ + +=head1 NAME + +ldifsort.pl - Sorts an LDIF file by the specified key attribute. The sorted +version is written to standard output. + +=head1 DESCRIPTION + +Sorts an LDIF file by the specified key attribute. + +=head1 SYNOPSIS + +ldifsort.pl B<-k keyattr> [B<-andc>] file.ldif + +=over 4 + +=item B<-k> + +Specifies the key attribute for making sort comparisons. If 'dn' is +specified, sorting is done by the full DN string, which can be composed of +different attributes for different entries. + +=item B<-a> + +Specifies that attributes within a given entry should also be sorted. This +has the side effect of removing all comments and line continuations in the +LDIF file. + +=item B<-n> + +Specifies numeric comparisons on the key attribute. Otherwise string +comparisons are done. + +=item B<-d> + +Specifies that the key attribute is a DN. Comparisons are done on a +DN-normalized version of attribute values. This is the default +behavior if 'dn' is passed as the argument to B<-k>. + +=item B<-c> + +Specifies case-insensitive comparisons on the key attribute. This is the +default behavior if 'dn' is passed as the argument to B<-k>. + +=back + + +=head1 AUTHOR + +Kartik Subbarao Esubbarao@computer.orgE + +=cut + + +use Net::LDAP::Util qw(canonical_dn); +use MIME::Base64; +use Getopt::Std; + +use strict; + +my %args; +getopts("k:andc", \%args); + +my $keyattr = $args{k}; +my $sortattrs = $args{a}; +my $ciscmp = $args{c}; +my $ldiffile = $ARGV[0]; + +die "usage: $0 -k keyattr [-n] [-d] [-c] ldiffile\n" + unless $keyattr && $ldiffile; + +$/ = ""; + +open(LDIFH, $ldiffile) || die "$ldiffile: $!\n"; + +my $pos = 0; +my @valuepos; +while () { + my $value; + 1 while s/^($keyattr:.*)?\n /$1/im; # Handle line continuations + if (/^$keyattr(::?) (.*)$/im) { + $value = $2; + $value = decode_base64($value) if $1 eq '::'; + } + $value = lc($value) if $ciscmp; + push @valuepos, [ $value, $pos ]; + $pos = tell; +} + +sub cmpattr { $a->[0] cmp $b->[0] } +sub cmpattrnum { $a->[0] <=> $b->[0] } +my %canonicaldns; +sub cmpdn { + my $cadn = ($canonicaldns{$a->[0]} ||= lc(canonical_dn($a->[0]))); + my $cbdn = ($canonicaldns{$b->[0]} ||= lc(canonical_dn($b->[0]))); + $cadn cmp $cbdn; +} + +my $cmpfunc; +if ($args{d} || lc($keyattr) eq 'dn') { $cmpfunc = \&cmpdn } +elsif ($args{n}) { $cmpfunc = \&cmpattrnum } +else { $cmpfunc = \&cmpattr; } + +my @sorted; +@sorted = sort $cmpfunc @valuepos; + +foreach my $valuepos (@sorted) { + seek(LDIFH, $valuepos->[1], 0); + my $entry = ; + if ($sortattrs) { + $entry =~ s/\n //mg; # collapse line continuations + my @lines = grep(!/^#/, split(/\n/, $entry)); + my $dn = shift(@lines); + print "$dn\n", join("\n", sort @lines), "\n\n"; + } + else { + print $entry; + print "\n" if $entry !~ /\n\n$/; + } +} diff --git a/slave/process-vmware-ldap/rpm.dependencies b/slave/process-vmware-ldap/rpm.dependencies new file mode 100644 index 00000000..a6717984 --- /dev/null +++ b/slave/process-vmware-ldap/rpm.dependencies @@ -0,0 +1 @@ +perun-slave-base diff --git a/slave/process-vmware-ldap/short_desc b/slave/process-vmware-ldap/short_desc new file mode 100644 index 00000000..f443613a --- /dev/null +++ b/slave/process-vmware-ldap/short_desc @@ -0,0 +1 @@ +Package for perun service - process-vmware-ldap