Latest Entries »

SiteImprove SSO with CAS 5.2.x

SiteImprove offers a service that will have a spider check your links on your sites for validity and report if they are down.  By default users are manually created and local to SiteImprove.  If you want to use a different account to login, you need to setup Single Sign On (SSO), which Site Improve supports via SAML2.  Thankfully, setting this up isn’t too difficult, though the available instructions on their site, currently, are for Okta, but we are going to be using CAS.

 

To do this you will need to do the following:

  1. Enable SAML Support in CAS
  2. Configure Your cas.properties File
  3. Generate and Configure Your IDP-Metadata
  4. Create Your Service
  5. Configure SiteImprove and Test
  6. Restrict Access

 

Enable SAML Support in CAS

If you have not already enabled support for SAML and for CAS to act as an IDP, add the following to your pom.xml file

<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-support-saml-idp</artifactId>
    <version>${cas.version}</version>
</dependency>

 

You may also need to add the following under the repository section of your pom.xml file

<repositories>
 ...
     <repository>
         <id>shibboleth-releases</id>
         <url>https://build.shibboleth.net/nexus/content/repositories/releases</url>
     </repository>
 ...
</repositories>

 

Once you’ve done that, move on to configuring your cas.properties file before you rebuild your war overlay package and restart tomcat.

 

Configure Your cas.properties File

With SAML Support turned on, we can now set the settings needed to generate our idp-metadata.xml file.  Open your cas.properties file and set the following;

#==========================
# SAML Authentication
#==========================
cas.authn.samlIdp.entityId=${cas.server.prefix}/idp
cas.authn.samlIdp.scope=example.com #replace this with yours
cas.authn.samlIdp.metadata.privateKeyAlgName=RSA
cas.authn.samlIdp.metadata.location=file:/etc/cas/saml
cas.authn.samlIdp.attributeQueryProfileEnabled=true

 

Save the file and now rebuild your mav overlay using the command “mvn clean package”.  After which you should restart Tomcat.

 

Generate and Configure Your IDP-Metadata

If you restarted Tomcat in the previous step and didn’t already have IDP metadata generated, CAS will generate that and its accompanying signing and encryption keys now.  The generated file will be fairly complete and only needing some minor modification.  The only thing you should need to do is uncomment the <AttributeAuthorityDescriptor> section in your idp-metadata.xml file.  Other than that, it wouldn’t hurt to flesh out your <ContactPersons> in the file, but that isn’t required.

 

The <AttributeAuthorityDescriptor> section should look like this

<AttributeAuthorityDescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
    <Extensions>
        <shibmd:Scope regexp="false">your_scope.com</shibmd:Scope>
    </Extensions>
    <KeyDescriptor use="signing">
       <ds:KeyInfo>
           <ds:X509Data>
               <ds:X509Certificate>
                   <!-- Your generated signing cert info here -->
               </ds:X509Certificate>
           </ds:X509Data>
       </ds:KeyInfo>
    </KeyDescriptor>
    <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding" Location="https://your_cas_server.com/cas/idp/profile/SAML1/SOAP/AttributeQuery"/>
    <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://your_cas_server.com/cas/idp/profile/SAML2/SOAP/AttributeQuery"/>
 </AttributeAuthorityDescriptor>

 

Create Your Service

Next we need to make a service in CAS for SiteImprove.  At this point you’re going to need grab a few details from SiteImprove which can be found under Settings -> Single Sign On -> SAML Authentication -> Configure.  Under the SiteImprove Service Provider Details section, make not of the Metadata URL and Entity ID properties as we will need them in our service definition.  These are important because they are specific to your account.

 

If you are using something other than the JSON Service Registry for CAS, you’ll need to adapt the below configuration to your own.

File Name: SiteImprove-10000010.json

{
    "@class" : "org.apereo.cas.support.saml.services.SamlRegisteredService",
    "serviceId" : "^https://sso.siteimprove.com/auth/Saml2/<your siteimprove account id>",
    "name" : "SiteImprove SAML",
    "id" : 10000010,
    "evaluationOrder" : 1,
    "usernameAttributeProvider" : {
        "@class" : "org.apereo.cas.services.PrincipalAttributeRegisteredServiceUsernameProvider",
        "usernameAttribute" : "sAMAccountName"
    },
    "attributeReleasePolicy" : {
        "@class" : "org.apereo.cas.services.ReturnMappedAttributeReleasePolicy" ,
        "allowedAttributes" : {
            "@class" : "java.util.TreeMap",
            "sAMAccountName" : "Username",
            "mail" : "Email",
            "givenName" : "FirstName",
            "sn" : "LastName"
        }
    },
    "requiredNameIdFormat" : "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
    "metadataLocation" : "https://sso.siteimprove.com/auth/Saml2/<your account number here>/Metadata",
    "signAssertions": true,
    "signResponses": false
}

 

A quick break down of the file above:

  • serviceId is your SiteImprove Metadata URL
  • usernameAttributeProvider is set to use the sAMAccountName as the username
  • attributeReleasePolicy is being configure to map sAMAccountName to Username, mail to Email, givenName to FirstName, and sn to LastName as these attributes are expected by SiteImprove
  • requireNameIdFormat is overriding the format of the username and setting it to persistent
  • signAssertions is set to turn because SiteImprove requires that assertions from our IDP be signed
  • signResponses is set to false, because if both signAssertions and signResponses are true, then assertions must be encrypted and to my knowledge, SiteImprove doesn’t support that.

Once you are done editing the service file, save and give Tomcat one more reboot.

 

Configure SiteImprove and Test

Now that we have our IDP setup and our service configured, we can now tell SiteImprove what it needs to know. For this part you only need to configure two parameters.

Under the section “Your Identity Provider Details” set the Login URL to your HTTP-Redirect SSO endpoint.

https://your_cas_server.com/cas/idp/profile/SAML2/Redirect/SSO

 

Next, enter the contents of the file etc/cas/saml/idp-signing.crt into the Certificate input.

Once you have done the above steps, click the “Save and Test” button at the bottom of the page.  Note that you may need to do this twice before SiteImprove will pull up your login site as the configuration page has a somewhat short timeout and will throw a page not found error on the first attempt after timeout.  All you need to do is click the button again and it should work.  If every thing is good, you should be greeted by a screen saying the login was successful and the attributes it pulled back.

 

Restrict Access

With your SSO now working for SiteImprove, you’ll want to secure your CAS service because SiteImprove does not restrict access for sign-ins.  The reason for this is that when you use SSO for SiteImprove, users are created by signing in via SSO.  So, if you want to restrict who can authenticate, you need to setup your CAS service to handle authorization.  Thankfully, this is pretty easy.

All you need to do is open your service definition and add the following section to your service definition:

 

"accessStrategy" : {
    "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
    "enabled" : true,
    "ssoEnabled" : true,
    "requiredAttributes" : {
        "@class" : "java.util.HashMap",
        "memberOf" : [ "java.util.HashSet", [ "<group DN here>" ] ]
    }
 }

 

The above snippet tells CAS that for someone to sign in to the service, they must have the specified group DN present in their memberOf attribute (make sure you are pulling memberOf as part of your principle attributes or it is being released for this service).

If you hate the idea of have that huge group DN in your service definition and instead want something smaller and easier to use, you can setup a groovy script for attribute release that processes the memberOf attribute and only keeps the names of the groups and puts them in a new variable called, groups.

Say by adding something like this:

"attributeReleasePolicy" : {
     "@class" : "org.apereo.cas.services.GroovyScriptAttributeReleasePolicy",
     "groovyScript" : "classpath:your_groove_script.groovy"
 }

note: classpath points to src/main/resources in your maven overlay directory

 

Example snippet of looping over the attributes by key in groovy

def formattedAttribues = [:]

//loop through the principles attributes
args[0].each { key, value ->
    switch(key) {
        case 'memberOf':
            def roles = []
            
             //iterate over groups and pullout only the content of CN
             for (group in value) {
                 def m = group =~ /CN=(.*?)(?:,[A-Z]+=|$)/
                 
                 //add the matched CN
                 if ((m)) {
                     roles.add(m.group(1))
                 }
             }

             //add parsed groups to groups in formattedAttributes map
             formattedAttribues['groups'] = roles

             break

             default:
                 formattedAttribues[key] = value
                 break
    }
};

return formattedAttributes;

 

And that’s it, now you can use the line below in your service definiton, instead of checking for the whole group DN

"groups" : [ "java.util.HashSet", [ "<group name here>" ] ]

Prior to version 5.x of CAS, JSTL was used via JSP pages and it was easy to extend the CAS 2.0 protocol to release attributes as a snippet for release was provided in the documentation.

If you have recently upgraded to the 5.x version, you might have noticed that the templating engine has changed to Thymeleaf 2.1 which uses html pages instead of jsp, and there is no longer a helpful snippet of code to extend the CAS 2.0 Protocol in the documentation to release attributes.   Granted, the 3.0 protocol releases attributes by default, but you might need to provide auth services to a few end points that don’t use the 3.0 protocol.

After a short while of groking Thymeleaf, I came up with the following code to release whatever attributes you give it, under the CAS 2.0 Protocol.  Any attributes that are lists, will be split on “, ” and release as an array of elements.

The older attribute processor could actually notice that an attribute was a list and split it easily; however, in 5.x all the attributes under the 2.0 protocol are provided as a string of “attribute name=value”, which requires a bit more processing.

Also, the split function is by a single character only, so, that being the case I did a cheap move of replacing “, ” with “|” and splitting on “|”.  This should work for most group lists and standard lists, not splitting the space in a display name or between DN elements.  Though it is possible that “|” might be in the list already as it is a valid character for Group names in AD, my company just happens to not have groups with this character in the name or their DN.  Yours might and you might need to pick a different character.

File: casServiceValidationSuccess.html

 

Screen Shot 2017-08-29 at 9.00.17 PM

Image of the code because it refused to paste in right

You can download the html here

It’s not the best code in the world, but hopefully, it will save some of you a few hours of your life so you can get on to the next problem/project sooner.

Introduction

About four years ago I wrote a series of posts covering installing Jenkins on Ubuntu 12.04 with Tomcat 6 and using Jenkins for PHP Continuous Integration.  A lot of the tools for PHP CI where provided via pear, but things have changed since then.  For starters, phpUnit as of version 4.0 was no longer provided by the channel pear.phpunit.de and should be installed by other means.

So in this new set of posts I plan to cover the following:

  • Installing the needed prerequisites
  • Globally installing Composer
  • Installing the PHP CI tools with Composer
  • Installing Tomcat 8.x
    • Installing Tomcat APR
    • Installing TCNative
  • Installing Jenkins
  • Configuring Apache to act as a SSL handler and proxy for Tomcat 8
    • TLSv1.2 and appropriate ciphers
    • HSTS (HTTP Strict Transport Security)
    • Port 80 redirect with 301 response code to 443
    • OCSP Stapling
    • Proxy Setup in Apache

 

Installing the Needed Prerequisites

In this section we are going to install several packages via apt-get, from Oracle-Java-8 to PHP 7.1

Installing Oracle Java 8

  1.  Add the Oracle Java PPA to apt-get
    add-apt-repository ppa:webupd8team/java
  2. Update apt-get and install Oracle Java 8
    apt-get update; sudo apt install oracle-java8-installer
  3. Agree to the license agreement.
  4. Edit /etc/environment
    vim /etc/environment
  5. add the following line
    JAVA_HOME="/usr/lib/jvm/java-8-oracle"
    
  6. Save the file and relaunch the terminal to use the updated environment

 

Installing PHP 7.1

  1. Add the PPA for PHP 7.1
    add-apt-repository ppa:ondrej/php
  2. Update with “apt-get update”
  3. Install the many PHP 7.1 libraries via apt-get
    • php7.1
    • php7.1-cli
    • php7.1-common
    • php7.1-curl
    • php7.1-dev
    • php7.1-fpm
    • php7.1-iconv
    • php7.1-intl
    • php7.1-json
    • php7.1-ldap
    • php7.1-mbstring
    • php7.1-mcrypt
    • php7.1-mysql
    • php7.1-opcache
    • php7.1-pdo
    • php7.1-pgsql
    • php7.1-xdebug
    • php7.1-xsl
    • php7.1-xml
    • php-pear
    apt-get install php7.1 php7.1-cli php7.1-common php7.1-curl php7.1-dev php7.1-fpm php7.1-iconv php7.1-intl php7.1-json php7.1-ldap php7.1-mbstring php7.1-mcrypt php7.1-mysql php7.1-opcache php7.1-pdo php7.1-pgsql php7.1-xdebug php7.1-xsl php7.1-xml php-pear

 

Install Subversion 1.9

  1. Add the WanDisco Repository
    sh -c 'echo "deb http://opensource.wandisco.com/ubuntu `lsb_release -cs` svn19" >> /etc/apt/sources.list.d/subversion19.list'
    
    wget -q http://opensource.wandisco.com/wandisco-debian.gpg -O- | sudo apt-key add -
    
    apt-get update
  2. Install subversion 1.9
    apt-get install subversion

 

Install Other Needed Libraries

There are a few more needed packages/libraries we need to install before we can move on

apt-get install graphviz python-software-properties build-essential git ant libxml2-utils libcrypt-openssl-dsa-perl maven libapache2-mod-proxy-html libxml2-dev

 

 

Globally Installing Composer

Since PHPUnit is now no longer on pear.phpunit.de, the new method of installing these tools uses Composer.  Which after a little setup, isn’t all the bad to use.

Setting up Composer globally is pretty easy with just a few commands

  1. Download composer
    curl -sS https://getcomposer.org/installer | php
  2. Move composer.phar into place
    mv composer.phar /usr/local/bin/composer
  3. Make sure composer is executable
    chmod +x /usr/local/bin/composer

 

 

Installing the PHP CI Tools With Composer

For this section, we will create a centrally accessible location for our PHP CI tools to reside and then use a simple composer.json file for the installation.  I could go with several composer commands instead, but this is far easier.

  1. Create a central location for the tools to be installed to
    mkdir -p /opt/composer/vendor
  2. Create the composer.json file
    {
        "config": {
             "vendor-dir": "/opt/composer/vendor"
        },
    
        "require-dev": {
             "phpunit/phpunit": "^6.0",
             "doctrine/annotations": "^1.3"
        },
    
        "require": {
             "phpunit/dbunit": "^3.0",
             "symfony/console": "2.8.9",
             "phing/phing": "^2.16",
             "sebastian/phpcpd": "2.0.4",
             "phploc/phploc": "^3.0",
             "phpmd/phpmd": "^2.6",
             "squizlabs/php_codesniffer": "^2.8",
             "phpdocumentor/reflection-docblock": "~2.0",
             "symfony/config": "~2.8",
             "symfony/filesystem": "~2.8",
             "symfony/finder": "~2.8",
            "phpdocumentor/phpdocumentor": "2.9"
        }
    }

    Alternatively, you can download the file here

     

  3.  Install the libraries/bundles (In the directory that you made composer.json or where ever you places it).
    composer install
  4. Add the following to the PATH variable line in /etc/environment to update the path for  all of the users
    :/opt/composer/vendor/bin/
  5. Close and re-open your terminal to have the new environment take effect

 

 

Installing Tomcat 8.x

This part will be pretty easy to do as I’m going to use the apt-get version and not the source version.  Apache will be handling our front end SSL and other things, so we just need Tomcat to serve the application.

  1. Install tomcat8
    apt-get install tomcat8

 

For the next two sections, if you do not want to do an install from source for APR or TC-Native, you can just install the libraries from apt-get, if you are happy with the versions in the repository for your ubuntu version.

apt-get install libapr1 libtcnative-1

 

Installing Tomcat APR

  1. Download the APR source from the Apache Portable Runtime Project
    wget http://mirrors.advancedhosters.com/apache/apr/apr-1.5.2.tar.gz
  2. Move the and extract the tar file to /opt
    mv apr-1.5.2.tar.gz /opt
    cd /opt
    tar xvf apr-1.5.2.tar.gz
  3.  Change to the directory and run the following
    cd apr-1.5.2
    ./configure
    make
    make install

Installing TC Native

  1. Download the source from Apache’s Site or use wget to download it.
    wget http://mirrors.ocf.berkeley.edu/apache/tomcat/tomcat-connectors/native/1.2.12/source/tomcat-native-1.2.12-src.tar.gz
  2. Extract the tar file and move into the new directories native directory
    tar xvf tomcat-native-1.2.12-src.tar.gz
    cd tomcat-native-1.2.12/native
  3. Configure TC-Native
     ./configure --with-apr=/usr/local/apr/bin/apr-1-config --with-java-home=/usr/lib/jvm/java-8-oracle --with-ssl=yes --prefix=/usr/share/tomcat8
  4. Make and install
    make
    make install
  5. Create or edit setenv.sh
    Under /usr/share/tomcat8/bin there should be a file called setenv.sh, if there is not, created it with your favorite editor and set its contents to the following

    LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CATALINA_HOME/lib
    export LD_LIBRARY_PATH
  6. Restart Tomcat 8 and check or watch with “tail -f”, /var/log/tomcat/catalina.out for the line where APR and TC Native is loaded, to make sure all went as expected.
  7. Open a browser and navigate to http://<your ip or server here>:8080 to see if tomcat is up and running
  8. Edit /etc/tomcat/server.xml with your favorite editor and uncomment the apr line in the file, while setting it’s ssl attribute to false so it looks like
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="off" />
  9. Change the connector for port 8080 to look like the following:
    note: remove the address= part if you want to test the page without apache first as it restricts access to only that address.

    <Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol"
                   connectionTimeout="20000"
                   URIEncoding="UTF-8"
                   server="Apache"
                   maxThreads="150"
                   address="127.0.0.1"/>
  10. Save server.xml and restart tomcat

 

 

Installing Jenkins

The installation of Jenkins is pretty easy and doesn’t have a lot of configuration

  1. Make the home directory for jenkins in /opt/jenkins
    mkdir /opt/jenkins
  2. Add the following home directory environment variable definition to tomcat8 in /etc/init.d/tomcat8
    #JENKINS HOME
    JENKINS_HOME=/opt/jenkins
    export JENKINS_HOME
  3. Download the version Jenkins you want (I’m using the LTS version) from the Jenkins-CI site or download it with wget, like so:
    wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war
  4. Make jenkins.war the root site of tomcat8 (you don’t have to do this, you could just copy it over to webapps, but you will need to append /jenkins to the proxy configuration when you get there)
    mv jenkins.war /var/lib/tomcat8/webapps/ROOT.war
  5. Restart Jenkins
    service tomcat8 restart

 

 

Configuring Apache to Act as an SSL Handler and Proxy for Tomcat 8

Since Apache is already installed by installing PHP 7.1, we can move straight on to the configuration and enabling the needed mods.

First, we are going to enable the needed mods: headers, proxy, proxy_connect, proxy_html, proxy_http, rewrite, and ssl.

  1. enable needed mods
    a2enmod headers
    a2enmod rewrite
    a2enmod ssl
    a2enmod proxy
    a2enmod proxy_http
    a2enmod proxy_html
  2. Restart Apache to enable the modsservice apache2 restart

 

TLSv1.2 and appropriate ciphers

For this particular install we are going to use TLS 1.2 only and a handful of high security ciphers.  For your users to use your site, they will need to be running the following clients or higher:

  • Android 5.0
  • Chrome 30
  • Edge
  • Firefox 27
  • IE 11 on Windows 7
  • Java 8
  • Opera 17
  • Safari 9

 

The first thing you will need to be able to perform the steps in this section, is to have an SSL cert, either self signed or not.  Once you have that and have placed it (I tend to prefer to put them under /etc/ssl/private), you will need to tell Apache where it is.  After which , we can set what ciphers are allowed and what versions of SSL will be accepted.

  1. Edit /etc/apache2/sites-available/default-ssl.conf
  2. Add the following lines
    SSLEngine on
    
    SSLCertificateChainFile /etc/ssl/private/<your chain cert>.crt
    SSLCertificateFile /etc/ssl/private/<your cert>.crt
    SSLCertificateKeyFile /etc/ssl/private/<your cert key>.key
    
    SSLProtocol -all +TLSv1.2
    
    SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
  3. Save and then restart apache2

 

If these ciphers are too restrictive for your, please check the following resources for some more cipher and TLS options.

Mozilla- Server Side TLS
OWASP – Securing Tomcat (Although this is for Tomcat, the translation to Apache is pretty easy)

HSTS (HTTP Strict Transport Security)

By enabling HSTS, your browser is instructed to always use HTTPS when communicating with your site, and it is quiet easy to turn on.

  1.  Add the following line to your ssl vhost definition for apache, /etc/apache2/sites-available/default-ssl.conf
    Header always set Strict-Transport-Security "max-age=86300; includeSubdomains;"
    


    In the above line max-age should be around a week or less than a day

 

Port 80 Redirect with 301 Response Code to 443

Now that we have HSTS setup and SSL, we should default our site to use SSL.  To do so, we will edit the default VHost for port 80 to redirect to the same URL, but with https and a response of 301.

  1. Edit /etc/apache2/sites-available/000-default.conf and make it look like the following
    <VirtualHost *:80>
            ServerAdmin webmaster@localhost
            ErrorLog ${APACHE_LOG_DIR}/error.log
            CustomLog ${APACHE_LOG_DIR}/access.log combined
    
           <IfModule mod_rewrite.c>
                   RewriteEngine On
                   RewriteCond %{HTTPS} off
                   RewriteRule (.*) https://%{SERVER_NAME}$1 [R=301,L]
           </IfModule>
    </VirtualHost>

 

OCSP Stapling

OCSP Stapling delivers certificate revocation information during the TLS handshake and can improve performance  of TLS when using HTTPS.  To use this, we simply need to enable it and specify a cache location.

  1.  Specify a cache location by adding the following line to the top of your default-ssl.conf vhost
    SSLStaplingCache shmcb:/tmp/stapling_cache(128000)
  2. Add the following line somewhere in the <VirtualHost _default_:443> block of the same file
    SSLUseStapling on
  3.  Restart Apache2
    service apache2 restart
  4. Check of your cert is using OCSP as you expect
    openssl.exe s_client -connect [yoursite.com]:443 -status

    You should see a line in the OCSP Response Data section like “OCSP Response Status: successful (0x0)

 

Proxy Setup in Apache

Having Apache act as an SSL handler for Tomcat is not too hard to setup and allows us to use the SSL Ciphers we want to use, without having to deal the capabilities of the Java version we have installed.

To set this up we need to add the following lines to our default-ssl.conf in the VirtualHost definition block.

ProxyRequests Off
ProxyPreserveHost On
AllowEncodedSlashes NoDecode

<Proxy *>
    Order deny,allow
    Allow from all
</Proxy>

ProxyPass        / http://localhost:8080/ nocanon
ProxyPassReverse / http://localhost:8080/
ProxyPassReverse / http://your_server_url.com/

# lets jenkins know SSL is being handled elsewhere
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"

In this part of the series I will cover how to setup CAS to authenticate using LDAP and store the ticket information in PostgreSQL, instead of the default memory ticket manager.

We will also be setting up connection pooling for:

  • LDAP
  • PostgreSQL (using C3Po)

 

Prerequisite Tasks

Letting Tomcat Know How to Use PostgreSQL

Tomcat doesn’t know how to work with PostgreSQL right off the bat, so we need to grab the JDBC4 driver file for it “postgresql-9.3-1102.jdbc4.jar”

  1. Download the file from here

    wget -c http://central.maven.org/maven2/org/postgresql/postgresql/9.3-1102-jdbc4/postgresql-9.3-1102-jdbc4.jar
  2. Move the file to /var/lib/tomcat7/lib
  3. Restart Tomcat

 

Setting Up Your PostgreSQL Server to Use SSL

To use SSL with PostgreSQL, you need to generate (or copy) and put in place to files sever.crt and server.key theses can be the star cert for your domain or self-signed.  Either way, once you have them you will need to place them in your $PGDATA folder.  On Ubuntu, this folder is typically /var/lib/postgresql/<version>/main/

Another option is to place these files in /etc/ssl/private and create a soft link to them in /var/lib/postgresql/<version>/main/ like so:

ln -s /etc/ssl/private/your_cert.key /var/lib/postgresql/<version>/main/server.key
ln -s /etc/ssl/private/your_cert.crt /var/lib/postgresql/<version>/main/server.crt

For more information on this setup, please refer to PostgreSQL’s documentation here.

 

Adding the SSL Certs For PostgreSQL and LDAPS

We intent to use SSL for both PostgreSQL and LDAP, so we will need to add the appropriate certs to our cacerts file.

  1. Get the certs for Postgres and your LDAP servers  in pem format (here is a post on converting crt and key files to a pem, if you don’t have one)If you will only be using certain servers for LDAP and do not have a cert that covers each of them, you can pull the cert from the server directly, like so:
    echo -n | openssl s_client -connect ldap_server_ip:636 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ldap_server.pem
    
    
  2. Once you have the pem files to you cacerts file like so:
    keystore -import -alias <server alias> -file <your_pem_file> -keystore $JAVA_HOME/jre/lib/security/cacerts

    Note: the default password is “changeit”

    If your JAVA_HOME is not set, you can find what it should by by issuing the following command to find your cacerts file:

    find /usr/lib/jvm -name cacerts

 

Setting Up The Application User and Database For CAS

  1. Open your favorite tool for working with Postgresql (phpPgAdmin, pgAdminIII, psql)
  2. Create the application user by running the following SQL, let’s call the user example_app:
    CREATE ROLE example_app LOGIN
    WITH PASSWORD 'some_password' NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION;
  3. Create the database for CAS (we don’t have to create any tables in it, CAS will do this for us)
    CREATE DATABASE cas
     WITH OWNER example_app
     ENCODING = 'UTF-8'
     TABLESPACE = pg_default
     LC_COLLATE = 'en_US.UTF-8'
     LC_CTYPE = 'en_US.URF-8'
     CONNECTION LIMIT = -1;
  4. If you are limiting connections to your database with the pg_hba.conf file, you should add a line to it for this user and database, like so:
    1. Open /etc/postgresql/<version>/main/pg_hba.conf
    2. Add the following line in the IPv4 section:
      host    cas            example_app       <cas_server_ip>/<host bits>        md5
    3. Save the file
    4. Reload the configuration for PostgreSQL by running the following command
      service postgresql reload

 

Altering the Maven WAR Overlay

In this part we will be altering the Maven WAR project from part 1.

 

Adding the LDAP and SQL Connection Pooling Properties

  1. In your $project_home (/opt/work/cas-local) open src/main/webapp/WEB-INF/cas.properties and add the following to the end (filling in your details)
    # ========================================
    # == LDAP Connection Pooling Properties ==
    # ========================================
    ldap.pool.minIdle=3
    ldap.pool.maxIdle=5
    ldap.pool.maxSize=10
    
    # Max time in ms to wait for a connection to
    # become available under pool exhausted condition
    ldap.pool.maxWait=10000
    
    # -- Evictor Settings --
    
    # Period in ms at which the evictor process runs
    ldap.pool.evictionPeriod=600000
    
    # Max time in ms that connection can remain idle
    # before becoming available for eviction
    ldap.pool.idleTime=1200000
    
    # -- Connection Testing Settings --
    
    # Set to true to enable connection liveliness testing on evictor process runs.
    # Probably results in best performance
    ldap.pool.testWhileIdle=true
    
    # Set to true to enable connection liveliness testing before every request
    # to borrow an object from the pool
    ldap.pool.testOnBorrow=false
    
    
    # ===========================================
    # == Database Connection Pooling Properties ==
    # ===========================================
    
     # == Basic database connection pool configuration ==
    database.dialect=org.hibernate.dialect.PostgreSQLDialect
    database.driverClass=org.postgresql.Driver
    database.url=jdbc:postgresql://<postgresql_server_address>/cas?ssl=true
    database.user=example_app
    database.password=example_app_password
    database.pool.minSize=3
    database.pool.maxSize=15
     
    # Maximum amount of time to wait in ms for a connection to become
    # available when the pool is exhausted
    database.pool.maxWait=10000
     
    # Amount of time in seconds after which idle connections
    # in excess of minimum size are pruned.
    database.pool.maxIdleTime=120
     
    # Number of connections to obtain on pool exhaustion condition.
    # The maximum pool size is always respected when acquiring
    # new connections.
    database.pool.acquireIncrement=3
     
    # == Connection testing settings ==
     
    # Period in s at which a health query will be issued on idle
    # connections to determine connection liveliness.
    database.pool.idleConnectionTestPeriod=30
     
    # Query executed periodically to test health
    database.pool.connectionHealthQuery=select 1
     
    # == Database recovery settings ==
     
    # Number of times to retry acquiring a _new_ connection
    # when an error is encountered during acquisition.
    database.pool.acquireRetryAttempts=5
     
    # Amount of time in ms to wait between successive acquire retry attempts.
    database.pool.acquireRetryDelay=2000
    
  2. Save the file
  3. Replace the following section to tell CAS we will authenticate with LDAP:
    <!--
    	| This is the authentication handler declaration that every CAS deployer will need to change before deploying CAS 
    	| into production.  The default SimpleTestUsernamePasswordAuthenticationHandler authenticates UsernamePasswordCredentials
    	| where the username equals the password.  You will need to replace this with an AuthenticationHandler that implements your
    	| local authentication strategy.  You might accomplish this by coding a new such handler and declaring
    	| edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules.
    	+-->
    <bean 
    	class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />
    

    with

    <!--
        | Use the LDAP auhentication handler
        +-->
    <bean 
        class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler"
        p:filter="cn=%u"
        p:searchBase="<your_base_OU. ex. o=some_ou or blank for searching everything>"
        p:contextSource-ref="contextSource"
        p:searchContextSource-ref="pooledContextSource" />
    
  4. Bellow that bean add the following bean to setup the LDAPContextSouce:
    	<!--
    	Define contextSource for LDAP
    	-->
    	<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
    		<!-- DO NOT enable JNDI pooling for context sources that perform LDAP bind operations. -->
    		<property name="pooled" value="false"/>
     
    		<!--
    		  Although multiple URLs may defined, it's strongly recommended to avoid this configuration
    		  since the implementation attempts hosts in sequence and requires a connection timeout
    		  prior to attempting the next host, which incurs unacceptable latency on node failure.
    		  A proper HA setup for LDAP directories should use a single virtual host that maps to multiple
    		  real hosts using a hardware load balancer.
    		-->
    		<property name="urls">
                <list>
                   <value>ldaps://<your ldap server address></value>
                   <value>ldaps://<second ldap server address></value>
                </list>
             </property>
     
    		<!-- Place JNDI environment properties here. -->
    		<property name="baseEnvironmentProperties">
    			<map>
    				<!-- Three seconds is an eternity to users. -->
    				<entry key="com.sun.jndi.ldap.connect.timeout" value="30000" />
    				<entry key="com.sun.jndi.ldap.read.timeout" value="30000" />
    		   
    				<!-- Explained at http://docs.oracle.com/javase/jndi/tutorial/ldap/security/auth.html -->
    				<entry key="java.naming.security.authentication" value="simple" />
    			</map>
    		</property>
    	</bean>
    
  5. Add the following beans for setting up the LDAP pool context and the context validator:
    	<!-- This uses the variables from cas.properties -->
    	<bean id="pooledContextSource"
    		class="org.springframework.ldap.pool.factory.PoolingContextSource"
    		p:minIdle="${ldap.pool.minIdle}"
    		p:maxIdle="${ldap.pool.maxIdle}"
    		p:maxActive="${ldap.pool.maxSize}"
    		p:maxWait="${ldap.pool.maxWait}"
    		p:timeBetweenEvictionRunsMillis="${ldap.pool.evictionPeriod}"
    		p:minEvictableIdleTimeMillis="${ldap.pool.idleTime}"
    		p:testOnBorrow="${ldap.pool.testOnBorrow}"
    		p:testWhileIdle="${ldap.pool.testWhileIdle}"
    		p:dirContextValidator-ref="dirContextValidator"
    		p:contextSource-ref="contextSource" />
    	
    	<!-- used by PoolingContextSource.  Does a search of the root pulling
    	     back a single record to check against the specified context.
    		 
    		 This is used to ensure that pooled connections are still properly
    		 connected. -->
    	<bean id="dirContextValidator"
    		class="org.springframework.ldap.pool.validation.DefaultDirContextValidator"
    		p:base=""
    		p:filter="objectclass=*">
    		<property name="searchControls">
    			<bean class="javax.naming.directory.SearchControls"
    				p:timeLimit="1000"
    				p:countLimit="1"
    				p:searchScope="0"
    				p:returningAttributes="" />
    		</property>
    	</bean>
    
  6. In the next section change the “@@THIS SHOILD BE REPLACED@@” with the username of the person you want to be able to access the “/service” section of CAS
  7. In the serviceRegistryDao bean, under the <property name=”evaluationOrder” value=”0″ /> or <property name=”evaluationOrder” value=”10000001″ /> line (depending on the one you are using), add the following:
    <property name="allowedAttributes">
    	<list>
    		<value>cn</value>
    		<value>telephoneNumber</value>
    		<value>groupMembership</value>
    		<value>sn</value>
    		<value>givenName</value>
    		<value>loginExpirationTime</value>
    	</list>
    </property>
    
  8. After the last bean in the file, add the following bean to setup the data source for your database
        <!-- Connection pooling for database -->
        <bean
            id="dataSource"
            class="com.mchange.v2.c3p0.ComboPooledDataSource"
            p:driverClass="${database.driverClass}"
            p:jdbcUrl="${database.url}"
            p:user="${database.user}"
            p:password="${database.password}"
            p:initialPoolSize="${database.pool.minSize}"
            p:minPoolSize="${database.pool.minSize}"
            p:maxPoolSize="${database.pool.maxSize}"
            p:maxIdleTimeExcessConnections="${database.pool.maxIdleTime}"
            p:checkoutTimeout="${database.pool.maxWait}"
            p:acquireIncrement="${database.pool.acquireIncrement}"
            p:acquireRetryAttempts="${database.pool.acquireRetryAttempts}"
            p:acquireRetryDelay="${database.pool.acquireRetryDelay}"
            p:idleConnectionTestPeriod="${database.pool.idleConnectionTestPeriod}"
            p:preferredTestQuery="${database.pool.connectionHealthQuery}"
        />
    
  9. Save the file
  10. Create the file “$project_home/src/main/webapp/WEB-INF/spring-configuration/ticketRegistry.xml” and populate it with the following:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="
             http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
             http://www.springframework.org/schema/tx
             http://www.springframework.org/schema/tx/spring-tx-3.0.xsd" >
     
      <description>
        Configuration for the Jpa TicketRegistry which stores the tickets in a
    database and cleans them out at specified intervals.
      </description>
     
      <!-- Ticket Registry -->
      <bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.JpaTicketRegistry" />
     
      <!--
        Injects EntityManager/Factory instances into beans with
        @PersistenceUnit and @PersistenceContext
      -->
      <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
     
     
      <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter">
          <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="generateDdl" value="true"/>
            <property name="showSql" value="true" />
          </bean>
        </property>
        <property name="jpaProperties">
          <props>
            <!-- Set the database dialect -->
            <prop key="hibernate.dialect">${database.dialect}</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
     
          </props>
        </property>
      </bean>
     
      <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager"
        p:entityManagerFactory-ref="entityManagerFactory" />
     
      <tx:annotation-driven transaction-manager="transactionManager" />
     
     
      <!-- TICKET REGISTRY CLEANER -->
      <bean id="ticketRegistryCleaner"
        class="org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner"
        p:ticketRegistry-ref="ticketRegistry"
        p:lock-ref="cleanerLock" />
     
      <!--
       Use JpaLockingStrategy for 3.4.11 and later.
       This bean is only needed for HA setups where multiple nodes are attempting
       cleanup on a shared database, but it doesn't substantially impact performance
       and is easy to setup and is therefore recommended for all JpaTicketRegistry deployments.
       This component automatically creates the LOCKS table so no further configuration
       is required.
      -->
      <bean id="cleanerLock"
        class="org.jasig.cas.ticket.registry.support.JpaLockingStrategy"
        p:uniqueId="${host.name}"
        p:applicationId="cas-ticket-registry-cleaner" />
     
    
      <bean id="ticketRegistryCleanerJobDetail"
        class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
        p:targetObject-ref="ticketRegistryCleaner"
        p:targetMethod="clean" />
     
      <bean id="periodicTicketRegistryCleanerTrigger"
        class="org.springframework.scheduling.quartz.SimpleTriggerBean"
        p:jobDetail-ref="ticketRegistryCleanerJobDetail"
        p:startDelay="20000"
        p:repeatInterval="1800000" />
    </beans>
    
  11. Save the file
  12. Open your pom.xml file and make it look like the following (with your own value in the groupId property/tag)
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
        <modelVersion>4.0.0</modelVersion>
        <strong><groupId>org.example.cas</groupId></strong>
        <artifactId>local-cas</artifactId>
        <packaging>war</packaging>
        <version>1.0-SNPAPSHOT</version>
        
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <configuration>
                        <warName>cas</warName>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        
        <dependencies>
            <dependency>
                <groupId>org.jasig.cas</groupId>
                <artifactId>cas-server-webapp</artifactId>
                <version>${cas.version}</version>
                <type>war</type>
                <scope>runtime</scope>
            </dependency>
            
            <dependency>
                <groupId>org.jasig.cas</groupId>
                <artifactId>cas-server-support-ldap</artifactId>
                <version>${cas.version}</version>
            </dependency>
            
            <!--
              The below is for connection pooling.
              Leave this commented out if you do not intend to use it -->
            <dependency>
                <groupId>commons-pool</groupId>
                <artifactId>commons-pool</artifactId>
                <version>${apache.commons.pool.version}</version>
            </dependency>
            
            
            <!-- database pooling -->
            <dependency>
                <groupId>c3p0</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.1.2</version>
            </dependency>
            
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>${hibernate.core.version}</version>
                <scope>runtime</scope>
            </dependency>
         
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-entitymanager</artifactId>
                <version>${hibernate.core.version}</version>
                <scope>runtime</scope>
            </dependency>
            
        </dependencies>
        
        <properties>
            <cas.version>3.5.2</cas.version>
            <apache.commons.pool.version>1.6</apache.commons.pool.version>
            <hibernate.validator.version>4.2.0.Final</hibernate.validator.version>
            <hibernate.core.version>4.1.0.Final</hibernate.core.version>
        </properties>
        
        <repositories>
            <repository>
                <id>ja-sig</id>
                <url>http://oss.sonatype.org/content/repositories/releases/ </url>
            </repository>
            <repository>
                <id>mvn-repo</id>
                <url>http://http://mvnrepository.com/artifact/ </url>
            </repository>
            <repository>
                <id>jboss</id>
                <url>http://repository.jboss.org/nexus/content/groups/public-jboss/</url>
            </repository>
        </repositories>
    </project>
    
  13. Save the file
  14. Got to $project_home
  15. (optional) In a different window watch your catalina.out log for problems (tail -f /var/log/tomcat7/catalina.out)
  16. Build the cas.war file
     mvn clean package
  17. Wait for Tomcat to recognize the new war file, then stop and restart tomcat
     service tomcat stop
     service tomcat start
  18. At this point, if your SSL, LDAP, and PostgreSQL are setup correctly, you shouldn’t see any errors when Tomcat starts (you will see errors before you stop Tomcat).
    Navigate to your CAS server in a web browser and try to authenticate with a valid account.  If everything is working, you should be sent to the logged in screen and see the validation successfully occur in your Catalina.out log.

 

Stoping PostgreSQL’s Database From Getting Bloated

The database created by CAS in Postgres has several “oid” fields.  This really shouldn’t be a problem, except that the JDBC and ODBC drivers for PostgreSQL don’t handle unlinking them correctly.  So when your tickets get cleaned up, the referenced object get orphaned, bloating the largeobject, shdepend, and largeobject_meta_data tables in the pg_catatlog.

To make this not happen, we need to inform the large object manager that these objects need to be cleaned.

Run the following SQL statements to create the needed triggers:

CREATE EXTENSION lo;

CREATE TRIGGER t_st_expiration_policy BEFORE UPDATE OR DELETE ON public.serviceticket
FOR EACH ROW EXECUTE PROCEDURE lo_manage(expiration_policy);

CREATE TRIGGER t_st_service BEFORE UPDATE OR DELETE ON public.serviceticket
FOR EACH ROW EXECUTE PROCEDURE lo_manage(service);

CREATE TRIGGER t_tgt_expiration_policy BEFORE UPDATE OR DELETE ON public.ticketgrantingticket
FOR EACH ROW EXECUTE PROCEDURE lo_manage(expiration_policy);

CREATE TRIGGER t_tgt_authentication BEFORE UPDATE OR DELETE ON public.ticketgrantingticket
FOR EACH ROW EXECUTE PROCEDURE lo_manage(authentication);

CREATE TRIGGER t_tgt_services_g_a_t BEFORE UPDATE OR DELETE ON public.ticketgrantingticket
FOR EACH ROW EXECUTE PROCEDURE lo_manage(services_granted_access_to);

I have noticed that in the log, there are periodic exceptions from C3P0 where an unlinked OID is referenced again by the ticket cleaner; however, we have not experienced any problems from our users in relation to this and the database we have hovers around 35-40MB in size, unlike the 6.8GB it grew to with out these triggers.

Introduction

This post will cover the installation of Tomcat 7 and Central Authentication Service (CAS) 3.52.  Although the process is pretty straight forward, there are a lot of bits of information scattered all around for setting these two things up and I figured bringing those to a single place would be useful for others.

Beyond installing CAS and Tomcat 7, this post will cover:

  • Configuring and Hardening (SSL wise) Tomcat 7

 

The next part will cover:

  • Authenticating against LDAP
  • Using the JPA Ticket Registry with Postgresql

Installing Tomcat 7 and Needed Libraries

To setup Tomcat 7, its needed libraries, and the libraries you will need for CAS, run the following commands:

sudo apt-get update
sudo apt-get install tomcat7 maven2 default-jdk libctnative-1 libssl-dev

The above commands will install

  • maven2  → This will be used for CAS.  We will be using the WAR overlay method.
  • Tomcat 7
  • default-jdk (openjdk-6) → This is needed by CAS
  • libctnative-1 → Installs libraries that will be needed by Apache Portable Runtime
  • libssl-dev  → Installs libraries for SSL

After the installation there will be several new environment variables that you may want to set (though Tomcat figures them out automatically).

  • CATALINA_HOME = /usr/share/tomcat7
  • CATALINA_BASE = /var/lib/tomcat7     (we’ll be doing most of our configuring here)
  • JAVA_HOME = /usr/lib/jvm/java-6-openjdk-amd64

Note: For the rest of this post and in the other parts of this series, I will use the above variable names.

My JAVA_HOME Path is Different! What Do I Do?

It is possible that your JAVA_HOME path may be different.  If you are not running a 64-bit system, the path will probably be /usr/lib/jvm/java-6-openjdk.

If this is not your path you can use the find command to find the cacerts file, which should help you get the path (minus the /jre/lib/security/ part) like so:

find / -name "cacerts"

Or you can check your java alternatives using the update-java-alternatives command, like so:

update-java-alternatives -l

Note: If your java path does not fit /usr/lib/jvm/java-6-openjdk-*  or /usr/lib/jvm/java-7-openjdk-*, you will need to set the environment variable JAVA_HOME because Tomcat will not find it.

Configuring Tomcat 7 for SSL

To setup Tomcat to use SSL you will need to either have a self signed certificate or a signed certificate from a Certificate Authority like DigiCert or Verisign.  If you want to create a self signed cert, check out this tutorial Creating a Self Signed Cert.  If you already have a signed certificate then you can move on to the next part.

When configuring Tomcat to use SSL, you have two options for how you want to do it.  The first way is to import the certificate into the cacerts file and use the keystoreFile and keystorePass attributes in the Connector declaration in the server.xml file for Tomcat.  If you want to use this approach MuleSoft has a good tutorial on how to do it here (This tutorial also includes the second method close to the bottom).

The other way (the one I’m doing) is to use the attributes SSLCertificateFile and SSLCertificateKeyFile to reference the files directly.  Whichever way you choose to go, be sure to define the ciphers attribute like I have done in the code snippet below.  This is needed because by default Tomcat will use whatever ciphers the Java library has to offer and some of these are very weak.

Once you have your SSL certificate and key file ready and stored in some place like /etc/ssl/certs you can move on to configuring Tomcat.  For this part

  1. Edit $CATALINA_BASE/conf/server.xmlUn-comment the following section:
    <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
        maxThreads="150" scheme="https" secure="true"
        clientAuth="false" sslProtocol="TLS" />
    

    And make it look like

    <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
                   maxThreads="150" scheme="https" secure="true"
                   clientAuth="false" sslProtocol="TLS"
                   SSLCertificateFile="/some/path/cert.crt"
                   SSLCertificateKeyFile="/some/path/cert.key"
                   ciphers="TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256,
                            TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
                            TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
                            SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
                            SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA" />
    

    Note: If you are sure that you will not have any Windows XP clients, you can remove the 3DES ciphers.

  2. Save the file
  3. Edit /etc/default/tomcat7 and add “-Dhttps.protocols=TLSv1” to the JAVA_OPTS line. Mine looks like this:
    JAVA_OPTS="-Djava.awt.headless=true -Xms512M -Xmx1024M -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=512m -XX:+CMSIncrementalMode -Dhttps.protocols=TLSv1"
    

    Note: You need to make this change because java will allow different ciphers than your restricted CAS instance, which will cause SSL issues.
    Note 2: The other parameters in the above line let the JVM allocate more memory for your CAS instance and have it clean said memory more often to make more efficient use of it.  It would probably be a good idea to use these flags as well, just adjusted to your servers capability.

  4. Save the file
  5. Restart tomcat
    /etc/init.d/tomcat7 restart

    Note: you may also want to watch the catalina log file in another console using

    tail -f $CATALINA_BASE/catalina.out
  6. Open a browser and navigate to https://your_server:8443.  If everything worked, you should see the “It Works” screen

Wait!  I Want to Use Port 80 and 443, not 8080 and 8443

If you want to use the standard HTTP and SSL ports of 80 and 443, make sure of the following first

  • No other application is binding to port 80 or 443
  • You are not using IP v6 (this is a restriction of AuthBind)

 

  1. Edit $CATALINA_BASE/conf/server.xml and change the following section:
    <Connector port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   URIEncoding="UTF-8"
                   server="Apache"
                   redirectPort="8443" />
    

    And make it look like

    <Connector port="80" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   URIEncoding="UTF-8"
                   server="Apache"
                   redirectPort="443" />
    
  2. Edit /etc/default/tomcat7  and make the AUTHBIND line look like:
    AUTHBIND=yes
    
  3. Restart tomcat

 

HTTPS Everywhere

Once you have HTTPS/SSL working from the previous part, let’s configure Tomcat so every site goes through HTTPS.

  1. Edit $CATALINA_BASE/conf/web.xml
  2. Add the following code snippet to the end of the file, just above the </web-app> line at the end of the file
        <security-constraint>
            <web-resource-collection>
                <web-resource-name>Protected Context</web-resource-name>
                    <url-pattern>/*</url-pattern>
            </web-resource-collection>
            <!-- auth-constraint goes here if you require authentication -->
            <user-data-constraint>
                 <transport-guarantee>CONFIDENTIAL</transport-guarantee>
            </user-data-constraint>
        </security-constraint>
    
  3. Save the file and restart Tomcat7
  4. Open a web browser and navigate to http://your_server:8080.  You should be redirected to https://your_server:8443.

 

Setting Up The CAS WAR Overlay

One of the easiest and recommend ways to setup CAS is to use the Maven WAR Overlay method. To do this, do the following: (or you can follow the guide from Jasig)

  1.   Create the workspace directory, in the future referred to as $project_home
    mkdir /opt/work/cas-local
    
  2. Create a file called pom.xml in the created folder, add add the following to it:
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd ">
        <modelVersion>4.0.0</modelVersion>
        <groupId>edu.university.cas</groupId>
        <artifactId>local-cas</artifactId>
        <packaging>war</packaging>
        <version>1.0-SNAPSHOT</version>
    
        <build>
            <plugins>
                <plugin>
                     <artifactId>maven-war-plugin</artifactId>
                                 <configuration>
                                     <warName>cas</warName>
                                 </configuration>
                            </plugin>
            </plugins>
        </build>
    
        <dependencies>
            <dependency>
                <groupId>org.jasig.cas</groupId>
                <artifactId>cas-server-webapp</artifactId>
                <version>${cas.version}</version>
                <type>war</type>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
    
        <properties>
            <cas.version>3.5.2</cas.version>
        </properties>
    
            <repositories>
                 <repository>
                      <id>ja-sig</id>
                      <url>http://oss.sonatype.org/content/repositories/releases/ </url>
                 </repository>
            </repositories>
    </project>
    
  3. Change the groupId tag to have the url for your place of business.
  4. Next we will add the parts that actually make our CAS package building useful.  Our customizations.Create the directory $project_home/src/main/webapp/WEB-INF
  5. Create a file called deployerConfigContext.xml in the new folder, and populate it with this contents.
  6. (Optional) The default service context for CAS will work just fine out of the box, but only restrict traffic to IMAP(S) or HTTP(S)If you would like to be a little more restrictive to say, just your domain, you will want to change the following line in deployerConfigContext.xml
    <property name="serviceId" value="^(https?|imap?)://.*" />

    To something more restrictive, like:

    <property name="serviceId" value="^(https?|imaps?)://([A-za-z0-9_-]+\.)*(your_domain\.com)(:\d{1,5})?/.*" />

    This will restrict service to HTTP(S) or IMAP(S) from your domain only, with a possible port number in the url.
    Note: the “(:\d{1,5})?” part is not needed if you use port 80 and 443 for CAS.

  7. Create a file called cas.properties in the same folder, and populate it with this contents.
  8. Alter the following line:
    server.name=https://localhost:8080

    and change it to

    server.name=https://<your domain>:<port_number>

    e.x. server.name=http://cas.example.com:8443

  9. Alter this line
    host.name=cas01.example.org

    and change it to

    host.name=<your cas servers FQDN>

    e.x. host.name=cas.example.com

  10. Save the cas.properties file.
  11. (Optional) If you want to setup throttling of repeat authentication attemps
    1. Create the file $project_home/src/main/webapp/WEB-INF/cas-servlet.xml and populate it with this contents.
    2. Change the following bean:
        <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping" p:flowRegistry-ref="flowRegistry"
              p:order="2">
          <property name="interceptors">
            <ref local="localeChangeInterceptor"/>
          </property>
        </bean>
      

      to

        <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping" p:flowRegistry-ref="flowRegistry"
              p:order="2">
          <property name="interceptors">
                <list>
                      <ref local="localeChangeInterceptor"/>
                      <ref bean="throttleInterceptor"/>
                </list>
          </property>
        </bean>
      
    3. Create the file $project_home/src/main/webapp/WEB-INF/spring-configuration/throttleInterceptorTrigger.xml and put the following in it:
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:p="http://www.springframework.org/schema/p"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
      
      <bean id="throttleInterceptor" class="org.jasig.cas.web.support.InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter"
         p:failureRangeInSeconds="120"
         p:failureThreshold="100"/>
      
      <bean id="throttleInterceptorJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
         p:targetObject-ref="throttleInterceptor"
         p:targetMethod="decrementCounts" />
      
      <bean id="periodicThrottleCleanerTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"
         p:jobDetail-ref="throttleInterceptorJobDetail"
         p:startDelay="0"
         p:repeatInterval="1000" />
      </beans>
      
  12. (Optional) If you want to setup a ticket expiration policy for your Ticket Granting Tickets
    1. Create the file $project_home/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml
    2. Populate it with this contents.
    3. If you want to change the default ticket expiration settings you will want to alter the attributes p:maxTimeToLiveInSeconds and p:timeToKillInSeconds 
  13. Change directory to $project_home
  14. Run the following command to build you cas package (this cleans the workspace and builds the package).
    mvn clean package
  15. Create a soft link for freshly built cas.war file for Tomcat 7
    ln -s target/cas.war /var/lib/tomcat7/webapps/cas.war

    The new package should be automatically deployed by Tomcat

    NOTE: When rebuilding the cas.war file because you’ve made changes to the configuration, you will want to issue the following commands:

    service tomcat7 stop
    service tomcat7 start

    This is because Tomcat will re-deploy the cas.war and throw a bunch of errors in the log and act wonky, until you stop and restart Tomcat.

  16. Open a browser and navigate to “https://<your_cas_server&gt;:8443/cas” (minus the port number if you are using port 80 and 443).  You should see the CAS login screen.

 

At this point CAS should be up and hopefully there aren’t any errors being spat in your /var/log/tomcat7/catalina.out log.  But, CAS isn’t all the useful yet.  It just gives you a nice login box to no where.

In the next part I’ll discuss several things to make CAS more useful:

  1. Setting up PostgreSQL ticket management
  2. Configuring CAS to use LDAP
  3. Configuring CAS to use pooling for LDAP and PostgreSQL

In this part of the series I will cover how to create your first job in Jenkins which will make use of all of the tools installed in the previous two sections of this series.

If you haven’t seen the previous two sections, they can be reached via the links below.

Part 1: Installing the PHP Tools and Jenkins

Part 2: Installing the Plugins and Configuring Jenkins

To begin I will talk about the directories and files you will need to have in your repository for your build to be successful, followed by a discussion of each of those files list, and then then I will cover the creation of your first job.  But, first, I will provide the links to the downloads in this post, up front for convenience.

Downloads

For convenience, here are all of the downloads covered in this post, so you don’t need to ferret out the links.

  1. Project directory structure and files –> here
  2. Jenkins job template –> here

Directory Structure and Files

Below is the minimal directory structure and files that you will need for the build script to work in Jenkins.  StructureExample is the root folder for the repository (just think of it as the trunk folder in a subversion repository)

Screen Shot 2013-05-15 at 2.57.59 PM

Directory structure and minimal files

From the above image you can see that there are two files “build.xml” and “phpunit.xml” in the root directory.

  • build.xml is an XML file that tells Ant what to do when performing a build.
  • phpunit.xml defines several parameters for PHPUnit to use during its portion of the build.

The location of these files are pretty standard; however, what is not so standard is that I have split the unit tests out into separate directories under the tests directory.  This was done to keep all of the PHP Unit tests separate from the SimpleTest tests.

For convenience you download the above directory structure and files  here.  The download included several versions of build.xml (build.xml.both, build.xml.simpletest, build.xml.phpunit) so you can pick the appropriate version for your build by renaming the appropriate file to build.xml.

Build.xml

The build.xml file is the most important file in the directory; it tells ant what to do during execution.  The file is a little long, roughly 178 lines and created the directory structure shown below.  Since this file is so important I will cover the sections that will be used while performing a build on Jenkins.

Jenkins Build directory

 

The fist thing to do is to put the name of your project in the file, in the name attribute for this line:

<project name="your-project-name-here" default="build">

Note that the above line also sets the default target to perform “build”. This line;

<target name="build"
        depends="prepare,lint,phploc,pdepend,phpmd-ci,phpcs-ci,phpcpd,phpdoc,phpunit,simpletest,phpcb"/>

This target depends on several other targets to be successful (meaning each of the dependent targets need to complete successfully).

Dependent Targets:

  • prepare –> Ensures that the workspace is setup correctly by wiping out old folders and remaking them.
  • lint –> calls php -l, to perform a syntax check on all of the php files in the directory.
  • phploc –> Uses PHP Loc to determine the number of lines of code in the project.
  • pdepend –> Runs pDepend and creates the dependency reports.
  • phpmd-ci –> Runs PHP Mess Detector to check for code smells and generates some reports of the results.
  • phpcs-ci –> Runs PHP CodeSniffer on the code in the project to check for coding standard violations and generats a report of the results.
  • phpcpd –> Runs PHP Copy Paste Detector which looks for duplicate lines of code.
  • phpdoc –> Generates API documentation using PHP Documentor 2
  • phpunit –> Performs the tests located in tests/phpunit, and uses phpunit.xml for its configuration. After which it creates a report of the results
  • simpletest –> Performs the tests in the TestSuite defined in test/simpletest/TestSuite.php, and then creates a report of the results
  • phpcb –> Creates a browsable report of your code

Target Definitions

Prepare

The prepare target depends on the clean target, which removes all the directories under the build directory in the workspace. After which, the prepare target re-creates them. ${basedir} contains the path to the current projects (jobs) workspace. For example, if your job was called “example-job” is would contain the path /var/lib/jenkins/jobs/example-job/workspace.

<target name="clean" description="Cleanup build artifacts">
   <delete dir="${basedir}/build/api"/>
   <delete dir="${basedir}/build/code-browser"/>
   <delete dir="${basedir}/build/coverage"/>
   <delete dir="${basedir}/build/logs"/>
   <delete dir="${basedir}/build/pdepend"/>
</target>

<target name="prepare" depends="clean" description="Prepare for build">
    <mkdir dir="${basedir}/build/api"/>
    <mkdir dir="${basedir}/build/code-browser"/>
    <mkdir dir="${basedir}/build/coverage"/>
    <mkdir dir="${basedir}/build/logs"/>
    <mkdir dir="${basedir}/build/pdepend"/>
</target>

Lint

This target uses the php command with the -l parameter to perform a syntax check on the php files in the project. While excluding the tests directory from its check.

<target name="lint" description="Perform syntax check of sourcecode files">
    <apply executable="php" failonerror="true">
        <arg value="-l" />

        <fileset dir="${basedir}">
            <include name="**/*.php" />
            <!-- exclude the tests directory, repeat line below to exclude more -->
            <exclude name="tests/**" />
            <modified />
        </fileset>

        <fileset dir="${basedir}/tests">
            <include name="**/*.php" />
            <modified />
        </fileset>
    </apply>
</target>

Phploc

This uses PHP Loc to determine the size of the project. The constructed command looks like phploc –exclude ${basedir}/tests –log-csv ${basedir}/build/logs/phploc.csv ${basedir}.
This command tells phploc to look at the code in ${basedir} but exclude the tests directory and to publish the report to build/logs/phploc.csv

<target name="phploc" description="Measure project size using PHPLOC">
    <exec executable="phploc">
        <!-- exclude tests directory -->
        <arg value="--exclude" />
        <arg path="${basedir}/tests" />
        <arg value="--log-csv" />
        <arg value="${basedir}/build/logs/phploc.csv" />
        <arg path="${basedir}" />
    </exec>
</target>

Pdepend

This target uses PHP Depend to create several metric reports about your code.

  1. An Overview pyramid covering inheritance, size and complexity, and coupling. You can find information about the pyramid here
  2. An Abstract Instability Chart, which you can find information on here

The target, again ignores the tests directory, as most of the targets do.

<target name="pdepend" description="Calculate software metrics using PHP_Depend">
    <exec executable="pdepend">
        <arg value="--ignore=${basedir}/tests,${basedir}/docs" />
        <arg value="--jdepend-xml=${basedir}/build/logs/jdepend.xml" />
        <arg value="--jdepend-chart=${basedir}/build/pdepend/dependencies.svg" />
        <arg value="--overview-pyramid=${basedir}/build/pdepend/overview-pyramid.svg" />
        <arg path="${basedir}" />
    </exec>
</target>

Phpmd-ci

This target uses PHP Mess Detector to check your code for common smells (indicators of possible bad coding or design). There are several rulesets you can use when checking your code: codesize, controversial, design, naming, and unusedcode. You can read about the rulesets here to figure out what each one covers.

You can also define your own rulesets of modify existing rulesets using an xml file. To learn more about there go here and checkout the menu on the right.

The current target configuration tells phpmd to create an xml report, using the codesize ruleset, and place the report in build/logs/pmd.xml. It should examine the current workspace and exclude the test directory.

<target name="phpmd-ci" description="Perform project mess detection using PHPMD creating a log file for the continuous integration server">
    <exec executable="phpmd">
        <arg path="${basedir}" />
        <arg value="xml" />
        <arg value="codesize" />
        <arg value="--reportfile" />
        <arg value="${basedir}/build/logs/pmd.xml" />
        <arg value="--exclude" />
        <arg value="${basedir}/tests" />
    </exec>
</target>

Phpcs-ci

This target uses PHP CodeSniffer to check the code in the project, excluding the tests directory, for Zend coding standard violations. The results of the analysis are then put into a checkstyle report in build/logs/checkstyle.xml.

To learn about the available standards and functionality of PHP CodeSniffer, visit here

<target name="phpcs-ci"
        description="Find coding standard violations using PHP_CodeSniffer creating a log file for the continuous integration server">
    <exec executable="phpcs" output="/dev/null">
        <arg value="--report=checkstyle" />
        <arg value="--report-file=${basedir}/build/logs/checkstyle.xml" />
        <arg value="--standard=Zend" />
        <arg value="--ignore=${basedir}/tests" />
        <arg value="--extensions=php" />
        <arg path="${basedir}" />
    </exec>
</target>

Phpcpd

This target uses PHP Copy Paste Detecter to check for duplicate lines of code in your project, excluding the tests directory, and outputting the results in build/logs/pmd-cpd.xml

<target name="phpcpd" description="Find duplicate code using PHPCPD">
    <exec executable="phpcpd">
        <arg value="--log-pmd" />
        <arg value="${basedir}/build/logs/pmd-cpd.xml" />
        <arg value="--exclude" />
        <arg path="${basedir}/tests" />
        <arg path="${basedir}" />
    </exec>
</target>

Phpdoc

This target uses PHP Documentor 2 to create API documentation of your code. You can find documentation about using phpdoc here.

<target name="phpdoc" description="Generate API documentation using phpDocumentor 2">
    <exec dir="${basedir}" executable="phpdoc" failonerror="true">
        <!-- excluded directories need the / at the end, else phpdoc ignores it -->
        <arg line="--title='test title' -d . -t build/api -i tests/ " />
    </exec>
</target>

Phpunit

This target runs your unit tests using PHP Unit. By default, PHP Unit checks the current directory for a configuration xml file, which is our phpunit.xml file. Because we took this approach, there is not much in the definition of this target.

However, you can find plenty about what you can put in the configuration file here

<target name="phpunit" description="Run unit tests with PHPUnit">
    <exec executable="phpunit" failonerror="true"/>
</target>

Simpletest

This target defines how to perform our unit/functionality tests using SimpleTest. You’ll notice that this target definition is a little different than the others. The reason is, is that SimpleTest does not have a nice command line tool that takes parameters like the other tools we are using. To make SimpleTest work, we need to create a little inline bash script.

The one-liner bash script that we will use tells the shell to use php to run TestSuite.php file and then send its output to XML Lint for formatting and output into build/logs/simpletest-results.xml

<!-- Inline bash script to run TestSuite.php and pipe its results to xmllint -->
<!-- XML Lint then outputs the results to simpletest-results.xml -->
<target name="simpletest" description="Run SimpleTest unit tests">
    <exec dir="${basedir}" executable="bash" failonerror="true">
        <arg value="-c"/>
        <arg line='"php tests/simpletest/TestSuite.php | xmllint --format -o build/logs/simpletest-results.xml -"' />
    </exec>
</target>

Phpcb

This target uses PHP CodeBrowser to create a browsable interface of your code in your project.

<target name="phpcb" description="Aggregate tool output with PHP_CodeBrowser">
    <exec executable="phpcb">
       <arg value="--log" />
       <arg path="${basedir}/build/logs" />
       <arg value="--ignore" />
       <arg path="${basedir}/tests" />
       <arg value="--source" />
       <arg path="${basedir}" />
       <arg value="--output" />
       <arg path="${basedir}/build/code-browser" />
    </exec>
</target>

phpunit.xml

This file is used to store our configuration for running PHP Unit.

For the over definition, we tell php unit to not backup and restore $GLOBALS or static attributes of user defined classes, to run in strict mode and have verbose output.

For our test suite settings, we set the name (which you should change) and tell php unit to run all of the tests in the php files in tests/phpunit.

Next for our logging settings, we tell PHP Unit to create a coverage report in build/coverage, with the title of your project (don’t forget to set this attribute value) and several other attributes. We also want a coverage-clover report in build/logs/clover.xml and a junit report in build/logs/junit.xml

To learn creating your own PHP Unit configuration file visit here

<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         strict="true"
         verbose="true">

  <testsuites>
    <testsuite name="Your Test Suit Name Here">
      <directory suffix='.php'>./tests/phpunit</directory>
    </testsuite>
  </testsuites>

  <logging>
    <log type="coverage-html" target="build/coverage" title="Your Project Name Here"
         charset="UTF-8" yui="true" highlight="true"
         lowUpperBound="35" highLowerBound="70"/>
    <log type="coverage-clover" target="build/logs/clover.xml"/>
    <log type="junit" target="build/logs/junit.xml" logIncompleteSkipped="false"/>
  </logging>

</phpunit>

TestSuite.php

This file defines out Test Suite for SimpleTest. For our test suite, we define the class MyTests and add the Test Files we want to run using the addFile method. Take note of the use of the magic word __DIR__. This is used so no matter where this file is being invoked from, we won’t have to worry about how the test files are included.

To run the tests in the test suite, a new instance of the class is made and then ran using the JUnitXMLReporter class, so we can jUnit and xUnit compatible output. I had noticed in some other blogs that there was a problem with the header and footer printing in non xml; however, this approach does not have that problem.

/**
 * Include the simpletest file so we can extend TestSuite
 */
require_once 'simpletest/simpletest.php';
/**
 * Include the JUnitXMLReporter class so the output is jUnit and xUnit compatible
 */
require_once 'simpletest/extensions/junit_xml_reporter.php';

class MyTests extends TestSuite
{
    function __construct()
    {
        parent::__construct();

        //add the files containing the tests we want to perform
        //note the use of __DIR__ so we don't have to worry about file location
        //when running the tests in Jenkins
        $this->addFile(__DIR__.'/TemplateTest.php');
    }

}

$test = new MyTests();
$test->run(new JUnitXMLReporter());

Example Test File – TemplateTest.php

I thought it might be pertinent to show an example test file for use with the Test Suite defined above. In the code below, you will notice that I never include simpletest/autorun.php, I only include simpletest/web_tester.php. This is because I want complete control over the running of the tests and their output.

<?php
require_once 'simpletest/web_tester.php';

class TemplateTest extends WebTestCase {
    function testLoginPageShowForm() {
        $this->get('https://example.domain.com/site/login/form.php');
	$this->assertFieldById('_userName');
    }
}

StackTest.php

This is a simple PHP Unit test file that just does some pushing and popping of an array. In fact, it is the example from chapter 4 “Writing Tests for PHPUnit” from PHP Units web site here

<?php
/**
 * Include the PHPUnit Framwork so we can extend the TestCase class
 */
require_once 'PHPUnit/Autoload.php';

class StackTest extends PHPUnit_Framework_TestCase
{
    /**
     * Tests pushing and popping of elements on and off of an array
     *
     * @return Void
     */
    public function testPushAndPop()
    {
        $stack = array();
        $this->assertEquals(0, count($stack));

        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertEquals(1, count($stack));

        $this->assertEquals('foo', array_pop($stack));
        $this->assertEquals(0, count($stack));
    }
}

Setting Up Your First Jenkins Job

This section will cover setting up your first job in jenkins using an existing template. The first thing you will need to do is add the job template to Jenkins.

Adding the job-template To Jenkins

There is a lot of configuration that needs to be done to use all the tools that we’ve installed and referenced in our build.xml file. So, let’s cut out a lot of the work and use an existing job template that has the majority of work done for us already.

  1. Go to the Jenkins job directory
    $cd /var/lib/jenkins/job
  2. Download the job template
    $wget https://googledrive.com/host/0B8dI-j7Xg0phVDRjMnN6QUhfcXc -O job-template.tar.gz
  3. Extract the job template
    $tar xvf job-template.tar.gz
  4. Set the permissions on the directory
    $chown -R jenkins:nogroup job-template/

Setting up the Jenkins Job

  1. Login to Jenkins and click “New Job”
  2. Enter the name of your job

    NOTE: It is a good idea to not have spaces in your job name as it can cause problems

  3. Select “Copy Existing Job”
  4. In the “Copy From” field, type “job-template” (without the quotes)Your screen should look like this

    Jenkins - New Job Screen

  5. Click “OK”
  6. Uncheck “Disable Build”
  7. If you want to use project based security.
    Check “Enable project-based security”You’ll see a permission matrix appear and a field to add a user or group to the matrix

    Jenkins - Project Based Security

    Enter the user or group you want to set the rights for into the “User/group to add” field and click add
    Then set their permissions

    NOTE: Make sure to add yourself or you’ll encounter an issue where your build history disappears until you restart Jenkins

  8. Under the “Source Code Management” section, select “Subversion
  9. Enter the URL to your subversion repository in the “Repository URL” field

    Jenkins - Subversion Settings
    If Jenkins cannot talk to your repository, you may need to enter your credentials.

    1. Click the “enter credentials” link in the error message
    2. Select Username/password authentication
    3. Enter your credentials
    4. Click OK

      Jenkins - Subversion Authentication

  10. Under the “Build Triggers” sectionSelect Poll SCM and use the document linked in the button to determine what you need to enter for your schedule
  11. If you do not want to use PHP Unit or SimpleTest
    Way down under the “Publish xUnit test result report” section, click the delete button for for the unit test suite you don’t want to use.
    PHP Unit

    Jenkins - PHP Unit Settings

    SimpleTest
    Jenkins - SimpleTest Settings

  12. Click Save
    Note: If you click Apply and then Save, Jenkins throws a parse error
  13. With your job created, you can now perform a build by clicking “Build” on the left side of the screen

As a final note, you will not see many graphs on the main page of a job until you have performed at least two builds.

In the first part of this series I covered installing several PHP tools for continuous integration testing (PHPUnit, Mess Detecter, Copy Paste Detecter, Code Sniffer, Code Coverage, Documenter 2, Lines of Code, and Simple Test) and installing Jenkins.  You can find the first part here  In this part I will cover installing the needed plugins to use the installed PHP tools and to authenticate to active directory, and to use HTTPS (SSL) instead of the standard HTTP connection.

If the Jenkins server is currently running (if you just finished up part 1, it probably is) stop the server by running the following command

$/etc/init.d/jenkins stop

Setup the LDAP / AD Certificate

If you care planning to connect to an LDAP server or Active Directory and use LDAPS when doing so, you will need to let Jenkins know about the certificate the server has. To do so following the following steps:

Download the InstallCert tool from here

Install the unzip application

$apt-get install unzip

Unzip the tool

$unzip InstallCert.zip

Move the tool the the Java bin location

$mv InstallCert* $JAVA_HOME/jre/bin

Get the servers certificate. If you have multiple servers to get certs from, repeat this step for each.

$java InstallCert someServer.example.com:636

If you are prompted for anything, just press enter to continue on.

The certificate(s) will be placed in a file called jssecerts. Now we need to import this file into the cacerts file that java uses.

$keytool --importkeystore -srckeystore jssecacerts -destkeystore $JAVA_HOME/jre/lib/security/cacerts -noprompt

The password for the keystore is “changeit”.

To inform Jenkins of the cacerts file, edit the file /etc/defaults/jenkins and add the line to the file before the end (preferable under the commented out JAVA_ARGS line)

JAVA_ARGS="-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts"

Setting Up SSL (HTTPS)

Edit the file /etc/defaults/jenkins and add the following lines

HTTPS_PORT=8443

HTTPS_KEYSTORE=/etc/ssl/certs/java/YourCertFile.crt
HTTPS_KEYSTORE_KEY=/etc/ssl/certs/java/YourCertKeyFile.key

Also set HTTP_PORT line to -1 to disable it

HTTP_PORT = -1

At the bottom of the file, set the JENKINS_ARGS line to the following

JENKINS_ARGS="--webroot=/var/cache/jenkins/war --httpPort=$HTTP_PORT --ajp13Port=$AJP_PORT --httpsPort=$HTTPS_PORT --httpsCertificate=$HTTPS_KEYSTORE --httpsPrivateKey=$HTTPS_KEYSTORE_KEY"

Installing the Plugins

  1. Open a web browser and go to https://yourServer. example.com:8443
  2. Click on Manage Jenkins
  3. Click on Manage Plugins
  4. Select the “Available” tab
  5. Select the following plugins
    • Active Directory Plugin
    • checkstyle
    • clover php plugin
    • dry
    • html publisher plugin
    • jdepend plugin
    • plot plugin
    • pmd plugin
    • violations
    • xUnit plugin
  6. Click “Install without Restart”

Setup Authentication to Active Directory

  1. Go to Manage Jenkins -> Configure Global Security
  2. Check “Enable Security”
  3. Select “Active Directory” under the Security Realm section
  4. Click “Advanced”
  5. Set the following fields:Domain Name: example.com
    Bind DN: CN=someUser,OU=Users,DC=example,DC=com
    Bind Password: TheBindUsersPassword
  6. Click “Test”
  7. If all is good, click “Apply”, else trouble shoot the issue
  8. Click “Save”
  9. Try to authenticate as a known user by clicking “log in” in the upper right corder and authenticating

NOTE: This setup did not work when I entered a domain controller. If I left the field blank, Jenkins was able to find the appropriate server and authenticate without issue.

Setting up LDAP Authentication

If you don’t want to use the active directory plugin, you can also authenticate using LDAP functionality Jenkins already has.

  1. Go to Manage Jenkins -> Configure Global Security
  2. Check “Enable Security”
  3. Under the Security Realm section, select “LDAP”
  4. Set the following fields:Server: ldaps://someServer.example.com:636
    User Search Filter: sAMAccountName={0}
    Manager DN:CN=someUser,OU=Users,DN=example,DN=com
    Manager Password:The manager users password
  5. Click “Apply”
  6. Click “Save”
  7. Test authenticating as a known user by clicking the “log in” link in the upper right corner and trying to log in

NOTE: If you intend to authenticate to an LDAP server like eDir (Novell) do not set the User Search Field to sAMAccountName={0} as it will not work

A Little Extra Security

To help prevent Cross-Site Scripting do the following:

  1. Go to Manage Jenkins -> Configure Global Security
  2. Check “Prevent Cross Site Request Forgery Exploits”
  3. Select “Default Crumb Issuer”
  4. Click “Apply”
  5. Click “Save”

Configuring Access

Now that you have users authenticating to Jenkins, you should limit what they can do. By default, Jenkins allows all users to do all things.

  1. Go to Manage Jenkins -> Configure Global SecurityUnder the “Authorization” section select “Project-based Matrix Authorization Strategy”
  2. Enter your username in the “User/group to add” field and click the “Add” button
  3. You should probably give yourself full permissions, you can do this quickly by clicking the image next to the red X on the right side of the row for your user
  4. If you want to add a group, just enter the group name in the “User/group to add” filed and click add. You used to have to prefix groups with ROLE_, but this is no longer required
  5. Set the permissions for the group or users you add to the list

NOTE: Under this authorization scheme, the permissions given to the users or groups here should be their base permissions site wide. In other words, give them the minimum amount here. Then in the projects they are a working on, you can specify additional rights under the “job configuration screen” for the project.

This is part one of a multi part series where I intend to cover the installation of Jenkins and several PHP tools to use for continuous integration. Along with the configuration of Jenkins and setting up a project and the build xml. This tutorial covers all of these steps in an Ubuntu 12.04 environment.

In this part of the series I will cover installing the following software and tools:

  • Ant
  • Pear
  • PHP 5.4
  • PHP Code Browser
  • PHP Code Coverage
  • PHP Code Sniffer
  • PHP Copy Past Detector
  • PHP Documenter 2
  • PHP Lines of Code
  • PHP Mess Detector
  • PHP Unit
  • Simple Test
  • XML Lint
  • XSL
  • XDebug

Installing PHP 5.4 and Pear

Ubuntu 12.04 by default installs PHP 5.3, so we will need the PPA repository where PHP 5.4 is available from. To do this, we will use the add-apt-repository tool. First we will need to install this tool and then add the repository.

Adding the tool

$apt-get install python-software-properties

Adding the repo

$add-apt-repository ppa:ondrej/php5

Installing PHP 5.4, PHP5.4 dev tools, and pear

$apt-get install php-pear php5-cli php5-common php5 php5-dev

Installing Ant, XSL, XML Lint, and XDebug

Ant will be used to run our build script later.  XSL and XDebug will be used by PHPUnit.  XML Lint will be used to format our reports created by SimpleTest.

To perform the installation of these tools, run the following command.

$apt-get install ant php5-xsl php5-xdebug libxml2-utils

Installing the PHP Tools

Setting up Auto Channel Discovery

$pear config-set auto_discover 1

PHP Code Browser

$pear channel-discover pear.phpqatools.org
$pear install phpqatools/PHP_CodeBrowser
$pear install pear/Text_Highlighter-beta

PHP Code Coverage

$pear install pear.phpunit.de/PHP_CodeCoverage

PHP Code Sniffer

$pear install --alldeps PHP_CodeSniffer

PHP Copy Paste Detecter

PHP-CPD requires the ConsoleTools from ezComponents, so let us take care of that first

$pear channel-discover components.ez.no
$pear install ezc/ConsoleTools

Now we can install PHP-CPD

$pear install --alldeps pear.phpunit.de/phpcpd

PHP Documenter 2

To use the inheritance chart feature, you’ll need to install GraphViz like so (this is not needed, but it’s nice)

$apt-get install graphviz

Now we can install PHP Documenter 2 after discovering its channel

$pear channel-discover pear.phpdoc.org
$pear install --alldeps phpdoc/phpDocumentor-alpha

PHP Lines of Code

$pear install --alldeps pear.phpunit.de/phploc

PHP Mess Detector

To install PHP-MD, we first need to install imagemagick and libmagickwand-dev to take care of the dependencies for imagick.

Installing imagemagick and libmagickwand-dev

$sudo apt-get install imagemagick libmagickwand-dev

Currently the stable version of imagick does not support PHP 5.4, so we need to grab the RC version by doing the following

$pecl config-set preferred_state beta
$pecl install imagick
$pecl config-set preferred_state stable

Now we can install PHP-MD after having pear discover the channels it needs (the auto discover setting didn’t work for this install, so we need to hold pears hand)

$pear channel-discover pear.phpmd.org
$pear channel-discover pear.pdepend.org

$pear install --alldeps phpmd/PHP_PMD

PHP Unit

$pear install --alldeps pear.phpunit.de/PHPUnit

Simple Test

Simple test is an easy to use functional testing library some find easier to use than PHPUnit. To install this library, fist download it like so

$wgetc -c http://sourceforge.net/projects/simpletest/files/simpletest/simpletest_1.1/simpletest_1.1.0.tar.gz

Then move the tar file to the place you want to store the library (I prefer /opt>

$mv simpletest_1.1.0.tar.gz /opt

Uncompress the tar

$cd /opt
$tar xvf simpletest_1.1.0.tar.gz

Both JUnit and xUnit use XML reports to publish test results and SimpleTest does have the ability to output its results in XML, but the format is not compatible with jUnit or xUnit.
To get the output in the format we need, we will need to use the JUnitXMLReporter class. This class is not in the download by default and you will have to download it separately.

Go to the extensions directory

$cd simpletest/extensions

Download the JUnitXMLReporter class

$wget --output-document=junit_xml_reporter.php http://simpletest.svn.sourceforge.net/viewvc/simpletest/simpletest/tags/1.1.0/extensions/junit_xml_reporter.php?revision=2050

Set permissions for the simplest directory

$chown -R jenkins:root /opt/simpletest
$chmod -R 775 /opt/simpletest

Add the library to the include path

$vim /etc/php5/cli/php.ini

Add “:/opt” to the php_include line like so

include_path = ".:/usr/share/php:/opt"

Save the file (escape, :wq, enter)

Installing Jenkins

To install Jenkins we first need to setup OpenJDK 7 as it is recommended over Open JDK 6, which Ubuntu will try to install.

Installing OpenJDK 7

Installing OpenJDK7 JDK

$apt-get install openjdk7-jdk

The install will not setup the $JAVA_HOME environment variable, so let’s take care of that.

Edit /etc/environment

Add the following line

JAVA_HOME="/usr/lib/jvm/java-7-openjdk-amd64/"

Note: If your install is not a 64-bit install your path will be different, please check /usr/lib/jvm to determine your proper path

Not that java is installed and the $JAVA_HOME variable has been defined, please exit (exit sudo or logout) and restart your terminal session (re login or enter sudo again)

Installing Jenkins

To perform the install, we first need to tell apt-get to get the latest versions from jenkins-ci.org

Run the following command to the key for jenkins-ci.org’s repo

wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -

Add the repo to apt-gets’ sources

sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'

Update apt-get

apt-get update

Install Jenkins

$apt-get install jenkins

In The Next Part…

In the next part of the series I will cover installing the plugins we will use in Jenkins, configuring Jenkins to use SSL, and configuring Jenkins to authenticate to Active Directory

For this example, let’s say you have a site you want to post data to so a newly trained employee is added to a mailing list.  In the older days of php, you could do this with http_post_params; however, this function is now deprecated.  Instead, you can use the curl method, or you can use some Zend libraries.

Since this is a post on using Zend, I think you know which one I chose to do.

The following example is a really simple one.  I’m just using a configuration file to hold my options and parameters to use for creating a curl request.  Then I have a script that reads the config  file, creates the request, sends the request, and then checks the response.

The Configuration File

; --configuration file /configFile/myConfig.ini
[production]

;connection info
 curl.mailingList.adapter = Zend_Http_Client_Adapter_Curl
 curl.mailingList.uri = "http://www.example.com/mailinglist/api/add/"

;authentication/post parameters
 curl.mailingList.postParameter.APIUSER = "admin_user"
 curl.mailingList.postParameter.APIPWD = 'some_password'

;configuration options
 curl.mailingList.options.curloptions[CURLOPT_RETURNTRANSFER] = true
 curl.mailingList.options.curloptions[CURLOPT_HEADER] = false
 curl.mailingList.options.curloptions[CURLOPT_ENCODING] = ""
 curl.mailingList.options.curloptions[CURLOPT_AUTOREFERER] = true
 curl.mailingList.options.curloptions[CURLOPT_CONNECTTIMEOUT] = 120
 curl.mailingList.options.curloptions[CURLOPT_TIMEOUT] = 120
 curl.mailingList.options.curloptions[CURLOPT_MAXREDIRS] = 10
 curl.mailingList.options.curloptions[CURLOPT_POST] = 1
 curl.mailingList.options.curloptions[CURLOPT_VERBOSE] = 1
 curl.mailingList.options.timeout = 120
 curl.mailingList.options.maxredirects = 10

Performing the Curl

To actually perform the curl, we will need to load the configuration file into our code and then prepare the curl to send.

<?php
/**
 * Create a post request to add a new trainee to the mailing list
 *
 * PHP Version 5
 *
 * @created 10/12/2011
 * @updated 10/12/2011
 */

/**
 * Include the Zend_Loader class
 */
require_once 'Zend/Loader.php';

//load the needed Zend libraries
Zend_Loader::loadClass('Zend_Config_Ini');  //for reading the configuration file
Zend_Loader::loadClass('Zend_Http_Client_Adapter_Curl');
Zend_Loader::loadClass('Zend_Http_Client');

//read in the configuration file
$config = new Zend_Config_Ini('/configFile/myConfig.ini', 'production');

//setup our post params (subscribees is the name of the form field)
$postParam = array('subscribees' => 'joeExample@corporation.com');

//try to create and send the curl
 try
 {
        //setup the curl connection
        $adapter = new Zend_Http_Client_Adapter_Curl();
        $adapter->setConfig($config->curl->mailingList->options->toArray());

        //instantiate the http client and add set the adapter
        $client = new Zend_Http_Client($config->curl->mailingList->uri);
        $client->setAdapter($adapter);

        //add the post parameters from our config file (the authentication part)
        $client->setParameterPost(
            $config->curl->mailingList->postParameter->toArray()
        );

        //add our post parameters to the request
        $client->setParameterPost($postParam);

        //perform the post, and get the response
        $response = $client->request(Zend_Http_Client::POST);

        //check the response for success
        if (strpos($response, "<h5>Successfully subscribed:</h5>") !== false) {

            echo "<p>Added {$fullName} to trainee email list successfully.</p>";

        } else if (strpos($response, "Already a member") !== false) {

            echo "<p>Already a member of the trainee email list";

        } else {

             echo "<p style='color: red;'>ERROR: Not added to the trainee email list</p>";
        }
}
catch (Zend_Http_Client_Adapter_Curl_Exception $e)
{
    echo "<p style='color: red;'> ERROR: Failed to add the employee to the trainee email list.</p>";
}
catch (Zend_Http_Client_Exception $e)
{
     echo "<p style='color: red;'> ERROR: Failed to add the employee to the trainee email list.</p>";
}
?>

As you can see, the script is not very complex. But it could be expanded to handle logging of errors and other functionality.

Installing PHP 5.4 and PEAR

PHP5.4 is pretty easy to install on windows.  Although it doesn’t have a self installing binary like some of the previous versions, the install is pretty straight forward.

  1. Download PHP 5.4 from php.net here (you want to download the VC9 x86 thread safe zip)
  2. Extract the downloaded zip to c:\php
  3. In your favorite browser go to http://pear.php.net/go-pear.phar to download go-pear.phar
  4. Move go-pear.phar from your download directory to c:\php
  5. Rename the file php.ini-development or php.ini-production to php.ini in the directory c:\php
  6. Add php to the PATH environment variable, by appending ;c:\php to it
    note: If you’re not sure how to set the path variable in windows 7 this is a good guide.
  7. Open a command prompt with administrative permissions (go to, all programs –> accessories and right-click command prompt and select “run as administrator”)
  8. Go to the php directory
    cd c:\php
  9. Beginning the PEAR install
    php go-pear.phar
  10. Select system for the install type (it’s the default, so you can just press enter)
  11. Next you will be shown the paths that PEAR will use, you want to change number 11 to c:\php\pear.ini
    –>To do this type 11, press enter, then type c:\php\pear.ini
  12. After the install has finished run c:\php\PEAR_ENV.reg to finish the installation

Installing PHP Codsniffer

  1. Open a command prompt with administrative rights
  2. Go to the PHP install directory
    cd c:\php
  3. Install PHP Codesniffer
    pear install PHP_CodeSniffer