Student Affairs Office of Technology

Adding CAS to Trouble Ticket Express


http://www.troubleticketexpress.com/ rev. 3.01.838.

Modification to troubleticket express (TTX)to apply CAS authentication. Authentication is forced on all pages, administration CAS user id is checked against records in TTX flat file.

Administrative logout is broken do to the built in authentication layer, template system and poor implementation.

Auto population via the cas_session.cgi into user submitted forms is buggy. It cannot be modified

Most cookies are broken as CAS module pulls ahead of all others.

Current implementation is crude at best.

Table of Contents ServerBuild

For the server build we will be adding the ability to use the campus Kerberos, some security features, a basic LAMP installation, etc.
  • Kerberos SSH and NTP (http://www.hilands.com/os-linux-kerberos-ssh-integration.html)
  • Deny Hosts (http://www.hilands.com/os-linux-denyhosts.html)

    We will only change the purge time to "4w" everything else will be left default. Including the email of root@localhost as it will be routed via the system aliases
  • Logwatch (http://www.hilands.com/os-linux-logwatch.html)

    Do NOT install sendmail as instructed on the bottom, we will be using postfix
  • Simple firewall script using IPTables (http://www.hilands.com/code-shell-fw.html) Then we do a few things for installation and having it autostart.

    create the file "/opt/scripts/fw.sh" and copy the code over. Modify the shell script... change ip_ext to the server IP, modify the trusted_ext to.
    trusted_ext="
    128.120.209.0/24 \
    169.237.72.0/24 \
    169.237.58.0/24 \
    10.2.1.0/24 \
    "
    Change the list of IP's to block
    block_ext="\
    169.237.224.18/32 \
    "
    In the external interface section open port 80 to all, and only keep ICMP and 22 for the trusted network. # ln -s /opt/scripts/fw.sh /etc/init.d/fw
    # chmod 755 /opt/scripts/fw.sh
    # update-rc.d fw defaults 1
    update-rc.d: using dependency based boot sequencing
    insserv: warning: script 'fw' missing LSB tags and overrides
  • LAMP install (http://www.hilands.com/os-linux-lamp.html)
  • Apache configs (http://www.hilands.com/os-linux-mod-security.html)
  • Apache Modules (http://www.hilands.com/os-linux-apache-sending-less-information.html)
  • Postfix install (http://www.hilands.com/os-linux-postfix.html)
    my origin should be the registered A record of the system, NOT ucdavis.edu. The relay_domain should be ucdavis.edu.
  • Server Stats is a PHP/RRD tool to get pretty graphs about system and network traffic (http://www.hilands.com/os-linux-serverstats.html)
    As we have done the iptables configuration early we can skip that section in the serverstats configurations.
    Adjust some of the commands for 0.8.3, save the original file in /opt/dnld/serverstats-0.8.3.tar.bz2 and extract it in /var/www/default instead of /var/www.

    When editing the simple.php file enable the ping array, no need to change the site. Ignore the ping_http section and go to the traffic array. To be consistant with the data you want monitored and the IPTables info. Comment out the postgresql section.
  • In addition we also added a few other things on the development server # apt-get install libwww-perl
    # apt-get install libnet-ldap-perl
    # apt-get install alpine
    # apt-get install rkhunter
CAS Requirements

The libraries that are triggered are:
  • CGI::Session
  • CGI
  • CGI :standard
  • AuthCAS
  • Net::LDAP
The AuthCAS library seems to use a lot of stuff
To get AuthCAS to work I did the following on Debain
# apt-get install libcrypt-ssleay-perl libnet-ssleay-perl libio-socket-ssl-perl
# apt-get install libmime-base64-urlsafe-perl libmime-base32-perl

To get the session handling # apt-get install libcgi-session-perl

* Notes for non debian installations
The modules pulled into debian should be downloadable via cpan from the following links:
I assume the following will handle the CGI modules (may need something else for CGI::Session and CGI :Standard
http://search.cpan.org/~markstos/CGI.pm-3.60/
direct download
http://search.cpan.org/CPAN/authors/id/M/MA/MARKSTOS/CGI.pm-3.60.tar.gz
The LDAP parts should be handled by
http://search.cpan.org/~marschap/perl-ldap-0.49/
direct download
http://search.cpan.org/CPAN/authors/id/M/MA/MARSCHAP/perl-ldap-0.49.tar.gz
Downloading and installing AuthCAS

Download the AuthCAS Client I got from
http://search.cpan.org/~osalaun/AuthCAS/
Direct download
http://search.cpan.org/CPAN/authors/id/O/OS/OSALAUN/AuthCAS-1.6.tar.gz wget http://search.cpan.org/CPAN/authors/id/O/OS/OSALAUN/AuthCAS-1.6.tar.gz
tar -xzf AuthCAS-1.6.tar.gz


The main site about AuthCAS can be found here
https://wiki.jasig.org/display/CASC/Perl+Client

To "build" the AuthCAS module I followed the readme and ran
(Note if you have not installed make at this point you will need to do that) # apt-get install make
# perl Makefile.PL
# make
# make test
# make install

Initial Setup

For instance handling we should make subgroups.
/cgi-bin/saot
	/cgi-bin/sis
	/cgi-bin/uga
We are currently using Trouble Ticket Express Version 3.0.1 which can be downloaded from www.troubleticketexpress.com. This should also be available on the software$ share on sa-fs.

Extract the ttx301.tar.gz file to /usr/lib/cgi-bin/ticket/. # tar -xzf ttx301.tar.gz
Icky, change the folder mode to 777, why did we do this? was it recommended in the setup?? # chmod 777 /usr/lib/cgi-bin/ticket/
In a browser go to http://<server>/cgi-bin/ticket/setup.cgi Create a password

For the "Image Directory URL" enter /img/tickets
For the "Sendmail path" enter /usr/sbin/sendmail
For the Company section...
"Company name" Student Affairs Office of Technology "Company email address" saot-help@ucdavis.edu "Company home page" http://saot-ticket-dev.ucdavis.edu "Time Zone" -480 Leave the rest default, we'll come back to the custom fields after we install/tranfer some modules.

add users generic password as it won't be used. add phiallen phiallen@ucdavis.edu Phil Allen djoshi djoshi@ucdavis.edu Divesh Joshi martha martha@ucdavis.edu Martha Cornejo push all check boxes
TTX Files

Outside of the core files we also have a handful of "modules" that need to be copied into the TTX ticket folder.

\\sa-fs\phiallen\code\perl\ttx\modules after copying the modules we can get into the setup and add groups, custom fields, etc.
  • TTXMySQLTickets.pm
  • TTXMySQLSetup.pm
  • TTXCFEd.pm
  • TTXFile.pm
  • TTXGroups.pm
  • TTXInvMod.pm
  • TTXLayout.pm
  • TTXMsgEdit.pm
Module Configurations

Only the ticket portion of this application end up going into the mysql database. All of the configurations, user names and passwords, etc. stay in flat files....

Setup Database
Database Name "tickets", User Name "tickets" password is some assinine 50 character line of code.
Create database & set grant permissions in cmd line.

in a web browser run the MySQL setup # mysql -u root -p
# mysql> create database tickets;
# mysql> grant all on tickets.* to 'tickets'@'localhost' identified by 'tadmin';
http://<server>/cgi-bin/ticket/sqlsetup.cgi


on the form enter the info that matches what we did above. Didn't do anything with the table prefix.

We'll need to go back to the setup.cgi and setup the custom fields. http://<server>/cgi-bin/ticket/setup.cgi All custom field types should be text and the only change we'll be doing is to the "column" text field. Check the boxes for Show in browser and Show in filter

For c0 enter Department
For c1 enter Phone
For c2 enter Room
Please note that these must match exactly and with case. They are hard coded in the TTXTicket.pm later so we can autopopulate the user data from LDAP.
Custom CAS info

  • cas_session.cgi
  • ttx.cgi
  • TTXDesk.pm
  • TTXLogin.pm
  • TTXTicket.pm
# cd /usr/lib/cgi-bin/tickets/
# cp ttx.cgi ttx.cgi.orig
# cp TTXDesk.pm TTXDesk.pm.orig
# cp TTXLogin.pm TTXLogin.pm.orig
# cp TTXTicket.pm TTXTicket.pm.orig
copy the files # opt/dnld/ttx/cas_custom# cp * /usr/lib/cgi-bin/ticket/
If the page auto logs you in and doesn't give you a user, the CAS module is not working properly. Verify the permissions of the file chmod 777 cas_xxxx.cgi and validate the file works itself by hitting it directly in the browser. interesting so now when we log in, it doesn't prompt for a password, etc. just sends us in....
Skin

# cd /cgi-bin/tickets/templates # cp footer.shtml footer.shtml.orig # cp header.shtml header.shtml.orig # cp helpdesk.html helpdesk.html.orig # cp newticket.html newticket.html.orig # cp ticket.html ticket.html.orig root@debian:/opt/dnld/ttx/skin/cgi-bin/templates# cp * /usr/lib/cgi-bin/ticket/templates/ copy the images and css folders root@debian:/opt/dnld/ttx/skin/www-root# cp -R * /var/www/default/
cas_session.cgi

The cas_session.cgi handles the core communications to the CAS server along with collecting the user ID variable and LDAP variables for misc user information. Currently this example is tied to the cas TEST server.

The session ticket SHOULD BE sent over a Secure Socket Layer!!!!!!

Security implentation includes
  • A secure Auth Token, changed every page hit
  • IP Address validation
  • User Agent validation
  • Session purge on failure
Timers are strictly based on cookie timers, as one hour limits, on a per hit basis. That means the timer is refreshed every time the user "hits" a page. These should not be reliant on the browser and should be validated on the server side!

Variables should be added to enable and disable the security "features" more easily.
#!/usr/bin/perl
# we should only have to set the session cookie one time.....

# http://search.cpan.org/~sherzodr/CGI-Session-3.95/Session/Tutorial.pm
#Can't locate CGI/Session.pm in @INC 
# apt-get install libcgi-session-perl
use CGI::Session;
#use:File::Spec;
use CGI;
$refCGI = CGI->new;

# Check to see if session cookie exists, or set to undefined.
$strSID = $refCGI->cookie('PerlSID') || undef;
#print "Content-type: text/html\n\n";
#print "cgi $cgi";
#exit 0;

# create session, use existing pulled from cookie.
# if it does not exist a new will be generated
#new CGI::Session(<Data Source Name>,<Session ID>,<DSN CGI::Session variable>);
$refSession = new CGI::Session("driver:File", $strSID, {Directory=>'/tmp'});
#$refSession = new CGI::Session("driver:File", $cgi, {Directory=>File::Spec->tmpdir});
$sid = $refSession->id();

# put the SID into a cookie
# note the expiration time will be loaded every time page is viewed.
# we should be running https with the cookie so the session isn't hijacked.
# http://search.cpan.org/~markstos/CGI.pm-3.60/lib/CGI.pm#HTTP_COOKIES
# use https cookie "-secure=>1"
# -domain=>'.ucdavis.edu'
use CGI ':standard';

$cookie = cookie(-name=>'PerlSID',
			-value=>$sid,
			-expires=>'+1h',
			-path=>'/');
		#print "Content-type: text/html\n\n";
		#print $cookie;
		#exit 0;
		#PerlSID=2a4380ea958e0b46d178aa497ec804d7; path=/; expires=Tue, 16-Oct-2012 01:14:06 GMT
# the security cookie is sent as a verification out to the user
# and stored in the session, verified at every view and renewed every page view
# random token
# http://speeves.erikin.com/2007/01/perl-random-string-generator.html
my $intLength="30";
my @chars=('a'..'z','A'..'Z','0'..'9');
my $strToken;
foreach (1..$intLength) {
	$strToken .=$chars[rand @chars];	
}
$secCookie = cookie(-name=>'AuthToken',
			-value=>$strToken,
			-expires=>'+1h',
			-path=>'/');

#print header(-cookie=>[$cookie,$secCookie]);

$uid = $refSession->param("UserID");

if (defined $uid) {
	# validate the data we have.
	#print "UID is defined";
	#if ($refSession->param("AuthToken") = $ cookie....
	# then we can reset auth token.
	#$refSession->param("AuthToken", $strToken);
	$strUserAgent = $refSession->param("UserAgent");

	$strAuthToken = $refCGI->cookie('AuthToken') || undef;
	$strSAuthToken = $refSession->param("AuthToken");

	if ($strAuthToken ne $strSAuthToken) {
		print "Content-type: text/html\n\n";
		#print "Auth Token DOES NOT Match

\n\n"; $refSession->param("Authed", "0"); } # before we send a new $secCookie we need to validate the old one. # reset secCookie and strToken ... $refSession->param("AuthToken", $strToken); # need to rip cookies from ttx.cgi print header(-cookie=>[$cookie,$secCookie]); #print "strUserAgent: $strUserAgent

\n\n"; #print "ENV-HTTP_USER_AGENT: $ENV{HTTP_USER_AGENT}

\n\n"; # eq, ne, gt, ge, lt, le if ($strUserAgent ne $ENV{HTTP_USER_AGENT}) { #print "User Agents DO NOT Match

\n\n"; $refSession->param("Authed", "0"); # we can create a session lock out, kills the session. # create a new session variable LOCK if true kick users out, require reauth. # don't rely on cookie timeouts, add time check in session # what about time stamp hashes, or is the generic token ok? # http only cookies, anti javascript/xss # A final note on replay attacks # We have ignored the problem of replay attacks so far in this example. A # replay attack occurs when an attacker sends a stolen cookie to the # server, effectively authenticating the attacker as the client that # the cookie was originally issued to. # http://raza.narfum.org/post/1/user-authentication-with-a-secure-cookie-protocol-in-php/
	}
	$strRemoteAddr = $refSession->param("RemoteAddr");
	#if ($refSession->param("RemoteAddr") = $ENV{REMOTE_ADDR}) {
	if ($strRemoteAddr ne $ENV{REMOTE_ADDR}) {
		$refSession->param("Authed", "0");
		#print "IP Address DOES NOT Match

\n\n"; } ####################################################################### # Possible hijack attempt delete session for user # ####################################################################### $strAuthed = $refSession->param("Authed"); if (!$strAuthed) { $refSession->delete(); print "Content-type: text/html\n\n"; print ""; print "Possible session hijacking, session has been disabled"; print ""; exit 0; } ####################################################################### # Grab ldap things from session instead of re-query # ####################################################################### $strFullName = $refSession->param("FullName"); $strEmail = $refSession->param("Email"); $strDepartment = $refSession->param("Department"); $strPhone = $refSession->param("Phone"); $strAddr = $refSession->param("Addr"); } else { # do cas checking here. #print "UID is NOT defined"; # Do the Auth CAS stuff here use AuthCAS; # need to do a check for HTTPS vs HTTP....... $strCASReturnURLEnvVar = "http://$ENV{SERVER_NAME}$ENV{REQUEST_URI}"; # script_name ignores _GET's $strCASReturnURL = "http://$ENV{SERVER_NAME}$ENV{SCRIPT_NAME}"; $strCASURL = 'https://cas-test.ucdavis.edu'; $strCASLoginPath = '/cas/login'; $strServiceValidatePath = '/cas/serviceValidate'; #$strCASValidationURL = 'https://cas-test.ucdavis.edu/cas/serviceValidate'; $refCAS = new AuthCAS('casUrl' => $strCASURL, 'loginPath' => $strCASLoginPath, 'serviceValidatePath' => $strServiceValidatePath); ################################################################################ # Process CAS authentication check # ################################################################################ # get service ticket from get or label it as undefined. $strServiceTicket = ($ENV{QUERY_STRING} =~ /\bticket=([^?&\s]+)/s) ? $1 : undef; if (defined $strServiceTicket) { # We have sent the data to CAS # verify the username from the CAS ticket.
	        $strCASUserID = $refCAS->validateST($strCASReturnURL, $strServiceTicket);
	        if (defined $strCASUserID) {
	                # Authentication successful
			print header(-cookie=>[$cookie,$secCookie]);
			$refSession->param("UserID", $strCASUserID);
			$uid = $strCASUserID;
			$refSession->param("AuthToken", $strToken);
			$refSession->param("UserAgent", $ENV{HTTP_USER_AGENT});
			$refSession->param("RemoteAddr", $ENV{REMOTE_ADDR});
			$refSession->param("Authed", "1");
	                #print "Content-type: text/html\n\n";
	                #print "GOOD!".$strCASUserID;
			#this isn't saved properly, just create our own and strip the ?....
			$strCASReturnURLEnvVar = $refSession->param("CASReturnURL");
			#print "Content-type: text/html\n\n";
			#$strRedirect = "http://".$ENV{HTTP_HOST}.$ENV{SCRIPT_NAME};
			#printf "Location: $strRedirect\n\n";
			#print "\n;
			#######################################################
			# Collect LDAP and store in session                   #
			#######################################################
			use Net::LDAP;
			$refLDAP = Net::LDAP->new('ldap.ucdavis.edu') or die "$@";
			# Bind as "anon" for searches
			$mesg = $refLDAP->bind (version=>3);

			#search function
			sub LDAPsearch {
			        my ($ldap,$searchString,$attrs,$base) = @_;
			        # if they don't pass a base... set it for them
			        if (!$base ) { $base = "ou=People,dc=ucdavis,dc=edu"; }
			        # if they don't pass an array of attributes...
			        # set up something for them
			        if (!$attrs ) { $attrs = [ 'cn','mail' ]; }
			        my $result = $ldap->search ( base => "$base",
			                scope   => "sub",
			                filter  => "$searchString",
			                attrs   =>  $attrs
			                );
			}
			# run search
			# http://middleware.ucdavis.edu/ldap-search-alt.php
			#my @Attrs = (); # all attributes to be returned.
			my @Attrs = ("givenName", "mail", "ou", "sn", "telephoneNumber", "street"); # grab attributes we want.
			#my $result = LDAPsearch ($refLDAP, "sn=*", \@Attrs);
			my $result = LDAPsearch ($refLDAP, "uid=$uid", \@Attrs);

			# Stash the results.
			my @entries = $result->entries;
			my $entr;
			my %hashLDAPData = ();
			foreach $entr(@entries) {
			#       print "DN: ", $entr->dn, "\n";
			        my $attr;
			        foreach $attr(sort $entr->attributes) {
			                next if ($attr =~ /;binary$/);
			                # stor in hash instead of print.
			                $hashLDAPData{$attr} = $entr->get_value($attr);
					# loop through hash code.
			                #print "    $attr : ", $entr->get_value($attr),"\n";
					#print "
";
					#read has data
					#while (my($key,$value)=each(%hashLDAPData)){
					#        print "$key => $value\n";
					#}
					#print "
"; } } $strFullName = $hashLDAPData{givenName}." ".$hashLDAPData{sn}; $strEmail = $hashLDAPData{mail}; $strDepartment = $hashLDAPData{ou}; $strPhone = $hashLDAPData{telephoneNumber}; $strAddr = $hashLDAPData{street}; $refSession->param("FullName", $strFullName); $refSession->param("Email", $strEmail); $refSession->param("Department", $strDepartment); $refSession->param("Phone", $strPhone); $refSession->param("Addr", $strAddr); } else { # error with authentication send message to purge cache and attempt login again. # send error to apache logs print "Content-type: text/html\n\n"; print "Ticket error, could be resend of same ticket."; printf STDERR "Error: %s\n", &AuthCAS::get_errors(); exit 0; #should do exit 1 for error or ignore auth errors? depends on how many logs$ } } else { #put return URL with EnvVar in here to resend after auth is done. $refSession->param("CASReturnURL", $strCASReturnURLEnvVar); $strLoginURL = $refCAS->getServerLoginURL($strCASReturnURL); printf "Location: $strLoginURL\n\n"; exit 0; } }



# store variable in session
# $session->param("name", $variable);


#$cookie = $cgi->cookie(CGISESSID => $refSession->id);
#print $cgi->header( -cookie=>$cookie );

#print "Content-type: text/html\n\n";
#print "strToken: $strToken

\n\n"; #print "UserAgent: $ENV{HTTP_USER_AGENT}

\n\n"; #print "RemoteAddr: $ENV{REMOTE_ADDR}

\n\n"; #print "UserID: $uid

\n\n"; #print "Session ID: $sid

\n\n"; #print "LDAP Full Name: $strFullName

\n\n"; #print "LDAP Email: $strEmail

\n\n"; #print "LDAP Department: $strDepartment

\n\n"; # $cookieTTX = main::prepareCASCookie(); # $cookieTTX = &prepareCASCookie; # $cookieTTX = &main::prepareCASCookie; # &prepareCAScookie; # print "Content-type: text/html\n\n"; # print "mah cookie $cookieTTX

\n\n";
ttx.cgi

The ttx.cgi handles the core engine of trouble ticket express. Including the CAS cgi file in here will blanket it over the majority of administration features. The "setup" which controls more of the engine features and build in form manipulation processes is controlled elsewhere.

By adding the CAS cgi file to ttx.cgi we will be blanketing the program to requires CAS authentication for viewing.

Added include for cas_session.cgi to the beginning of code (after comments line 27)
do 'cas_session.cgi';
TTXDesk.pm

The TTXDesk.pm file handles most of the template output for the ticket viewing. That is the administrative portion of the tickets.
Changed the table formatting so it doesn't go oversided on anything but the default template.

line 145
#  my $buff = "<table cellspacing=0 cellpadding=0 width=$width>\n";
  my $buff = "<table cellspacing=0 cellpadding=0 width=\"98%\">\n";
TTXLogin.pm

The TTXLogin.pm has two functions.
  1. Login form functionality (display and process)
  2. Validate authentication
We can strip the primary display and slap in a quick validation against our CAS user id.

*Note* the templating engine is problematic with the logout and this routine. As this routine just cuts through the login routine it doesn't process the logout features. We should be able to add a redirect when the logout is triggered and kill the session authorization.

Take out Line 40 to 49
  $data->{PAGEHEADING} = '[%Login%]';
  return undef if !$query->param('dologin');
  if (TTXCommon::cleanit($query, 'login') eq undef) {
    $data->{ERROR_MESSAGE} = '[%Missing User ID%]';
    return undef;
  }
  if (TTXCommon::cleanit($query, 'passwd') eq undef) {
    $data->{ERROR_MESSAGE} = '[%Missing Password%]';
    return undef;
  }
and replace it with
$query->param('login', $main::uid);
which replaces the user ID with the userid pulled from CAS.

take out line 51,52
  if ($user eq undef || ($user->get('passwd') eq undef) || ($user->get('passwd') ne $query->param('passwd'))) {
    $data->{ERROR_MESSAGE} = '[%Wrong User ID or Password%]';
which does the validation of input from the form (which we removed above)
and add
if ($user->get('login') ne $main::uid) {
    $data->{ERROR_MESSAGE} = '[%Invalid User Account%]';
which validates based on OUR input
TTXTicket.pm

The TTXTicket.pm handles the output of the ticket submissions. This is the user end display. Some of the modifications need to be done in the custom setup interface for the fields starting with x. X is for custom in the program.

Skimming down to the form display section we can insert our variables, pulled from LDAP in the cas_session.cgi, into the param class after it has been instantiated.

Line 677 we can add some of the LDAP pulls from the cas_session.cgi
$query->param('name', $main::strFullName);
$query->param('email', $main::strEmail);
$query->param('xDepartment', $main::strDepartment);
$query->param('xPhone', $main::strPhone);
$query->param('xRoom', $main::strAddr);
TTXUser.pm

just added some crap to testing.

Students:: Administration:: About us:: Contact us:: Giving to Student Affairs


Copyright © 2010 The Regents of the University of California
You are welcome toContact Student Affairs to ask questions or report problems with the Web site. Contact information for the Student Affairs Web team.