Run a Spring-Boot application on OpenShift behind HTTPS only

When a Spring-Boot application is deployed on OpenShift, it can be reached both with a HTTP URL and a HTTPS URL. This is because OpenShift runs a proxy in front of the application which in case of HTTP just routes the request to the application. If a request comes in via HTTPS, the proxy does all the encryption handling with the client and then passes the decrypted request on to the application – on the HTTP channel – and encrypts the response before sending it to the client.

The advantage for an application developer is that you do not need to bother about the details of encryption, you just write your application and leave the rest to OpenShift.

This post shows how to setup and modify your application so that it only can be reached by HTTPS and so enforces the use of a secure conversation channel. To know how to set up a Spring-Boot application on OpenShift you might read this post.

Add the security to your project and set it to ssl only

When you setup your project with the Spring Initializr include the core/security. If you have an existing project, add the following dependency to your pom.xml:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

This enables Spring security and secures your application with the user named user and a password that is displayed on the console during startup.  To force the use of HTTPS you normally only need to add the following entry to the application.properties file:

security.require-ssl=true

Now you would have an application that will automatically redirect to HTTPS (the default port in Spring Boot for HTTPS is 8443 when running on unsecure port 8080) when called on port 8080. Besides not having a certificate and the configuration to run on HTTPS, we don’t need the Basic Authentication for our purpose of running HTTPS only. So we can disable it by adding the following entry to the application.properties file:

security.basic.enabled=false

When restarting the application the need for Basic Authentication is gone, but this also disables the require-ssl setting, so we can access our application as before on the normal HTTP port.

The solution to this problem is to provide a custom configuration class:

/**
 * Copyright (c) 2015 sothawo
 *
 * http://www.sothawo.com
 */
package com.sothawo.sayservice;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;

/**
 * Security configuration.
 *
 * @author P.J. Meisch (pj.meisch@sothawo.com).
 */
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requiresChannel().anyRequest().requiresSecure();
        http.csrf().disable();
    }
}

After adding this class to our project (as I in this case don’t need CSRF for a pure REST service behind HTTPS, I disable it here), all requests to the HTTP port are redirected to HTTPS and the Basic Authentication is still disabled (you can remove the server-require-ssl entry from the config file). So far so good.

Fix eternal redirection on OpenShift

After deploying the application in this stage to OpenShift you will notice that both requests, HTTP on port 80 and HTTPS on port 443 result in an eternal redirection to the HTTPS URL. This happens because when the application is accessed by HTTPS, the OpenShift proxy does the HTTPS handling and then contacts the application on the normal internal HTTP port. The application checks the channel and sends a redirect request to the secure channel to the client which in turn request the application from the HTTPS proxy, which will strip the HTTPS part and so on.

To fix this you need to make your application honour two special HTTP headers. Add the following lines to the application.properties file:

server.tomcat.remote_ip_header=x-forwarded-for
server.tomcat.protocol_header=x-forwarded-proto

The x-forwarded- headers are set by the proxy and by putting these settings in your configuration, the embedded tomcat checks these headers when deciding whether a redirect is needed and so even when the application is called from the proxy on HTTP, a redirect will only be requested when the proxy itself was not accessed by a secure channel.

That’s all that’s needed to run your application HTTPS only.

OpenShift configure which branch to deploy

Note to self: this is a copy from the OpenShift documentation:

Configuring Which Branch to Deploy
By default OpenShift deploys the master branch when you do a git push (when automatic deployments are enabled). You can change this behavior using the rhc app-configure –deployment-branch command, where is the new branch to deploy.

$ rhc app-configure  --deployment-branch  # Master is the Default
 $ rhc app-configure myapp --deployment-branch new_branch
 Configuring application 'myapp' ... done
myapp @ http://myapp-osexample.rhcloud.com/ (uuid: 54da3b4be0b8cd64a2000010)
 ----------------------------------------------------------------------------
 Deployment: auto (on git push)
 Keep Deployments: 5
 Deployment Type: git
 Deployment Branch: new_branch

Your application 'myapp' is now configured as listed above.

Now when you make commits to your new branch and do a git push to OpenShift, those changes will be deployed.

Spring-Boot, Spring profiles and configuration files

Note to self: When using Spring-Boot, use application.conf as a base configuration for the needed values. Configuration values for the specific profile go into the application-<profile>.config file.

Profiles are activated by using either the -Dspring.profiles.active=<profile> VM flag or --spring.profiles.active=<profile> commandline arg.

Deploying a Spring-Boot application running with Java8 on OpenShift2

This post describes how to create and deploy a Spring-Boot application to RedHat OpenShift (version 2) when the application is using Java 8.

Edit 2015-10-04: In this newer post I show how to not install a custom JDK. So you should first read this post and then the linked one for additional information.

Normally deploying a Spring-Boot application on OpenShift is not too much pain and is explained in the Spring-Boot documentation. But some extra work is needed when the application is built and run with Java 8, as at the time of writing, the DIY cartridge of OpenShift only supports Java 7. And, to make things worse, the mvn command which is available in the DIY cartridge is rewritten by RedHat, so it will pick up Java 7 no matter what you set your JAVA_HOME to.

This post will show how to overcome these deficiencies by walking through the necessary steps to create a Spring-Boot based REST service which is deployed on OpenShift. To follow along you need

  • Java 8 installed
  • an OpenShift account
  • setup the rhc command line tool as described on OpenShift documentation
  • know how to create and set up an Spring Boot project (I use a maven project)

My sample is created on Mac OSX by using the terminal and IntelliJ. I will create a REST service named SayService which will just return it’s string input prepended by “you said: “. Not very interesting, but enough for this example.

Create the OpenShift application

As a first step I create the application on OpenShift. To do that, I change into the local directory where I want the app to be created and issue the following rhc command, assuming you are logged in to OpenShift with rhc:

rhc app-create sayservice diy

This creates the OpenShift application and clones it’s Git repository to your local sayservice directory. The structure is shown below:

sayservice
├── .git
├── .openshift
│   ├── README.md
│   ├── action_hooks
│   │   ├── README.md
│   │   ├── start
│   │   └── stop
│   ├── cron
│   │   ├── README.cron
│   │   ├── daily
│   │   │   └── .gitignore
│   │   ├── hourly
│   │   │   └── .gitignore
│   │   ├── minutely
│   │   │   └── .gitignore
│   │   ├── monthly
│   │   │   └── .gitignore
│   │   └── weekly
│   │       ├── README
│   │       ├── chrono.dat
│   │       ├── chronograph
│   │       ├── jobs.allow
│   │       └── jobs.deny
│   └── markers
│       └── README.md
├── README.md
├── diy
│   ├── index.html
│   └── testrubyserver.rb
└── misc
    └── .gitkeep

The diy subdirectory contains the sample, we ignore that. What we need to adjust later are scripts in the .openshift/action_hooks directory. And of course we need to add our source code for the service.

Create the Spring-Boot REST service

With the help of the Spring Boot Initializr (which I use from within IntelliJ, but the jar created on the Website is quite the same) I create a project that just has the Web/Web component added and where the Java version is set to 1.8. The important thing here is that the project is created in the sayservice directory so that the project files are added to the existing directory. After adding my standard .gitignore file, the directory contains the following data (not showing the contents of the .openshift directory again and skipping IntelliJ files):

sayservice
├── .git
├── .gitignore
├── .openshift
├── README.md
├── diy
├── misc
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── sothawo
    │   │           └── sayservice
    │   │               └── SayserviceApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── sothawo
                    └── sayservice
                        └── SayserviceApplicationTests.java

The following listing shows the pom.xml, notice the explicit setting of the java version to 1.8:

<?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>com.sothawo</groupId>
  <artifactId>sayservice</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>sayservice</name>
  <description>Demo project for Spring Boot REST service on OpenShift</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

Add the Service implementation

At the moment we have an application that has not yet a service defined, so we add the following Sayservice class:

/**
 * Copyright (c) 2015 sothawo
 *
 * http://www.sothawo.com
 */
package com.sothawo.sayservice;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * Sample Service.
 *
 * @author P.J. Meisch (pj.meisch@sothawo.com).
 */
@RestController
@RequestMapping("/")
public class Sayservice {
    @RequestMapping(value = "/say/{in}", method = RequestMethod.GET)
    public String echo(@PathVariable(value = "in") final String in) {
        return "you said: " + in;
    }
}

After creating and running the application with

mvn package && java -jar target/*.jar

you can access and test it:

curl http://localhost:8080/say/hello
you said: hello

Create an OpenShift build script to install Java8 and build the application

The following script named build must be put in the .openshift/action_hooks directory (it must be executable):

#!/bin/bash

# define some variables for JDK 8
JDK_TGZ=jdk-8u60-linux-i586.tar.gz
JDK_URL=http://download.oracle.com/otn-pub/java/jdk/8u60-b27/$JDK_TGZ
JDK_DIR=jdk1.8.0_60
JDK_LINK=jdk1.8

# download JDK1.8 to the data directory if it does not yet exist, extract it and create a symlink
cd ${OPENSHIFT_DATA_DIR}

if [[ ! -d $JDK_DIR ]]
then
  wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" $JDK_URL
  tar -zxf $JDK_TGZ
  rm -fr $JDK_TGZ
  rm $JDK_LINK
  ln -s $JDK_DIR $JDK_LINK
fi

# export environment vriables
export JAVA_HOME="$OPENSHIFT_DATA_DIR/$JDK_LINK"
export PATH=$JAVA_HOME/bin:$PATH

# call our own mvn script with the right settings
cd $OPENSHIFT_REPO_DIR
./.openshift/mvn package -s .openshift/settings.xml -DskipTests=true

The script downloads the Oracle JDK if it is not yet available and extracts it to the OPENSHIFT_DATA_DIR directory.

The next thing to adjust is the mvn script. The one that’s available in the DIY cartridge resets JAVA_HOME, so I put the following mvn script in the .openshift directory:

#!/bin/sh
prog=$(basename $0)
export JAVACMD=$JAVA_HOME/bin/java
export M2_HOME=/usr/share/java/apache-maven-3.0.4
exec $M2_HOME/bin/$prog "$@"

As an alternative you might add a download of maven to the build script.

The last needed file for the build is the settings.xml, which I also put into the .openshift directory (Edit 2015-10-04: fixed variable with {} and added /.m2/repository):

<settings>
 <localRepository>${OPENSHIFT_DATA_DIR}/.m2/repository</localRepository>
</settings>

Set up start and stop scripts

Replace the files start and stop in the .openshift/action_hooks directory with the following ones (they must be executable):

start

#!/bin/bash
# The logic to start up your application should be put in this
# script. The application will work only if it binds to
# $OPENSHIFT_DIY_IP:8080

JDK_LINK=jdk1.8

export JAVA_HOME="$OPENSHIFT_DATA_DIR/$JDK_LINK"
export PATH=$JAVA_HOME/bin:$PATH

cd $OPENSHIFT_REPO_DIR
nohup java -jar target/*.jar --server.port=${OPENSHIFT_DIY_PORT} --server.address=${OPENSHIFT_DIY_IP} &

stop

#!/bin/bash
source $OPENSHIFT_CARTRIDGE_SDK_BASH

# The logic to stop your application should be put in this script.
PID=$(ps -ef | grep java.*\.jar | grep -v grep | awk '{ print $2 }')
if [ -z "$PID" ]
then
    client_result "Application is already stopped"
else
    kill $PID
fi

Deploy and run the application

After committing your files to git a final

git push

will upload all your changes to OpenShift, build and start the application. Check it out by calling

curl http://sayservice-yourdomain.rhcloud.com/say/it-works

and getting the answer

you said: it-works

So much for my first post concerning OpenShift.