Categories
Development Java

OneLogin AccessFilter

Wir wollen unsere Anwendung durch einen Filter schützen, der nur Anfragen von eingeloggten Benutzern mit der richtigen Rolle hindurch lässt.

Für diesen PoC bauen wir einen Reverse Proxy für den Login und einen Anwendungsserver für den Filter. Der Anwendungsserver bekommt einen Zugang über den RP und einen Zugang ohne RP, um so zu zeigen, dass nur über den RP auf die Anwendung zugegriffen werden kann.

In produktiven Umgebungen darf es den Zugang ohne RP so ungeschützt nicht geben, da es sehr einfach ist, zB über ein Browser-Plugin, den RP mittels selbstgesetzter Header zu faken.

Reverse Proxy

Der Reverse Proxy bekommt grundsätzlich die gleiche Konfiguration wie in den Beispielen zuvor.

Hier müssen wir aber noch den Scope params hinzufügen, um so die OneLogin-Rollen des Benutzers zu übertragen:

## OIDCScope params
## to put params including roles into header
OIDCScope "openid email profile groups params"

Im Header sieht das dann beispielsweise so aus:

oidc_claim_params
	{"post_logout_redirect_uri": "http://localhost", "roles": "user;admin"}

ACHTUNG

Das die Rollen als Parameter übertragen werden, funktioniert nur, wenn man explizit angibt, dass diese als Parameter übergeben werden sollen!

Dazu muss die Application in OneLogin entsprechend konfiguriert werden:

Applications -> Applications -> select Application -> Parameters -> “+”-Button:

Im nächsten Fenster des Dialoges die Value (“User Roles”) setzen:

Access Filter

Der Access Filter prüft, ob eine UserID vorhanden ist. Das ist in unserem Beispiel die Email, es könnte aber auch der Preferred Username genommen werden.

Des weiteren prüft der Access Filter, ob der Benutzer die erforderliche Rolle user hat.

Sind beide Bedingungen erfüllt, kann auf die Anwendung zugegriffen werden, ansonsten wird lediglich “Access denied” angezeigt.

package deringo.oneloginjavaappsample;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;


@WebFilter("/*")
public class AccessFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("AccessFilter init");
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println("AccessFilter doFilter");
		
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		
		String oidc_claim_email = httpRequest.getHeader("oidc_claim_email");
		String oidc_claim_preferred_username = httpRequest.getHeader("oidc_claim_preferred_username");
		String userid = oidc_claim_email;
		
		String oidc_claim_params = httpRequest.getHeader("oidc_claim_params");
		List<String> roles = getRoles(oidc_claim_params);
		boolean hasRequiredRole = roles.contains("user");
		
		if (!StringUtils.isBlank(userid) && hasRequiredRole) {
			// Grant access
			chain.doFilter(request, response);
		} else {
			response.getWriter().println("Access denied");
			response.getWriter().close();
		}
	}

	@Override
	public void destroy() {
		System.out.println("AccessFilter destroy");
	}

	private static List<String> getRoles(String oidc_claim_params) {
		List<String> rolesList = new ArrayList<>();
		try {
			JSONObject o = new JSONObject(oidc_claim_params);
			String rolesS = o.get("roles").toString();
			String[] roles = StringUtils.split(rolesS, ";");
			rolesList = Arrays.asList(roles);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return rolesList;
	}
}

Docker

Über Docker Compose werden beide Server gestartet.

Über Port 80 ist der Reverse Proxy erreichbar, über Port 8080 direkt der Anwendungsserver.

version: '3.8'
services:

   reverseproxy:
      build: ./reverseproxy
      hostname: reverseproxy
      volumes:
        - ./reverseproxy/public_html:/usr/local/apache2/htdocs
        - ${PWD}/reverseproxy/conf/reverseproxy_httpd.conf:/usr/local/apache2/conf/httpd.conf
        - ${PWD}/reverseproxy/conf/reverseproxy.conf:/usr/local/apache2/conf/reverseproxy.conf
      ports:
        - 80:80
      
   sampleapp:
      build: ./oneloginjavaappsample
      hostname: sampleapp
      ports:
        - 8080:8080

Im Docker File des Anwendungsservers kopieren wir erst nur das Maven POM, lassen dann Maven bauen, um so die Abhängigkeiten herunterzuladen. Erst danach kopieren wir das gesamte Projekt in den Container und lassen die Anwendung bauen. So wird nach Code Änderungen, ohne Anpassungen in Maven, der Bau des Images beschleunigt, da nicht jedes Mal die Bibliotheken heruntergeladen werden müssen.

   FROM tomcat:8.5-jdk8-openjdk-slim
    RUN apt update && apt install -y \
        maven
   COPY pom.xml /app/pom.xml
WORKDIR /app
    RUN mvn package
   COPY . /app
    RUN mvn package
WORKDIR $CATALINA_HOME
    RUN mv /app/target/ROOT.war webapps
 EXPOSE 8080
    CMD ["catalina.sh", "run"]

Test

Zugriff auf den Anwendungsserver über den Reverse Proxy nach Anmeldung:

http://localhost/private/test.html

Direkter Zugriff auf den Anwendungsserver ohne Anmeldung:

http://localhost:8080/index.html

GitHub

Die Dateien zu diesem Post sind im OneLogin-GitHub-Projekt unter version6 zu finden.

Categories
Development Java

Tomcat Start beschleunigen

Das Starten des Tomcat-Servers hat für ein Projekt sehr lange gedauert. Im Eclipse kann man die Zeit bis zum Timeout hoch setzen, den Nerven des Entwicklers hilft das aber nur bedingt.

Eine Ursache für die lange Startzeit liegt darin, dass der Tomcat-Server beim Start alle jar-Files nach Taglibs durchsucht. Das Projekt hat sehr viele Libraries.

Eine Abhilfe schafft hier die Konfiguration, dass Tomcat keine jar-Files scannen soll, außer denen, in denen eine Taglib enthalten ist.

Wie man Jars mit Taglibs findet

Tomcat kann anzeigen lassen, welche Jars, die beim Start gescannt werden, Taglibs enthalten. Dazu muss das entsprechende Log-Level gesetzt werden.

In meinem Fall musste ich lediglich die logging.properties aus dem Original-Tomcat Verzeichnis in das Verzeichnis des Eclipse Tomcats kopieren:

Am Ende der logging.properties das Log-Level für den TLDScanner setzen:

[...]

org.apache.jasper.compiler.TldLocationsCache.level = FINE
org.apache.jasper.servlet.TldScanner.level = FINE

In den VM Arguments des Tomcats muss der Pfad zur logging.properties angegeben werden:

Beim Start wird jetzt angezeigt, in welchen JARs TLDs zu gefunden wurden.

Wie nur noch ausgewählte JARs gescannt werden

In aktuellen Projekt wurden folgende JARs mit TLDs gefunden:

  • standard-1.1.2.jar
  • jstl-1.2.jar
  • jsf-impl-2.2.20.jar
  • tomahawk20-1.1.14.jar

Die Konfiguration des JARScanFilters für den Tomcat Server erfolgt in der catalina.properties Datei.

Bei den jarsToSkip lasse ich alle (*.jar) skippen.

Bei den jarsToScan füge ich obige JARs hinzu:

Alleine durch diese Konfigurationsänderung konnte die Startzeit von 25 Sekunden auf 10 Sekunden reduziert werden.

JarScanner Konfiguration im Projekt

Der obige Weg beschreibt die Konfigurationsänderung im Server. Das hat den Nachteil, dass jede Serverinstanz diese Konfiguration gesetzt bekommen muss. Eine Konfiguration im Projekt selbst hat den Vorteil, dass zB alle Mitentwickler direkt mit profitieren können und nicht erst die Konfiguration selbst setzen müssen.

Die Konfiguration im Projekt erfolgt über context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
  <JarScanner>
    <JarScanFilter tldScan="standard-1.1.2.jar,jstl-1.2.jar,jsf-impl-2.2.20.jar,tomahawk20-1.1.14.jar" 
                   defaultTldScan="false" 
                   defaultPluggabilityScan="false"/>
  </JarScanner>
</Context>
Categories
Java

ThreadLocal

Bei der Migration einer größeren Anwendung (> 120.000 LOC) von einem SAP NetWeaver 7.3 mit Java 1.6 auf einen Tomcat 8.5 mit Java 1.8 hatten wir ein “interessantes” Problem:

Nach der erfolgreichen Umstellung einer Schnittstelle (SAP PI/PO) auf unseren neuen Tomcat Server wurden wir am nächsten Tag damit konfrontiert, dass die PI keinen Zugang mehr zu unserem Server hätte. Die Verbindung würde zwar aufgebaut, aber die Annahme der Daten dann mit HTTP Code 401 abgelehnt.

Das Kuriose dabei: Im Prinzip findet gar keine Authentifizierung in der Anwendung statt, denn diese Schnittstelle ist lediglich für die PI freigegeben, über einen IP Filter.

Ein Neutstart unseres Tomcat-Servers konnte das Problem kurzfristig beheben, bis es dann wieder auftrat.

Der Code des empfangenden Servlets sieht stark vereinfacht so aus:

public class IdocImportServlet extends HttpServlet implements Servlet {
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if (UserInSession.get() == null) {
            UserInSession.put("DiesIstDiePI");
        } else {
            response.setStatus(401);
            response.getWriter().println("Illegal attempt to submit data");
            return;
        }
   		doStuff(request, response);
    }
}

Da diese Kommunikation an den üblichen Authentifizierungsmechanismen vorbei läuft, muss der UserInSession auch immer NULL sein. Aber schauen wir uns diese Klasse mal genauer an, denn der Name “InSession” ist irreführend:

public final class UserInSession {
	private static ThreadLocal sessionUser = new ThreadLocal() {
        protected synchronized Object initialValue() {
            return null;
        }
    };
	public static void put(String userId) {
        sessionUser.set(user);
    }
	public static String get() {
        return ((String) sessionUser.get());
    }
}

Unabhängig von der Session wird der User zu Begin der Verarbeitung im Servlet gesetzt. Es ist also eher ein “UserInRequest”, der da gesetzt wird.
Der User wird in einer ThreadLocal gespeichert und kann so später an anderer Stelle wieder aus dieser ThreadLocale ausgelesen werden.
Das alles sollte auch kein Problem machen und so funktionieren. Und beim Debugging hat es dann auch erstmal funktioniert.

Um mir das Debugging-Leben etwas einfacher zu gestalten, habe ich die Payload einer Übertragung aus den Logfiles herausgesucht und in eine Datei (data.xml) gespeichert und dann per Terminal Befehl an die Schnittstelle zu schicken:

curl -X POST -d @data.xml http://localhost:8080/myApp/PISchnittstelle

Beim ersten Mal hat alles funktioniert und auch dann noch ein paar Mal, bis es dann nicht mehr ging.
Das Problem ließ sich beim Debuggen erkennen: der UserInSession war auf einmal schon mit dem User “DiesIstDiePI” gesetzt und daher wurde der Zugang verweigert.

Wie sich bei der Analyse herausstelle, läuft ein Request in einem Thread. So weit so gut.
Der Tomcat stellt eine bestimmte Anzahl an Threads bereit. Und nach <Anzahl der Threads +1> Requests wird der erste Thread wiederverwertet. Dummerweise ist dabei aber noch die ThreadLocale aus dem ersten Request gefüllt.

In der alten SAP NetWeaver Umgebung scheint immer ein “frischer” Thread zu kommen, so dass der Code in der alten Umgebung funktioniert hat. Wodurch genau dieses unterschiedliche Verhalten ausgelöst wird, konnten wir leider nicht in vertretbarer Zeit herausfinden.

Lösung

Die Lösung (oder vielleicht doch eher ein Workaround) besteht darin, bei jedem Request die ThreadLocal zu löschen. Gerne hätten wir nach einer Lösung gesucht, die ThreadLocal möglicherweise ganz los zu werden, aber der Aufwand wäre in Summe zu groß gewesen (was ich hier auf dieser Seite darstelle ist eine sehr stark vereinfachte und verkürzte Version des Codes und des Problems).

Um den UserInSession vor jedem Aufruf des Servlets löschen zu können, haben wir einen zusätzlichen Filter eingebaut:

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;


public class ThreadLocalFilter implements Filter {

    public void destroy() {
        // nothing to do
    }

    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
            throws IOException, ServletException {
        // Wipe all ThreadLocal
        UserInSession.put(null);
        arg2.doFilter(arg0, arg1);
    }

    public void init(FilterConfig arg0) throws ServletException {
        // nothing to do
    }
}

Und diesen Filter entsprechend in der web.xml eingebaut:

    <filter>
        <filter-name>ThreadLocalFilter</filter-name>
        <filter-class>deringo.filter.ThreadLocalFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>ThreadLocalFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Categories
Java

Remote Debugging

I need to directly debug on the application Tomcat server, not only on my local Tomcat instance.

Compile Java code

For debugging we need to keep the line numbers while compiling.
To build the war file we use an ANT script and we have to add the debug and debuglevel attributes in the javac tag:

<javac srcdir="${src}" destdir="${build}" 
       includeantruntime="false" 
       encoding="UTF-8" source="1.8" 
       fork="true" 
       debug="on" debuglevel="lines,vars,source" 
       verbose="true">
  [...]
</javac>

Configure Tomcat server

To enable remote debugging on the Tomcat server add some arguments to CATALINA_OPTS in setenv.sh file:

ATALINA_HOME=/app/myApp/tomcat
CATALINA_BASE=/app/myApp/tomcat
CATALINA_PID=/app/myApp/tomcat/tomcat.pid
JAVA_HOME=/app/java/jdk8u265-b01-jre
CATALINA_OPTS="$CATALINA_OPTS -Djava.library.path=/app/library -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/library
export LD_LIBRARY_PATH
JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=/app/certs/corporate_truststore.jks -Djavax.net.ssl.trustStorePassword=secret -Xms256M -Xmx1024M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/myApp/dump"

Configure Eclipse

Create a new debug configuration:


Categories
Java

Deploy to Tomcat with Maven

In my last post I decribed the way to build and deploy a war file to Tomcat application server with Ant.

In this post I show how to deploy the build war file to Tomcat application server with Maven.

Create inside the “ant” folder this Maven file:

<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>deringo</groupId>
    <artifactId>myApp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>myApp</name>
    <description>myApp Maven deployment</description>

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

    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>${tomcat.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <warName>myApp</warName>
                    <wtpContextName>myApp</wtpContextName>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <url>http://myAppServer:7011/manager/text</url>
                    <username>tomcat</username>
                    <password>tomcat</password>
                    <warFile>dist/myApp.war</warFile>
                    <path>/myApp</path>
                    <update>true</update>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Execute with goal: tomcat7:deploy

Use Properties file

In my ANT file I have used a properties file for tomcat username/password etc. In my Maven script I want also to use this properties file.

Unfortunatly Maven can not handle property files out of the box. But there is a Plugin we can use:

        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>properties-maven-plugin</artifactId>
            <version>1.0-alpha-1</version>
            <executions>
                <execution>
                    <phase>initialize</phase>
                    <goals>
                        <goal>read-project-properties</goal>
                    </goals>
                    <configuration>
                        <files>
                            <file>tomcat.properties</file>
                        </files>
                    </configuration>
                </execution>
            </executions>
        </plugin>

This is the same, as writing this directly into pom.xml:

<properties>
    <tomcat.username>tomcat</tomcat.username>
    <tomcat.password>tomcat</tomcat.password>
</properties>

The complete pom.xml with properties:

<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>deringo</groupId>
  <artifactId>myApp</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>myApp</name>
  <description>myApp Maven deployment</description>

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

  <dependencies>
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-catalina</artifactId>
      <version>${tomcat.version}</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>properties-maven-plugin</artifactId>
        <version>1.0-alpha-1</version>
        <executions>
          <execution>
            <phase>initialize</phase>
            <goals>
              <goal>read-project-properties</goal>
            </goals>
            <configuration>
              <files>
                <file>tomcat.properties</file>
              </files>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.0</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.2.2</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
          <warName>myApp</warName>
          <wtpContextName>myApp</wtpContextName>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <url>${tomcat.manager.url}</url>
          <username>${tomcat.username}</username>
          <password>${tomcat.password}</password>
          <warFile>dist/myApp.war</warFile>
          <path>${webapp.name}</path>
          <update>true</update>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

Update 2021-03-24

I tried the configuration in a project but got this error:

Plugin execution not covered by lifecycle configuration: org.codehaus.mojo:properties-maven-plugin:1.0-alpha-1:read-project-properties (execution: default, phase: initialize)

I solved the error adding a pluginManagement tag:

<build>
    <pluginManagement>
        <plugins>
            <plugin> ... </plugin>
            <plugin> ... </plugin>
                  ....
        </plugins>
    </pluginManagement>
</build>

But this solution seems more like a workaround when reading the discussion on stackoverflow.
…need to search further…

Use in Jenkins

I have not tried it, but it should be possible to set an environment variable in Jenkins build step to execute with a configurable filename.

For example TOMCAT_PROPERTIES=jenkins-home/secret/tomcat-dev.properties.
And we can use it in Maven script this way:

            <configuration>
              <files>
                <file>${env.TOMCAT_PROPERTIES}</file>
              </files>
            </configuration>
Categories
Java

Deploy to Tomcat

Deploy myApp.war file to Tomcat application server, using Tomcat manager app and an ant script.

Make a folder “ant” in your project and this it the folder for all other actions below.

Make a folder “dist” and generate your myApp.war file into this folder with the “build.xml” script.

Make a folder “tomcat-libs” and copy following files from your tomcat installation:

  • catalina-ant.jar
  • catalina.jar
  • servlet-api.jar
  • jsp-api.jar

Create file “tomcat.properties” with properties for tomcat server and tomcat manager app.

Create “deploy.xml” file for deployment.

Execute deploy.xml script with ant, it must show the info sections with properties of your tomcat.properties file.

Execute deploy-webapp target from deploy.xml script to deploy your myApp.war file to tomcat server through the tomcat manager app.

If you are using for example Jenkins, you can set the “secprops.location” from outside the script, so you can use the same script for different tomcat installations.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project>
<project name="myApp" default="info" basedir=".">

  <description>
    myApp ant deploy file.
  </description>

  <property name="secprops.location" value="tomcat.properties" />
  <property file="${secprops.location}" />

  <property name="war.file" value="dist/myApp.war" />

  <target name="info">
    <echo message="-------------------------------------------------------------------" />
    <echo message="Commands:" />
    <echo message="  start-webapp" />
    <echo message="  stop-webapp" />
    <echo message="  undeploy-webapp" />
    <echo message="  deploy-webapp" />
    <echo message="  sessions-webapp" />
    <echo message="-------------------------------------------------------------------" />
    <echo message="Properties: "/>
    <echo message="tomcat.manager.url = ${tomcat.manager.url}"/>
    <echo message="tomcat.username    = ${tomcat.username}"/>
    <echo message="tomcat.password    = ${tomcat.password}"/>
    <echo message="webapp.name        = ${webapp.name}"/>
    <echo message="war.file           = ${war.file}"/>
    <echo message="-------------------------------------------------------------------" />
  </target>

  <path id="catalina-ant-classpath">
    <fileset dir="tomcat-libs">
      <include name="catalina-ant.jar" />
      <include name="catalina.jar" />
    </fileset>
  </path>

  <taskdef name="catalina-start" classname="org.apache.catalina.ant.StartTask" classpathref="catalina-ant-classpath" />
  <taskdef name="catalina-stop" classname="org.apache.catalina.ant.StopTask" classpathref="catalina-ant-classpath" />
  <taskdef name="catalina-deploy" classname="org.apache.catalina.ant.DeployTask" classpathref="catalina-ant-classpath" />
  <taskdef name="catalina-undeploy" classname="org.apache.catalina.ant.UndeployTask" classpathref="catalina-ant-classpath" />
  <taskdef name="catalina-sessions" classname="org.apache.catalina.ant.SessionsTask" classpathref="catalina-ant-classpath" />
  
  <target name="sessions-webapp">
    <catalina-sessions url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" failonerror="false" />
  </target>
  
  
  <target name="stop-webapp">
    <catalina-stop url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" failonerror="false" />
  </target>

  <target name="start-webapp">
    <catalina-start url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" />
  </target>

  <target name="undeploy-webapp">
    <catalina-undeploy url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" failonerror="false" />
  </target>

  <target name="deploy-webapp">
    <echo message="START to deploy file ${war.file} to Tomcat Server: ${tomcat.manager.url}" />
    <catalina-deploy url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" war="file:${war.file}" update="true"/>
    <echo message="END of deployment" />
  </target>

</project>
tomcat.manager.url=http://myAppServer:7011/manager/text
tomcat.username=tomcat
tomcat.password=tomcat
webapp.name=myApp
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project>
<project name="myApp" default="clean" basedir=".">
  <description>
    myApp ant build file.
  </description>
  <!-- Set global properties for this build -->
  <property name="src" location="../source"/>
  <property name="webcontent" location="../WebContent"/>
  <property name="build" location="build"/>

  <tstamp prefix="build-info">
    <format property="current-date" pattern="d-MMMM-yyyy" locale="en" />
    <format property="current-time" pattern="hh:mm:ss a z" locale="en" />
  </tstamp>

  <target name="init">
    <!-- Create the build directory structure used by compile -->
    <mkdir dir="${build}"/>
    <!-- Create the distribution directory -->
    <mkdir dir="${dist}/lib"/>
  </target>

  <target name="compile" depends="init" description="compile the source">
    <!-- Compile the Java code from ${src} into ${build} -->
    <javac srcdir="${src}" destdir="${build}" includeantruntime="false" encoding="UTF-8">
      <classpath>
        <!-- Taken from Apache Tomcat 8.5 lib folder -->
        <pathelement path="tomcat-libs/servlet-api.jar"/>
        <pathelement path="tomcat-libs/jsp-api.jar"/>
      </classpath>
    </javac>
    <!-- Copy all non-java ressoures from source folder -->
    <copy todir="${build}">
      <fileset dir="${src}" excludes="**/*.java" />
    </copy>

  </target>

  <target name="war" depends="compile">
    <war destfile="dist/myApp.war" webxml="${webcontent}/WEB-INF/web.xml">
      <fileset dir="${webcontent}"/>
      <classes dir="${build}"/>
      <manifest>
        <attribute name="Manifest-Version" value="1.0"/>
        <attribute name="Built-On" value="${build-info.current-date}"/>
        <attribute name="Built-At" value="${build-info.current-time}"/>
      </manifest>
    </war>
  </target>

  <target name="clean" description="clean up">
    <!-- Delete the ${build} directory trees -->
    <delete dir="${build}"/>
  </target>
</project>
Categories
Java Linux

Setup Tomcat manager app

In my last post I set up a Tomcat application server in general, now I enable Tomcat manager app for deployment.

# Tomcat Users
mv /app/myApp/tomcat/conf/tomcat-users.xml /app/myApp/tomcat/conf/tomcat-users.xml_original
vim /app/myApp/tomcat/conf/tomcat-users.xml

vim/app/myApp/tomcat/conf/server.xml

# By default the Manager is only accessible from a browser running on the same machine as Tomcat. If you wish to modify this restriction, you'll need to edit the Manager's context.xml file.
vim /app/myApp/tomcat/webapps/manager/META-INF/context.xml
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
 
  <role rolename="manager-gui"/>
  <role rolename="manager-script"/>
  <user username="tomcat" password="tomcat" roles="manager-gui,manager-script"/>
 
</tomcat-users>
<Server port="7010" shutdown="SHUTDOWN">
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
 
  <Service name="Catalina">
    <Connector port="7011" />
   
    <!-- Define an AJP 1.3 Connector on port 7012 -->
    <Connector port="7012" protocol="AJP/1.3" secretRequired="false" />
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="myApp-dev">
      <Host name="localhost" appBase="webapps" />
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
    </Engine>
  </Service>
</Server>
<Context antiResourceLocking="false" privileged="true" >
    <!-- ## uncomment this Block ##
    <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
    -->
</Context>

Test Tomcat manager app

Open http://myAppServer:7011/manager and login with username: tomcat and password: tomcat.

Categories
Java Linux

Setup Tomcat application server

Setup files

su myUser
 
# Java
cd /app/java
tar -xzf /app/files/OpenJDK8U-jre_x64_linux_hotspot_8u265b01.tar.gz
tar -xzf /app/files/OpenJDK11U-jre_x64_linux_hotspot_11.0.8_10.tar.gz
tar -xzf /app/files/OpenJDK15U-jre_x64_linux_hotspot_15_36.tar.gz
 
# myApp Tomcat
cd /app/myApp
tar -xzf /app/files/apache-tomcat-8.5.59.tar.gz
tar -xzf /app/files/apache-tomcat-9.0.39.tar.gz
 
# Certificate
cp /app/files/corporate_truststore.jks /app/certs/
 
# SAP JCO
cp /app/files/_sapjco3-64/3.0.19/linuxx86/libsapjco3.so /app/library/
cp /app/files/_sapjco3-64/3.0.19/linuxx86/sapjco3.jar /app/library/

Setup Tomcat

su myUser
 
# Symlink to actual Tomcat version
ln -s /app/ccp/apache-tomcat-8.5.59 /app/myApp/tomcat
 
# remove sample application
# but keep the Tomcat Manager app for deployment
rm -rf /app/myApp/tomcat/webapps/docs
rm -rf /app/myApp/tomcat/webapps/examples
rm -rf /app/myApp/tomcat/webapps/ROOT
 
# configure Tomcat
vim /app/myApp/tomcat/bin/setenv.sh
mv /app/myApp/tomcat/conf/server.xml /app/myApp/tomcat/conf/server.xml_original
vim /app/myApp/tomcat/conf/server.xml
 
# expand Classpath
vim /app/myApp/tomcat/conf/catalina.properties
# tomcat/bin/setenv.sh
CATALINA_HOME=/app/myApp/tomcat
CATALINA_BASE=/app/myApp/tomcat
CATALINA_PID=/app/myApp/tomcat/tomcat.pid
JAVA_HOME=/app/java/jdk8u265-b01-jre
CATALINA_OPTS="$CATALINA_OPTS -Djava.library.path=/app/library"
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/library
export LD_LIBRARY_PATH
JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=/app/certs/corporate_truststore.jks -Djavax.net.ssl.trustStorePassword=notchangeit -Xms512M -Xmx2048M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/myApp/dump"
# tomcat/conf/catalina.properties
common.loader= [...] ,"/app/library/sapjco3.jar"
# server.xml
<Server port="7010" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Connector port="7011" />
   
    <!-- Define an AJP 1.3 Connector on port 7012 -->
    <Connector port="7012" protocol="AJP/1.3" secretRequired="false" />
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="myApp-dev">
      <Host name="localhost" appBase="webapps" />
    </Engine>
  </Service>
</Server>

Tomcat as a Service

# As root
vim /etc/systemd/system/tomcat.service
# enable script:
systemctl enable tomcat.service
# tomcat.service

# Systemd unit file for myApp tomcat
#
# To create clones of this service:
   
# Systemd unit file for tomcat
 [Unit]
 Description=myApp Tomcat Web Application Container
 After=syslog.target network.target
   
   
 [Service]
 Type=forking
   
 ExecStart=/app/myApp/tomcat/bin/startup.sh
 ExecStop=/app/myApp/tomcat/bin/shutdown.sh
   
 User=myUser
 Group=myUser
   
 [Install]
 WantedBy=multi-user.target

Service control

Enable user myUser to control Tomcat services:

visudo -f /etc/sudoers
##################################################
## Allow user myUser to control (apache & tomcat) services
%myUser ALL=(root) NOPASSWD: /bin/systemctl
%myUser ALL=(root) NOPASSWD: /usr/sbin/service

Test Tomcat

For Tomcat testing I use my ShowHeaders app (GitHub).
ShowHeaders is a minimalistic webapp that is not much more than a “Hello World”, but it shows the HTTP headers, what is quite useful when testing reverse proxy integration.

# copy ShowHeaders App for Testing (also for Reverse Proxy Configuration Testing)
[myUser@DEV ~]$ cp /app/files/ShowHeaders/ROOT.war /app/myApp/tomcat/webapps/
# start tomcat:
[myUser@DEV ~]$ sudo systemctl start tomcat

#
curl localhost:7011

Test connection from outside the server itself: http://myAppServer:7011/