Skip to content
Snippets Groups Projects
Commit 50bf8452 authored by Martin Haase's avatar Martin Haase
Browse files

move completed, via three towns

git-svn-id: https://textgridlab.org/svn/textgrid/trunk/middleware/tgauth@8232 7c539038-3410-0410-b1ec-0f2a7bf1c452
parent ef471a20
No related branches found
No related tags found
No related merge requests found
synchronizes Access Policy from openRBAC into POSIX ACL entries on a Grid Host. To be called by CRON with root access regularly.
ldap_conf_host = ldap.example.org
ldap_conf_port = 389
ldap_conf_binddn = "cn=manager,dc=example,dc=org"
ldap_conf_bindpw = secret
ldap_conf_is_tls = 1
ldap_conf_tls_cafile = /path/to/cacert/directory/chain.pem
ldap_conf_tls_verify = require
ldap_conf_tls_cypher = AES256-SHA
ldap_conf_basedn = dc=example,dc=org
ldap_conf_scope = sub
gridmapfilepath = /etc/grid-security/grid-mapfile
groupfilepath = /etc/group
slcs_dn_prefix = /C=DE/O=DFN-Verein/OU=DFN-PKI/OU=SLCS/
progname = "syncFromRBAC"
version = 0.2
date = "2010-12-06"
<author>
name = "Martin Haase"
org= "DAASI International GmbH"
mail = "martin.haase@daasi.de"
</author>
<copyright>
text1 = Copyright (c) 2010 DAASI International GmbH
text2 = This library is free software; you can redistribute it and/or \
modify it under the same terms as Perl itself.
</copyright>
progshortdescr = "Synchronize RBAC rules into POSIX ACLs"
<progdescription>
text01 = Syncs an external PDP's policy into extendeded Access Control entries
text02 = in the resource's file system. The PDP for this proof-of-concept system
text03 = is an RBAC implementation (openRBAC), which is part of TextGrid's
text04 = TG-auth* infrastructure.
</progdescription>
<bugs>
text = Please report bugs to martin.haase@daasi.de
</bugs>
<additions example>
text1 = "For getting this manpage: "
text2 = " syncFromRBAC.pl -h"
</additions>
<additions requirements>
text1 = "Following modules are required: "
text2 = Data::Dump
text3 = Net::LDAP
text4 = Set::Scalar
text5 = DAASIlib::CONF
text5 = DAASIlib::DATA
</additions>
<options loglevel>
key = "l"
must = 0
description = "Loglevel for controlling logmessages. Currently unused, use -d."
arg = 1
argtype = "skalar"
values = "debug, info"
default = "info"
</options>
<options logfile>
key = "f"
must = 0
description = "Name of the logfile with absolute or relative path. "
arg = 1
argtype = "filename_add_subdir_log"
default = "./log/syncFromRBAC.log"
</options>
<options debugmode>
key = "d"
must = 0
description = "sets debug mode to on"
arg = 0
default = 0
</options>
<options write_to_system>
key = "w"
must = 0
description = "does not make a dry run. Unless -w is specified, nothing is really written into the system"
arg = 0
default = 0
</options>
<options printhelp>
key = "h"
must = 0
description = "prints out the manpage"
arg = 0
</options>
<options helpfeature>
key = "H"
must = 0
description = "prints out description of the feature referenced by \
commandline flag or config file token. "
arg = 1
</options>
<options alternative_last_modify_timestamp>
key = "t"
must = 0
description = "Instead of reading from a file (see option -m), use this option to specify a timestamp directly on the command line in the same format."
arg = 1
</options>
<options last_modifyTimestamp_path>
key = "m"
must = 0
description = "File to read timestamp from when to start synchronizing. Format: YYYYMMDDhhmmssZ on first line of file. Needs to be Z time, aka GMT."
arg = 1
argtype = "filename_exist_subdir_etc"
default = "./last_LDAP_sync_timestamp.txt"
</options>
<options configfile>
key = "c"
must = 0
description = Name of the user config file with absolute or relative path.
arg = 1
argtype = "filename_exist_subdir_etc"
default = "./etc/syncFromRBAC.conf"
</options>
20101231235959Z
#!/usr/bin/perl -w
############################################
# Syncs an external PDP's policy (here: the contents of the LDAP Database
# of a TextGrid TG-auth* openRBAC instance) into group and extendeded Access
# Control entries in the Grid resource's file system.
#
# Author: Martin Haase / DAASI International GmbH / Gap-SLC
#
### Detailed function:
# * Every role in a TextGrid project will be mapped to a system group entry.
# * Every performer of this role will be added as a member to this group.
# * A TextGrid project is mapped to a directory under <project creator's home>/.textgrid/
# * A TextGrid object is mapped to a file in a project directory
# * Extended POSIX ACLs will be used to ensure each resource can be accessed by the right project members.
#
### Some remarks:
### A user's account must exist in the system's grid-mapfile. You can
#use the companion gap-SLC script, Pseudo_dgridmap.perl, to accomplish
#this.
### Currently only a subset of the accounts in the gri-mapfile is
#honored, i.e. only those which are issued by the Short-Lived
#Certificate Service (SLCS). Why? We need a mapping from the PDP. A
#user's identity in TG-auth* is by reference to his
#eduPersonPrincipalName, and the SLCS DNs contain exactly the
#ePPN. This way the mapping can be established.
### Permissions for roles are still hard-wired, as they are in
#TG-auth* as well
### we contact the LDAP database of TG-auth*/openRBAC
#directly. Re-implementors might wish to create dedicated accessor
#functions in the PDP they wish to contact.
### see syncFromRBAC -h for the man page
#
#
# History:
# version 0.1 2010-07-13 first proof-of-concept
# version 0.2 2010-12-06 separated configuration and code, documentation
# version 0.2b 2010-12-23 error handling, configuration with DAASIlib, first check-in to SVN
# version 0.2c 2010-12-27 further documentation
### imports ===========================================
use Data::Dump qw(dump);
use Net::LDAP qw(LDAP_SUCCESS LDAP_TIMELIMIT_EXCEEDED LDAP_SIZELIMIT_EXCEEDED LDAP_NO_SUCH_OBJECT LDAP_ALREADY_EXISTS );
use Set::Scalar;
use DAASIlib::CONF qw (is_debug);
use DAASIlib::Data;
# these two need: IO::Prompt, Log::Log4perl, DBI, DAASIlib::Gettext, Config::General
### configuration management ===========================
my $data = new DAASIlib::Data;
my ($progname, $progpath, $etcdir, $sysconfig) = $data->getProgramFiles($0);
my $conf = new DAASIlib::CONF;
$conf->loadConfig($sysconfig, $progpath, $etcdir);
# GLOBAL CONSTANTS ======================================
$DEBUG = 0;
$DRY_RUN = 1;
if (defined $conf->{data}->{debugmode} && $conf->{data}->{debugmode}) { $DEBUG = 1 }
if (defined $conf->{data}->{write_to_system} && $conf->{data}->{write_to_system}) { $DRY_RUN = 0 }
$gridmapfilepath = $conf->{data}->{gridmapfilepath};
$groupfilepath = $conf->{data}->{groupfilepath};
$logfilepath = $conf->{data}->{logfile};
$last_modifyTimestamp_path = $conf->{data}->{last_modifyTimestamp_path};
$alternative_last_modify_timestamp = $conf->{data}->{alternative_last_modify_timestamp};
# these roles are currently hard-wired in TG-auth*
@standardroles = (
{rolename => "Projektleiter", projectperms => "", resourceperms => ""},
{rolename => "Administrator", projectperms => "wx", resourceperms => ""},
{rolename => "Bearbeiter", projectperms => "rwx", resourceperms => "rw"},
{rolename => "Beobachter", projectperms => "rx", resourceperms => "r"},
);
my %ldap_config;
foreach $k (keys %{$conf->{data}}) {
if ($k =~ /^ldap_conf_(\S+)/) {
$ldap_config{$1} = $conf->{data}{$k};
}
}
$ldap_config{attribs} = ['*', 'modifyTimestamp'];
if (defined $alternative_last_modify_timestamp ) {
$last_modifyTimestamp = $alternative_last_modify_timestamp;
} else {
open LMTS, $last_modifyTimestamp_path;
$last_modifyTimestamp = <LMTS>;
chomp $last_modifyTimestamp;
close LMTS;
}
open LOG, ">>$logfilepath"; # used by logg();
if ($last_modifyTimestamp !~/^\d{4}[01]\d[0123]\d[012]\d[0-5]\d[0-5]\dZ$/) {
logg ("invalid timestamp ($last_modifyTimestamp) specified, should be YYYYMMDDhhmmssZ.");
exit 0;
}
logg ("Start synchronizing LDAP from timestamp ($last_modifyTimestamp)");
############## MAIN ===========================================
# user must exist in grid-mapfile, and we only take those DNs into account that are issued by DFN's SLCS
$userhash = parse_gridmap ( $gridmapfilepath, $conf->{data}->{slcs_dn_prefix});
$grouphash = parse_groupfile ($groupfilepath);
debugg( dump $userhash);
debugg ( "Log file is $logfilepath");
# query BEFORE ldapsearch, to ldap write operations in this second are accounted for
# doesnt matter if one entry gets processed twice
$now_timestamp = construct_timestamp (gmtime());
$result = getNewEntriesFromLDAP ( $last_modifyTimestamp, \%ldap_config);
foreach $e ($result->entries) {
if (is_role($e)) {
logg ("found role, ". $e->dn());
handle_role($e);
} elsif (is_resource($e)) {
logg ("found resource, ". $e->get_value("TGResourceURI"));
handle_resource($e);
} else {
# do nothing
}
}
if (not $DRY_RUN) {
logg ("write system timestamp of last ldapsearch ($now_timestamp) to file for next run");
open LMTS, ">$last_modifyTimestamp_path";
print LMTS $now_timestamp;
close LMTS;
} else {
logg ("This has been a DRY RUN, did NOT alter anything on the system nor write timestamp of last ldapsearch ($now_timestamp) to file. After verifying from the logs that everything is correct, you can force this with -w.");
}
close LOG;
exit 0;
# FUNCTIONS ======================================
# this function handles ordinary TextGridObjects. As CRUD has written
# them already, including the project directory they live in, no mkdir
# has to be issued.
sub handle_resource {
my $e = shift;
my ($tgpr, $dir, $uuid) = parse_uuid ($e->get_value("TGResourceUUID"));
logg ("Now setting ACLs of $dir and ./$uuid such that the right ones can access them.");
if (not exists $grouphash->{$tgpr."_Projektleiter"}) {
logg ( "WARNING: It seems that project $tgpr has not been created although you try to add resources! I will create it for you now...");
create_project ($tgpr);
}
sssys ("chgrp ${tgpr}_Projektleiter $dir $dir/$uuid $dir/${uuid}.meta");
sssys ("setfacl -m m::rwx $dir; setfacl -m m::rw $dir/$uuid $dir/${uuid}.meta");
sssys ("chmod 700 $dir; chmod 600 $dir/$uuid $dir/${uuid}.meta");
# TODO: query actual permissions instead of using the standard roles. However, this has no use as long as there is no Lab frontend for it...
foreach $r (@standardroles) {
if ($r->{projectperms}) {
sssys ( "setfacl -m g:${tgpr}_".$r->{rolename}.":".$r->{projectperms} ." $dir");
}
if ($r->{resourceperms}) {
sssys ( "setfacl -m g:${tgpr}_".$r->{rolename}.":".$r->{resourceperms} ." $dir/$uuid $dir/${uuid}.meta");
}
}
}
sub parse_uuid {
my $u = shift;
# format:
# gsiftp://ingrid.sub.uni-goettingen.de//home/ttest/.textgrid/TGPR29/a084e9c0-e7d4-3802-b16d-37a223414c82
if ($u =~ /gsiftp:\/\/ingrid.sub.uni-goettingen.de\/(\/home\/[^\/]+\/.textgrid)\/([^\/]+)\/(\S+)/) {
return ($2, "$1/$2", $3); # i.e. TGPR29, /home/ttest/.textgrid/TGPR29, a084e9c0-e7d4-3802-b16d-37a223414c82
} else {
return (0,0,0)
}
}
# this functions handles any change in a project role entry, either
# the project itself (it is a role as well in TG-auth*) or sub-roles.
# takes existing group entries into account and only makes a diff
sub handle_role {
my $e = shift;
my ($tgpr, $role) = parse_role_dn ($e->dn());
return if not $tgpr;
if (not $role) {
create_project($tgpr);
return;
}
my @eppnperformers = $e->get_value("rbacPerformer");
my @uidperformers = ();
foreach $eppn (@eppnperformers) {
if (exists $userhash->{$eppn} ) {
push @uidperformers, $userhash->{$eppn};
} else {
logg ("Warning: User $eppn not yet in Gridmap File");
}
}
my $newperformers = new Set::Scalar(@uidperformers);
if (not exists $grouphash->{$tgpr."_".$role}) {
logg ( "WARNING: It seems that project $tgpr has not been created although you try to add members to its $role role! I will create it for you now...");
create_project ($tgpr);
}
my $oldperformers = $grouphash->{$tgpr."_".$role};
debugg ("Old members in $tgpr: ". join ",", $oldperformers->members);
foreach $out ($oldperformers->difference($newperformers)->members) {
logg ("Throwing out $out of $role in $tgpr");
sssys ("/usr/sbin/groupmod -R $out ${tgpr}_".$role);
}
foreach $in ($newperformers->difference($oldperformers)->members) {
logg ("Adding $in to $role in $tgpr");
sssys ("/usr/sbin/groupmod -A $in ${tgpr}_".$role);
}
$grouphash->{$tgpr."_".$role} = $newperformers;
}
sub create_project {
$p = shift;
logg ("Creating role groups in $p IF they didnt exist before...");
foreach $r (@standardroles) {
if (not exists $grouphash->{$p."_".$r->{rolename}}) {
sssys("/usr/sbin/groupadd ${p}_".$r->{rolename});
$grouphash->{$p."_".$r->{rolename}} = new Set::Scalar(());
}
}
}
sub parse_role_dn {
my $dn = shift;
# format:
# rbacName=Bearbeiter,rbacName=TGPR29,rbacName=Projekt-Teilnehmer,ou=roles,dc=rbac,dc=textgrid,dc=de
if ($dn =~ m/rbacName=([^,]+),rbacName=([^,]+),rbacName=Projekt-Teilnehmer,ou=roles,dc=rbac,dc=textgrid,dc=de/) {
return ($2, $1);
} elsif ($dn =~ m/rbacName=([^,]+),rbacName=Projekt-Teilnehmer,ou=roles,dc=rbac,dc=textgrid,dc=de/) {
return ($1,0);
} else {
return (0,0);
}
}
sub is_role {
my $e = shift;
my @oc = $e->get_value("objectClass");
return in_array("rbacRole",@oc);
}
sub is_resource {
my $e = shift;
my @oc = $e->get_value("objectClass");
return in_array("TextGridResource",@oc)
}
sub in_array {
my $x = shift;
foreach $e (@_) {
return 1 if ($x eq $e);
}
return 0;
}
sub logg {
if ($DRY_RUN) {
print "\n".scalar localtime(). " LOG: ";
print shift;
print "\n";
} else {
print LOG "\n".scalar localtime(). " LOG: ";
print LOG shift;
print LOG "\n";
}
}
sub debugg {
return unless $DEBUG;
print "\n".scalar localtime(). " DEBUG: ";
print shift;
print "\n";
}
sub errorExit {
my $msg = shift;
logg ($msg);
exit 0;
}
sub sssys {
my $cmd = shift;
if ($DRY_RUN) {
print "\nSYSTEM CALL: >>>";
print $cmd;
print "<<<\n";
} else {
logg ($cmd);
system ($cmd);
}
}
sub parse_gridmap {
my ($path, $only) = @_;
my %h = ();
open F, $path;
while (<F>) {
# Format:
# "/C=DE/O=DFN-Verein/OU=DFN-PKI/OU=SLCS/OU=DAASI International GmbH/CN=Tanja Test - tanja.test@idp01.nds.daasi.de" ttest
if (m/$only/) {
m/ - (\S+)\" (\S+)/;
$h{$1} = $2; # eppn => uid
}
}
close F;
return \%h;
}
sub parse_groupfile {
my ($path) = shift;
my %h = ();
open F, $path;
while (<F>) {
# TGPR29_Bearbeiter:!:1008:aausprobier,bbenutzer
if (/(TGPR[^:]+):[^:]+:\d+:(.*)/) {
debugg ("found group $1, with members $2");
$h{$1} = new Set::Scalar(split ",", $2);
}
}
close F;
return \%h;
}
sub getNewEntriesFromLDAP {
my ($since, $rh_ldap_config) = @_;
my $ldap = ldapConnect($rh_ldap_config);
my $filter = "(modifyTimestamp>=$since)";
my $mesg = ldapSearch($ldap, $filter, $rh_ldap_config);
# ingrid:~ # ldapsearch -x -H ldap://:5389 -b dc=rbac,dc=textgrid,dc=de -D cn=manager,dc=rbac,dc=textgrid,dc=de -w XhNtmqigeXz61pI9jfwmLyqUOTeJ "*" "+" | less
foreach $entry ($mesg->entries) {
# $entry->dump;
debugg (dump $entry);
}
logg "Found ". $mesg->count() . " new entries since last call.";
return $mesg;
}
sub ldapConnect {
my ($rh_ldapdef) = @_;
my $uri = 'ldap://'.$rh_ldapdef->{host}.':'.$rh_ldapdef->{port};
my $ldap = Net::LDAP->new( $uri);
if ( !$ldap ) {
logg ("ERROR while creating connection to ldap server ".
"$$rh_ldapdef{host}:$$rh_ldapdef{port}");
errorExit ( " ERROR while creating connection to ldap server ".
"$$rh_ldapdef{host}:$$rh_ldapdef{port} \n");
return(undef);
}
if ( $rh_ldapdef->{is_tls} ) {
debugg("starting TLS ");
debugg("verify: $rh_ldapdef->{tls_verify}, cafile: $rh_ldapdef->{tls_cafile} cypher: $rh_ldapdef->{tls_cypher}");
my $tlsmesg = $ldap->start_tls ( verify => $rh_ldapdef->{tls_verify},
cafile => $rh_ldapdef->{tls_cafile},
ciphers => $rh_ldapdef->{tls_cypher});
if ( $tlsmesg->code != Net::LDAP::LDAP_SUCCESS ) {
logg ( "ERROR in ldap->start_tls: " . $tlsmesg->error );
errorExit( "ERROR in ldap->start_tls: " . $tlsmesg->error) ;
}
else {
debugg ( "Start_TLS operation succeeded");
debugg ( "tls cipher: " . $ldap->cipher . "\n" );
debugg ( "tls certificate: " . $ldap->certificate . "\n" );
}
}
my $mesg = $ldap->bind( $rh_ldapdef->{binddn},
password => $rh_ldapdef->{bindpw} );
unless ( $mesg->code == Net::LDAP::LDAP_SUCCESS ) {
logg ("Returning undef instead of ldap connection");
$ldap = undef;
errorExit( "Error in LDAP-Bind: ".$mesg->error);
}
return $ldap;
}
sub ldapSearch {
my ( $ldap, $filter, $rh_ldapdef ) = @_;
if ( ! $$rh_ldapdef{filter}) {
$$rh_ldapdef{filter}="(objectclass=*)";
}
my $mesg = $ldap->search(
base => $$rh_ldapdef{basedn},
scope => $$rh_ldapdef{scope},
filter => $filter,
attrs => $$rh_ldapdef{attribs},
# deref => $$rh_ldapdef{deref},
# sizelimit => $$rh_ldapdef{sizelimit},
# timelimit => $$rh_ldapdef{timelimit},
# typesonly => $$rh_ldapdef{is_typesonly},
# callback => $$rh_ldapdef{callback},
);
if ( $mesg->code == Net::LDAP::LDAP_SUCCESS ) {
debugg("search performed without error" );
} else {
errorExit ( "Error in search: ". $mesg->error);
}
return ($mesg);
}
### takes time as from localtime()
### returns ldap timstamp as in attribute modifyTimestamp
sub construct_timestamp {
my ($nsec,$nmin,$nhours,$nmday,$nmon,$nyear) = @_;
my $ntime = sprintf("%04d%02d%02d%02d%02d%02dZ",
($nyear+1900),($nmon+1),
$nmday,$nhours,$nmin,$nsec);
return $ntime;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment