Archive for October, 2014


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