Summary
In this example from University of Pennsylvania, Grouper manages permissions for actions used in Linux commands. The Grouper permissions are exported to a text file and kept in sync (batch and real time) via the grouperClient on the linux server.
Using
For someone to restart a tomcat, call a script that uses sudo to call the command to restart a tomcat:
[mchyzer@lukes clusterLinux]$ clusterTomcat tomcat_directory restart
To onboard someone, add a user to the sudoers file. e.g. allow user mchyzer to restart tomcats (that are allowed in Grouper).
mchyzer ALL=(appadmin) /opt/appserv/binMgmt/clusterTomcatHelper.sh *
Maybe this should be a wildcard so anyone can do this since permissions are in Grouper and you dont have to do this for each user... either way
ALL ALL=(appadmin) /opt/appserv/binMgmt/clusterTomcatHelper.sh *
At this point the user is not allowed to do anything, since they have no permissions in Grouper, and clusterTomcatHelper.sh (below) uses Grouper permissions to decide if the user can perform the action.
Go to the Grouper permissions screen, and add the user to the role for this server
Assign permissions to the user. Note, the resource is which tomcat the user is allowed to perform actions on
Wait max two minutes, and the permissions file on the server will be updated, e.g.
[appadmin@server clusterLinux]$ more /opt/appserv/binMgmt/clusterLinux/clusterLinuxRules.sh # File automatically generated from PennGroups... clusterLinux__mchyzer__app_directory__all=true clusterLinux__mchyzer__app_directory__restart=true clusterLinux__mchyzer__app_directory__start=true clusterLinux__mchyzer__app_directory__status=true clusterLinux__mchyzer__app_directory__stop=true clusterLinux__mchyzer__app_fastUnit__status=true [appadmin@lukes clusterLinux]$
Note the file is concerned with the user, resource (which tomcat), and action (start|stop|restart|status). If there are other rollup actions (e.g. "all") then it will be ignored by the calling script since you cant perform "all" on the tomcat service.
Architecture
Diagram of permissions in use, cron updates, and XMPP real time updates

One-time setup on the server where the permissions are needed
Create a permission definition:
Add action inheritance so "all" implies the others
Create a role for an environment
Add permission names for each application, in this case prefix the real app name with "app_"
Assign permissions to a user in the role
Permissions client real time and daemon process
Here is the source file:
/** * @author mchyzer * $Id: ClusterLinuxLogic.java,v 1.4 2012/05/21 17:01:24 mchyzer Exp $ */ package edu.upenn.isc.clusterLinux; import java.io.File; import java.sql.Timestamp; import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.TreeSet; import org.jivesoftware.smack.packet.Message; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.StatefulJob; import edu.internet2.middleware.grouperClient.api.GcGetPermissionAssignments; import edu.internet2.middleware.grouperClient.api.GcGetSubjects; import edu.internet2.middleware.grouperClient.collections.MultiKey; import edu.internet2.middleware.grouperClient.util.GrouperClientUtils; import edu.internet2.middleware.grouperClient.ws.beans.WsGetPermissionAssignmentsResults; import edu.internet2.middleware.grouperClient.ws.beans.WsGetSubjectsResults; import edu.internet2.middleware.grouperClient.ws.beans.WsPermissionAssign; import edu.internet2.middleware.grouperClient.ws.beans.WsSubject; import edu.internet2.middleware.grouperClient.ws.beans.WsSubjectLookup; import edu.internet2.middleware.grouperClientExt.org.apache.commons.logging.Log; import edu.internet2.middleware.grouperClientExt.xmpp.GrouperClientXmppMain; import edu.internet2.middleware.grouperClientExt.xmpp.GrouperClientXmppMessageHandler; /** * */ public class ClusterLinuxLogic implements Job, StatefulJob { /** * @param args */ public static void main(String[] args) { log.debug("Starting cluster linux permissions daemon"); //schedule full refresh job on startup performFullRefresh(); scheduleQuartzJob(); //do xmpp loop xmppLoop(); } /** * schedule the full refresh job, e.g. nightly */ private static void scheduleQuartzJob() { String jobName = "clusterLinuxFullRefreshJob"; String quartzCronString = GrouperClientUtils.propertiesValue("clusterLinux.fullRefreshQuartzCron", false); log.debug("Scheduling cluster linux permissions daemon for quartzCron string: " + quartzCronString); GrouperClientXmppMain.scheduleJob(jobName, quartzCronString, ClusterLinuxLogic.class); } /** timer */ private static Timer timer = null; /** timer scheduled for */ private static long timerScheduledFor = -1; /** * do a full refresh in one minute (batch subsequent requests) */ public static synchronized void scheduleFullRefresh() { //if it is already scheduled, then we are all good if (timer != null) { log.debug("Job is already scheduled at " + new Date(timerScheduledFor).toString() + ", exiting"); return; } timer = new Timer(true); int timeInFuture = 1000*60; timerScheduledFor = System.currentTimeMillis() + timeInFuture; //schedule in 60 seconds timer.schedule(new TimerTask() { @Override public void run() { performFullRefresh(); } }, timeInFuture); } /** * base folder for grouper * @return the folder, e.g. school:apps:clusterLinux */ public static String grouperFolderBase() { return GrouperClientUtils.propertiesValue("clusterLinux.grouperFolderBase", true); } /** * do a full refresh in one minute (batch subsequent requests) */ public static synchronized void performFullRefresh() { try { //let another timer be scheduled, whether from schedule or xmpp timer = null; String nameOfAttributeDef = grouperFolderBase() + ":permissions:tomcats:tomcatPermissionDef"; String theRoleName = grouperFolderBase() + ":clusterLinuxRoles:" + GrouperClientUtils.propertiesValue("clusterLinux.environmentRoleExtension", true); WsGetPermissionAssignmentsResults wsGetPermissionAssignmentsResults = new GcGetPermissionAssignments().addAttributeDefName(nameOfAttributeDef) .addRoleName(theRoleName).addSubjectAttributeName("PENNNAME").execute(); WsSubject[] wsSubjects = wsGetPermissionAssignmentsResults.getWsSubjects(); //note, there is a bug where subjects arent returned... go get them wsSubjects = ClusterLinuxLogic.retrieveSubjectsFromServer(wsGetPermissionAssignmentsResults); //lets make the permissions file StringBuilder fileContents = new StringBuilder("# File automatically generated from PennGroups...\n\n"); //lets sort these so they are easier to manage Set<String> lines = new TreeSet<String>(); if (log.isDebugEnabled()) { log.debug("Received " + GrouperClientUtils.length(wsGetPermissionAssignmentsResults.getWsPermissionAssigns()) + " permission entries from Grouper"); } for (WsPermissionAssign wsPermissionAssign : GrouperClientUtils.nonNull( wsGetPermissionAssignmentsResults.getWsPermissionAssigns(), WsPermissionAssign.class)) { //only worried about personal access at this time if (!GrouperClientUtils.equals(wsPermissionAssign.getSourceId(), "pennperson")) { continue; } //get the subject WsSubject wsSubject = ClusterLinuxLogic.retrieveSubject( wsSubjects, wsPermissionAssign.getSourceId(), wsPermissionAssign.getSubjectId()); if (wsSubject == null) { throw new RuntimeException("Why is wsSubject null??? " + wsPermissionAssign.getSourceId() + wsPermissionAssign.getSubjectId()); } String pennname = GrouperClientUtils.subjectAttributeValue(wsSubject, wsGetPermissionAssignmentsResults.getSubjectAttributeNames(), "PENNNAME"); //not sure why there wouldnt be a pennname, but if not, then skip if (GrouperClientUtils.isBlank(pennname)) { continue; } //ok, we have pennname, extension, action, lets write the line String extension = GrouperClientUtils.extensionFromName(wsPermissionAssign.getAttributeDefNameName()); String action = wsPermissionAssign.getAction(); lines.add("clusterLinux__" + pennname + "__" + extension + "__" + action + "=true"); } for (String line : lines) { fileContents.append(line).append("\n"); } if (log.isDebugEnabled()) { log.debug("Generated file has " + GrouperClientUtils.length(lines) + " lines"); } File fileToSave = new File(GrouperClientUtils.propertiesValue("clusterLinux.fileToSave", true)); //save this to file (try 3 times) for (int i=0;i<3;i++) { try { boolean updated = GrouperClientUtils.saveStringIntoFile(fileToSave, fileContents.toString(), true, true); if (updated) { log.debug("File: " + fileToSave.getAbsolutePath() + " was saved since there were updates from Grouper"); } else { log.debug("File: " + fileToSave.getAbsolutePath() + " was not saved since there were no changes from Grouper"); } //we are done break; } catch (Exception e) { log.error("Error saving file", e); } GrouperClientUtils.sleep(2000); } } catch (Exception e) { log.error("Error in full refresh", e); } } /** * logger */ private static Log log = GrouperClientUtils.retrieveLog(ClusterLinuxLogic.class); /** * this will be run when the quertz nightly job fires * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) */ // @Override public void execute(JobExecutionContext context) throws JobExecutionException { String jobName = null; try { jobName = context.getJobDetail().getName(); if (log.isDebugEnabled()) { log.debug("Scheduling full refresh on job: " + jobName); } ClusterLinuxLogic.scheduleFullRefresh(); } catch (RuntimeException re) { log.error("Error in job: " + jobName, re); throw re; } } /** * connect to xmpp, listen for messages from a certain sender (the grouper server) */ private static void xmppLoop() { GrouperClientXmppMain.xmppLoop(new GrouperClientXmppMessageHandler() { @Override public void handleMessage(Message message) { log.debug("Received message: " + message.getBody()); //whatever message we get, we know it is from the right sender based on //config, so just schedule a full refresh a minute from now if its not already scheduled scheduleFullRefresh(); } }); } /** * lookup a subject by subject id and source id * @param subjects * @param sourceId * @param subjectId * @return probably shouldnt be null, but if cant be found, then will be null */ public static WsSubject retrieveSubject(WsSubject[] subjects, String sourceId, String subjectId) { for (WsSubject wsSubject : GrouperClientUtils.nonNull(subjects, WsSubject.class)) { if (GrouperClientUtils.equals(sourceId, wsSubject.getSourceId()) && GrouperClientUtils.equals(subjectId, wsSubject.getId())) { return wsSubject; } } return null; } /** * until Penn is upgraded to Grouper 2.1, we need another query to get subjects based on bug in grouper. * Once this is fixed this wont be needed, the subjects will be in the permissions response * @param wsGetPermissionAssignmentsResults * @return subjects */ public static WsSubject[] retrieveSubjectsFromServer(WsGetPermissionAssignmentsResults wsGetPermissionAssignmentsResults) { //make sure no dupes Set<MultiKey> subjectSourceAndIds = new HashSet<MultiKey>(); for (WsPermissionAssign wsPermissionAssign : wsGetPermissionAssignmentsResults.getWsPermissionAssigns()) { if (GrouperClientUtils.equals("pennperson", wsPermissionAssign.getSourceId())) { subjectSourceAndIds.add(new MultiKey(wsPermissionAssign.getSourceId(), wsPermissionAssign.getSubjectId())); } } GcGetSubjects gcGetSubjects = new GcGetSubjects().addSubjectAttributeName("PENNNAME"); //add those to the get subjects request for (MultiKey subjectSourceAndId : subjectSourceAndIds) { gcGetSubjects.addWsSubjectLookup(new WsSubjectLookup((String)subjectSourceAndId.getKey(1), (String)subjectSourceAndId.getKey(0), null)); } WsGetSubjectsResults wsGetSubjectsResults = gcGetSubjects.execute(); return wsGetSubjectsResults.getWsSubjects(); } }
Compile that into a jar:
<project name="clusterLinux" default="build" basedir="."> <!-- declare the ant-contrib tasks --> <taskdef resource="net/sf/antcontrib/antlib.xml" /> <!-- copy build.properties if not there already --> <if><not><available file="build.properties" /></not> <then><copy file="build.example.properties" tofile="build.properties" /></then> </if> <!-- Grouper Global Build Properties --> <property file="${basedir}/build.properties"/> <target name="build" description="full build" depends="init,clean,compile,jarPrepare,jar"> </target> <target name="init"> <tstamp /> <property name="main.source" value="src" /> <property name="main.lib" value="lib" /> <property name="main.bin" value="dist/bin" /> <property name="main.outputDir" value="dist" /> <property name="main.appName" value="clusterLinux" /> <property name="main.jarFile" value="${main.outputDir}/${main.appName}.jar" /> <path id="main.classpath"> <fileset dir="${main.lib}"> <include name="**/*.jar" /> </fileset> </path> </target> <target name="clean" depends="init"> <mkdir dir="${main.bin}" /> <delete dir="${main.bin}" /> <mkdir dir="${main.bin}" /> </target> <target name="compile"> <mkdir dir="${main.outputDir}" /> <mkdir dir="${main.bin}" /> <javac target="1.6" srcdir="${main.source}" destdir="${main.bin}" debug="true" > <classpath refid="main.classpath" /> </javac> </target> <target name="jarPrepare"> <mkdir dir="${main.bin}" /> <copy todir="${main.bin}"> <fileset dir="${main.source}"> <include name="**/*.java"/> <!-- source --> </fileset> </copy> <mkdir dir="${main.bin}/clusterLinux" /> <copy todir="${main.bin}/clusterLinux" file="build.xml" /> </target> <target name="jar"> <tstamp> <format property="the.timestamp" pattern="yyyy/MM/dd HH:mm:ss" /> </tstamp> <jar jarfile="${main.jarFile}" duplicate="fail"> <fileset dir="${main.bin}" /> <manifest> <attribute name="Built-By" value="${user.name}"/> <attribute name="Implementation-Vendor" value="Penn"/> <attribute name="Implementation-Title" value="clusterLinux"/> <attribute name="Build-Timestamp" value="${the.timestamp}"/> </manifest> </jar> <echo message="Output is: ${main.jarFile}" /> </target> </project>
Put that jar, and the other required jars from the grouper client for XMPP and quartz cron into a dir on the server:
[appadmin@lukes clusterLinux]$ ls -lat total 6964 -rw-r--r-- 1 appadmin users 11099 May 21 14:29 clusterLinux.jar drwxrwxr-x 2 appadmin appadmin 4096 May 21 14:04 . -rw-r--r-- 1 appadmin users 173783 May 21 14:04 commons-beanutils.jar -rw-r--r-- 1 appadmin users 570463 May 21 14:04 commons-collections.jar -rw-r--r-- 1 appadmin users 468109 May 21 14:04 commons-lang.jar -rw-r--r-- 1 appadmin users 131078 May 21 14:04 commons-logging.jar -rw-r--r-- 1 appadmin users 86542 May 21 14:04 ezmorph.jar -rw-r--r-- 1 appadmin users 2649669 May 21 14:04 grouperClient.jar -rw-r--r-- 1 appadmin users 255813 May 21 14:04 json-lib.jar -rw-r--r-- 1 appadmin users 8374 May 21 14:04 jta.jar -rw-r--r-- 1 appadmin users 391834 May 21 14:04 log4j.jar -rw-r--r-- 1 appadmin users 792769 May 21 14:04 quartz.jar -rw-r--r-- 1 appadmin users 1381464 May 21 14:04 smack.jar -rw-r--r-- 1 appadmin users 753 May 21 13:48 log4j.properties -rw-r--r-- 1 appadmin users 17402 May 21 13:47 grouper.client.properties -rw-r--r-- 1 appadmin users 17390 May 21 13:46 grouper.client.properties~ -rwxr-xr-x 1 appadmin users 3124 May 21 13:44 clusterLinuxPermissions -rwxr-xr-x 1 appadmin users 306 May 21 13:41 clusterLinuxCommand.sh
Have a log4j.properties
## Grouper API error logging log4j.appender.grouper_error = org.apache.log4j.RollingFileAppender log4j.appender.grouper_error.File = /opt/appserv/local/clusterLinux/clusterLinux.log log4j.appender.grouper_error.MaxFileSize = 1000KB log4j.appender.grouper_error.MaxBackupIndex = 1 log4j.appender.grouper_error.layout = org.apache.log4j.PatternLayout log4j.appender.grouper_error.layout.ConversionPattern = %d{ISO8601}: [%t] %-5p %C{1}.%M(%L) - %x - %m%n # Loggers ## Default logger; will log *everything* log4j.rootLogger = ERROR, grouper_error ## All Internet2 (warn to grouper_error per default logger) #log4j.logger.edu.internet2.middleware = WARN log4j.logger.edu.upenn.isc.clusterLinux = DEBUG
Configure the grouper.client.properties to point to the Grouper WS, XMPP, and configuration for this daemon (here are snippets)
######################################## ## Web service Connection settings ######################################## # url of web service, should include everything up to the first resource to access # e.g. http://groups.school.edu:8090/grouperWs/servicesRest # e.g. https://groups.school.edu/grouperWs/servicesRest grouperClient.webService.url = https://grouper.school.edu/grouperWs/servicesRest # kerberos principal used to connect to web service # e.g. name/server.whatever.upenn.edu grouperClient.webService.kerberosPrincipal = clusterLinuxGrouper/school.edu # password for shared secret authentication to web service # or you can put a filename with an encrypted password grouperClient.webService.password = XXXXXXXXXXX ... ################################ ## XMPP client settings ## Note: you need the smack.jar in your classpath, see the grouper xmpp wiki for usage ## https://spaces.at.internet2.edu/display/Grouper/Grouper+XMPP+notifications+v1.6.0 ################################ ## general xmpp configuration grouperClient.xmpp.server.host = jabber.school.edu grouperClient.xmpp.server.port = 5222 grouperClient.xmpp.user = clusterLinuxProcess@school.edu # note, pass can be in an external file with morphstring grouperClient.xmpp.pass = xxxxxxxxxxxxx grouperClient.xmpp.resource = clusterLinuxTest # note, you need the exact id and resource here or it wont match grouperClient.xmpp.trustedMessagesFromJabberIds = grouperjabber@school.edu/grouperServer ################################ ## Cluster linux settings ################################ # when should the full refresh kick off clusterLinux.fullRefreshQuartzCron = 0 0 4 * * ? # cluster linux base folder in grouper clusterLinux.grouperFolderBase = XXXXXXXX:apps:fast:clusterLinux # must be a role in the clusterLinuxRoles folder under the base for this env clusterLinux.environmentRoleExtension = cloudTestenvUser # file that holds the BASH env variables clusterLinux.fileToSave = /somefolder/clusterLinuxPermissions.sh
Here is the init.d script (which is symlinked to the real location): /opt/appserv/binMgmt/clusterLinux/clusterLinuxPermissions
#!/bin/sh # # Startup script for the Tomcat Server # # chkconfig: - 86 14 # description: Cluster Linux Permissions # processname: # pidfile: # config: # Tomcat # description: Keeps Grouper permissions in sync with file on linux server runningUser=`/usr/bin/whoami` # we only want appadmin or root to be able to do this if [ "$runningUser" != "appadmin" ]; then if [ "$runningUser" != "root" ]; then echo "ERROR: only user appadmin or root can run administer the clusterLinuxPermissions service: '$runningUser'" echo exit 0 fi fi case "$1" in start) echo -n "Starting Cluster linux permissions service: " echo #if there is a pid file, then stop this service if [ -f /opt/appserv/local/clusterLinux/clusterLinuxDaemon.pid ]; then $0 stop sleep 5 fi # if not appadmin, then we must be root if [ ! "$runningUser" == "appadmin" ]; then su appadmin -c /opt/appserv/binMgmt/clusterLinux/clusterLinuxCommand.sh else # if not root then we must be appadmin /opt/appserv/binMgmt/clusterLinux/clusterLinuxCommand.sh fi echo ;; stop) pid=`cat /opt/appserv/local/clusterLinux/clusterLinuxDaemon.pid` /bin/kill $pid #loop until the program is dead waitingForExit=true iterator=1 while [ "$waitingForExit" == "true" ]; do processId=`/bin/ps -fp $pid | grep java | cut -c10-15` if [ -n "$processId" ]; then echo Waiting for exit... else waitingForExit=false fi if [ "$iterator" -gt "10" ]; then waitingForExit=false fi let "iterator += 1" sleep 1 done rm /opt/appserv/local/clusterLinux/clusterLinuxDaemon.pid echo ;; status) if [ -f /opt/appserv/local/clusterLinux/clusterLinuxDaemon.pid ]; then pid=`cat /opt/appserv/local/clusterLinux/clusterLinuxDaemon.pid` processId=`/bin/ps -fp $pid | grep java | cut -c10-15` if [ -n "$processId" ]; then echo "clusterLinuxPermissions is running under process ID $processId" else echo "clusterLinuxPermissions is not running" fi else echo "clusterLinuxPermissions is not running" fi ;; restart) echo -n "Restarting clusterLinuxPermissions: " $0 stop sleep 5 $0 start echo "done." ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 esac
Symlink to init.d
[root@server init.d]# ln -s /opt/appserv/binMgmt/clusterLinux/clusterLinuxPermissions clusterLinuxPermissions [root@server init.d]# chkconfig --add clusterLinuxPermissions [root@server init.d]# chkconfig --levels 345 clusterLinuxPermissions on
Start the process:
[appadmin@server clusterLinux]$ /sbin/service clusterLinuxPermissions start Starting Cluster linux permissions service: [appadmin@server clusterLinux]$ more /opt/appserv/local/clusterLinux/clusterLinuxDaemon.pid 25507 [appadmin@server clusterLinux]$ ps -ef | grep clusterLinux | grep -v "grep" appadmin 25507 1 4 17:06 pts/9 00:00:04 /opt/appserv/common/java6/bin/java -cp /opt/appserv/binMgmt/clusterLinux:/opt/appserv/binMgmt/clusterLinux/* edu.upenn.isc.clusterLinux.ClusterLinuxLogic [appadmin@server clusterLinux]$
Stop the process:
[appadmin@lukes clusterLinux]$ /sbin/service clusterLinuxPermissions stop Waiting for exit... [appadmin@lukes clusterLinux]$ more /opt/appserv/local/clusterLinux/clusterLinuxDaemon.pid /opt/appserv/local/clusterLinux/clusterLinuxDaemon.pid: No such file or directory [appadmin@lukes clusterLinux]$ ps -ef | grep clusterLinux | grep -v "grep" [appadmin@lukes clusterLinux]$
At this point the permissions file should exist
[appadmin@server clusterLinux]$ more /opt/appserv/binMgmt/clusterLinux/clusterLinuxRules.sh # File automatically generated from PennGroups... clusterLinux__mchyzer__app_directory__all=true clusterLinux__mchyzer__app_directory__restart=true clusterLinux__mchyzer__app_directory__start=true clusterLinux__mchyzer__app_directory__status=true clusterLinux__mchyzer__app_directory__stop=true clusterLinux__mchyzer__app_fastUnit__status=true [appadmin@lukes clusterLinux]$
That script is what the end user script calls
[mchyzer@lukes clusterLinux]$ clusterTomcat tomcat_directory restart
The command should sudo su as another user:
/usr/bin/sudo -u appadmin /somedir/whatever/clusterTomcatHelper.sh $1 $2
Another shell script (clusterTomcatHelper.sh) can be coded to use the rules from the permissions file:
#!/bin/bash # this script *must* run as appadmin or root runningUser=`/usr/bin/whoami` # sudo should set this variable loggedInUser=$SUDO_USER if [ "$runningUser" != "appadmin" ]; then echo "ERROR: only user appadmin can run this command: '$runningUser'" echo exit 0 fi # this script will start / stop / get status of tomcat if [[ $# -ne "2" && $# -ne "3" ]]; then echo echo "ERROR: pass in the tomcat or application to affect (appName, tomcat_b, ... etc) and" echo "an argument to tomcat as the command line arg" echo "e.g. status, start, stop, restart" echo "optionally you can give a server to run on, or blank for all in cluster" echo "or 'all' to run on all nodes in every cluster (if tomcat has changed clusters)" echo "e.g. clusterTomcatHelper.sh tomcat_b status" echo "e.g. clusterTomcatHelper.sh secureShare status theprince" echo exit 1 fi argAppName=$1 argAction=$2 argServer=$3 # make sure the argument has no spaces or special chars function validateArg() { if [[ "${1}" == "" ]]; then return; fi #echo checking "'$1'" if [[ ! $1 =~ ^[a-zA-Z0-9_-]*$ ]]; then echo "Invalid arg '$1'" exit 1; fi } validateArg "$argAppName" validateArg "$argAction" validateArg "$argServer" # convert the arg to the application name, e.g. secureShare appName=`/opt/appserv/common/binGroovy/clusterGetApplicationName.groovy $argAppName` #echo "'$appName'" appTomcatName=tomcat_"$appName" #echo $appTomcatName # get the env var name # e.g. clusterLinux__mchyzer__app_directory__all=true securityVarName=clusterLinux__"$loggedInUser"__app_"$appName"__"$argAction" #echo $securityVarName # see if it is set, if it is, it is a security problem since the ACL file should set it eval securityVarValue=\$$securityVarName if [[ ! "${securityVarValue}" == "" ]]; then echo "Why is this set? $securityVarValue???" exit 1; fi source /opt/appserv/binMgmt/clusterLinuxRules.sh eval securityVarValue=\$$securityVarName if [[ ! "$securityVarValue" == "true" ]]; then echo "User is not allowed to run the command, contact the sysadmin to get permission: $loggedInUser, $appName, $argAction" exit 1 fi # ok, we are allowed to execute the command, go for it
...
sdf
Change log consumer
This is the part that is a jar installed in the Grouper loader and an edit in the grouper-loader.properties to send permissions to XMPP
/** * @author mchyzer * $Id: ClusterLinuxChangeLogConsumer.java,v 1.2 2012/05/21 17:01:24 mchyzer Exp $ */ package edu.upenn.isc.clusterLinuxClc; import java.util.List; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import edu.internet2.middleware.grouper.app.loader.GrouperLoaderConfig; import edu.internet2.middleware.grouper.changeLog.ChangeLogConsumerBase; import edu.internet2.middleware.grouper.changeLog.ChangeLogEntry; import edu.internet2.middleware.grouper.changeLog.ChangeLogLabels; import edu.internet2.middleware.grouper.changeLog.ChangeLogProcessorMetadata; import edu.internet2.middleware.grouper.changeLog.ChangeLogTypeBuiltin; import edu.internet2.middleware.grouper.util.GrouperUtil; import edu.internet2.middleware.grouper.xmpp.XmppConnectionBean; /** * change log consumer to listen for specific gruoper actions that should tell linux to refresh * their permissions */ public class ClusterLinuxChangeLogConsumer extends ChangeLogConsumerBase { /** */ private static final Log LOG = GrouperUtil.getLog(ClusterLinuxChangeLogConsumer.class); /** * @see edu.internet2.middleware.grouper.changeLog.ChangeLogConsumerBase#processChangeLogEntries(java.util.List, edu.internet2.middleware.grouper.changeLog.ChangeLogProcessorMetadata) */ @Override public long processChangeLogEntries(List<ChangeLogEntry> changeLogEntryList, ChangeLogProcessorMetadata changeLogProcessorMetadata) { //identifies which config is running, multiple could be running String consumerName = changeLogProcessorMetadata.getConsumerName(); long currentId = -1; XmppConnectionBean xmppConnectionBean = null; String recipient = null; String grouperFolderBase = null; { String grouperFolderBaseConfigName = "changeLog.consumer." + consumerName + ".clusterLinux.grouperFolderBase"; grouperFolderBase = GrouperLoaderConfig.getPropertyString(grouperFolderBaseConfigName, true); } boolean sendMessage = false; for (ChangeLogEntry changeLogEntry : changeLogEntryList) { //try catch so we can track that we made some progress try { currentId = changeLogEntry.getSequenceNumber(); //only need to send once if (sendMessage) { continue; } //this might be a little aggressive, but basically if a permission or member changes in the clusterLinux folder of //grouper then send a refresh message //note: not using constants for permissions so it works in 2.0 and 2.1... if (StringUtils.equals("permission", changeLogEntry.getChangeLogType().getChangeLogCategory()) && (StringUtils.equals("addPermission", changeLogEntry.getChangeLogType().getActionName()) || StringUtils.equals("deletePermission", changeLogEntry.getChangeLogType().getActionName()))) { String permissionName = changeLogEntry.retrieveValueForLabel("attributeDefNameName"); if (permissionName != null && permissionName.startsWith(grouperFolderBase)) { sendMessage = true; } if (LOG.isDebugEnabled()) { LOG.debug("Processing changeLog #" + currentId + ", permissionName: " + permissionName + ", grouperFolderBase: " + grouperFolderBase + ", message: " + changeLogEntry.getChangeLogType().getChangeLogCategory() + "." + changeLogEntry.getChangeLogType().getActionName() + ", sendMessage: " + sendMessage ); } } else if (changeLogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.MEMBERSHIP_ADD) || changeLogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.MEMBERSHIP_DELETE) || changeLogEntry.equalsCategoryAndAction(ChangeLogTypeBuiltin.MEMBERSHIP_UPDATE)) { String roleName = changeLogEntry.retrieveValueForLabel(ChangeLogLabels.MEMBERSHIP_ADD.groupName); if (roleName != null && roleName.startsWith(grouperFolderBase)) { sendMessage = true; } if (LOG.isDebugEnabled()) { LOG.debug("Processing changeLog #" + currentId + ", roleName: " + roleName + ", grouperFolderBase: " + grouperFolderBase + ", message: " + changeLogEntry.getChangeLogType().getChangeLogCategory() + "." + changeLogEntry.getChangeLogType().getActionName() + ", sendMessage: " + sendMessage ); } } else if (StringUtils.equals("permission", changeLogEntry.getChangeLogType().getChangeLogCategory()) && StringUtils.equals("permissionChangeOnRole", changeLogEntry.getChangeLogType().getActionName())) { //note, this is 2.1+ String roleName = changeLogEntry.retrieveValueForLabel("roleName"); if (roleName != null && roleName.startsWith(grouperFolderBase)) { sendMessage = true; } if (LOG.isDebugEnabled()) { LOG.debug("Processing changeLog #" + currentId + ", roleName: " + roleName + ", grouperFolderBase: " + grouperFolderBase + ", message: " + changeLogEntry.getChangeLogType().getChangeLogCategory() + "." + changeLogEntry.getChangeLogType().getActionName() + ", sendMessage: " + sendMessage ); } } else { if (LOG.isDebugEnabled()) { LOG.debug("Processing changeLog #" + currentId + ", " + changeLogEntry.getChangeLogType().getChangeLogCategory() + "." + changeLogEntry.getChangeLogType().getActionName() + ", sendMessage: " + sendMessage ); } } //is there something to send? if (sendMessage) { String message = "Update permissions"; if (xmppConnectionBean == null) { recipient = GrouperLoaderConfig.getPropertyString("changeLog.consumer." + consumerName + ".publisher.recipient", ""); String xmppServer = GrouperLoaderConfig.getPropertyString("xmpp.server.host"); int port = GrouperLoaderConfig.getPropertyInt("xmpp.server.port", -1); String username = GrouperLoaderConfig.getPropertyString("xmpp.user", ""); String password = GrouperLoaderConfig.getPropertyString("xmpp.pass", ""); String resource = GrouperLoaderConfig.getPropertyString("xmpp.resource", ""); xmppConnectionBean = new XmppConnectionBean(xmppServer, port, username, resource, password); } xmppConnectionBean.sendMessage(recipient, message); } } catch (Exception e) { //we unsuccessfully processed this record... decide whether to wait, throw, ignore, log, etc... LOG.error("problem with id: " + currentId, e); changeLogProcessorMetadata.setHadProblem(true); changeLogProcessorMetadata.setRecordException(e); changeLogProcessorMetadata.setRecordExceptionSequence(currentId); //stop here return currentId; //continue } } return currentId; } }
Build that consumer into a jar:
<project name="clusterLinuxChangeLogConsumer" default="build" basedir="."> <!-- declare the ant-contrib tasks --> <taskdef resource="net/sf/antcontrib/antlib.xml" /> <!-- copy build.properties if not there already --> <if><not><available file="build.properties" /></not> <then><copy file="build.example.properties" tofile="build.properties" /></then> </if> <!-- Grouper Global Build Properties --> <property file="${basedir}/build.properties"/> <target name="build" description="full build" depends="init,clean,compile,jarPrepare,jar"> </target> <target name="init"> <tstamp /> <property name="main.sourceChangeLogDir" value="../sourceChangeLogConsumer" /> <property name="main.lib" value="../lib" /> <property name="main.binChangeLogDir" value="../dist/binChangeLog" /> <property name="main.outputDir" value="../dist" /> <property name="main.appName" value="clusterLinuxChangeLog" /> <property name="main.jarFileChangeLog" value="${main.outputDir}/${main.appName}.jar" /> <path id="main.changeLogClasspath"> <fileset dir="${main.lib}"> <include name="**/*.jar" /> </fileset> <fileset file="${grouper.jar.location}" /> </path> </target> <target name="clean" depends="init"> <mkdir dir="${main.binChangeLogDir}" /> <delete dir="${main.binChangeLogDir}" /> <mkdir dir="${main.binChangeLogDir}" /> </target> <target name="compile"> <mkdir dir="${main.outputDir}" /> <mkdir dir="${main.binChangeLogDir}" /> <javac target="1.6" srcdir="${main.sourceChangeLogDir}" destdir="${main.binChangeLogDir}" debug="true" > <classpath refid="main.changeLogClasspath" /> </javac> </target> <target name="jarPrepare"> <mkdir dir="${main.binChangeLogDir}" /> <copy todir="${main.binChangeLogDir}"> <fileset dir="${main.sourceChangeLogDir}"> <include name="**/*.java"/> <!-- source --> </fileset> </copy> <mkdir dir="${main.binChangeLogDir}/clusterLinux" /> <copy todir="${main.binChangeLogDir}/clusterLinux" file="build.xml" /> </target> <target name="jar"> <tstamp> <format property="the.timestamp" pattern="yyyy/MM/dd HH:mm:ss" /> </tstamp> <jar jarfile="${main.jarFileChangeLog}" duplicate="fail"> <fileset dir="${main.binChangeLogDir}" /> <manifest> <attribute name="Built-By" value="${user.name}"/> <attribute name="Implementation-Vendor" value="Penn"/> <attribute name="Implementation-Title" value="clusterLinuxChangeLog"/> <attribute name="Build-Timestamp" value="${the.timestamp}"/> </manifest> </jar> <echo message="Output is: ${main.jarFileChangeLog}" /> </target> </project>
If you want to log the change log, add this to the log4j.properties:
log4j.logger.edu.upenn.isc.clusterLinuxClc.ClusterLinuxChangeLogConsumer = DEBUG
If you need more debugging, try these:
log4j.logger.edu.internet2.middleware.grouper.changeLog = DEBUG log4j.logger.edu.internet2.middleware.grouper.app.loader = DEBUG
Make the jar with ant, copy to the grouper loader lib dir, and edit the grouper-loader.properties
##################################### ## CLUSTER LINUX CONSUMER ##################################### changeLog.consumer.clusterLinuxTest.class = edu.upenn.isc.clusterLinuxClc.ClusterLinuxChangeLogConsumer changeLog.consumer.clusterLinuxTest.publisher.recipient = someuser@school.edu/clusterLinuxTest, anotheruser@school.edu, someuser@school.edu/clusterLinuxTestLegacy changeLog.consumer.clusterLinuxTest.clusterLinux.grouperFolderBase = xxxxxxx:apps:fast:clusterLinux changeLog.consumer.clusterLinuxTest.quartzCron =
Restart the loader and add/remove someone from the role, or change permissions, see an XMPP message being sent
(4:35:01 PM) PennGroups: Update permissions
Note, the body of the message isn't important... if the message comes from Grouper and goes to the grouperClient keeping the file in sync, then it will do a 1 minute delay full refresh if one isnt already scheduled (to batch up multiple changes)
If you have DEBUG logging on for the change log consumer, you will see entries like this:
120521 163500.260 [DefaultQuartzScheduler_Worker-5] DEBUG edu.upenn.isc.clusterLinuxClc.ClusterLinuxChangeLogConsumer - Processing changeLog #3593567, permissionName: XXXXXXXXX:apps:fast:clusterLinux:permissions:tomcats:app_directory, grouperFolderBase: XXXXXXXXX:apps:fast:clusterLinux, message: attributeAssign.deleteAttributeAssign, sendMessage: true
sdf