Categories
Development Java

Default Charset UTF-8

New Java 18 Feature: Default Charset UTF-8

Intern benutzt Java UTF-16, "extern" wurde bisher standartmäßig das Encoding des Betriebssystems, zB Windows-1252, verwendet. Das hatte in der Vergangenheit immer wieder zu lustigen Encoding Problemen geführt, so dass ich schon lange meine Projekte und IDE Einstellungen auf UTF-8 umgestellt hatte. Ausnahme sind die Property-Dateien, die erst ab Java 9 UTF-8 können. Und Java 9 ist noch lange nicht in allen Projekten verfügbar.

Mit Java 18 wurde endlich JEP 400: UTF-8 by Default umgesetzt.

Das Problem ���

Ich habe ein kleines Testprojekt aufgesetzt und dafür Java 19 verwendet. Dabei stieß ich irgendwann auf ein Encoding Problem, dass sich recht einfach nachstellen ließ:

package deringo;

public class TestMain {
    public static void main(String[] args) {
        String s = "abc äüß def";
        System.out.println(s);
    }
}

Output:

Projekt auf Java 17 umgestellt und der Output ist korrekt:

Die IDE ist Eclipse und der Bug ist bekannt: Bug 579383

Mit Java 17 wurde, als Vorbereitung auf JEP400, eine neue System Property eingeführt: native.encoding

Die kann man sich ganz einfach anzeigen lassen:

        String encoding = System.getProperty("native.encoding");
        System.out.println(encoding);

Sowohl unter Java 17 wie unter Java 19 wird bei mir angezeigt:

Was nicht funktioniert hat

Die eclipse.ini erweitern:

-Dfile.encoding=UTF-8
-Dsun.stdout.encoding=UTF-8
-Dsun.stderr.encoding=UTF-8

Die Lösung

In der Run Configuration für das Programm unter Common -> Encoding auf Use system encoding umstellen.

Oder erstmal weiterhin Java 17 verwenden. 🤷‍♂️

Categories
Development Java

KeyStore

Im letzten Post musste ich CVS in Eclipse nachinstallieren.

Der Download über die Update Seite hatte aber nicht auf Anhieb geklappt, denn Eclipse konnte sich nicht mit dem Update Server verbinden. Es gab ein Problem mit SSL: "PKIX path building failed"

Hintergrund ist, dass die Verbindung aus dem Firmen Intranet hinaus in das Internet über einen Proxy von ZScaler läuft, der eigene Zertifikate ausstellt und diese sind nicht im Java Truststore.

Ich hatte schon mal in einem anderen Setup das gleiche Problem: Man In The Middle (ZScaler) aber mit anderen Anforderungen und anderer Lösung.

Für mein Eclipse Problem muss ich zuerst die Zertifikate finden, dann den Eclipse Truststore und dann diesem die Zertifikate hinzufügen.

Zertifikate

Die Zertifikate habe ich ganz einfach aus dem Firefox gezogen: Über Settings -> Privacy & Security -> Certificates exportieren:

Truststore

Der Pfad zum Truststore des von Eclipse verwendeten Javas findet sich in der eclipse.ini Datei:

Allerdings nicht im /bin Folder, sondern in der Datei /lib/security/cacerts

Das Standart Passwort lautet: changeit

KeyStore Explorer

Man könnte die Zertifikate händisch per Command Line hinzufügen.

Oder man macht es sich leicht und verwendet ein Tool mit GUI. Da bietet sich der KeyStore Explorer an.

Einfach den KeyStore Explorer öffnen, cacerts-Datei hinein ziehen, Passwort (changeit) eingeben und anschließend die aus dem Firefox heruntergeladenen Zertifikate hineinziehen und importieren. Anschließend speichern und ggf. Eclipse neu starten.

Categories
Development Java

Eclipse removed CVS

Aus der IDE Eclipse wurde der CVS Support entfernt.

Eine lapidare Mitteilung verkündet das Ende einer Ära: link

Ist halt nur blöd, wenn man ein Update fährt und anschließend ist der CVS Support weg, den man aber essentiell für die Arbeit braucht, da die Projekte nun mal in einem CVS liegen. Warum sollte man auch das gute alte CVS verlassen, ist es doch immerhin so perfekt, dass schon seit Jahren keine Updates dafür entwickelt werden mussten. SVN hat unser CVS schon überlebt und ob Git sich durchsetzen wird, muss sich erst noch zeigen.

Das Gute ist: Man kann aus einer älteren Eclipse Version die CVS Funktionalität nachinstallieren. Die Anleitung dazu habe ich hier gefunden:

On Eclipse, click on Help -> Install New Software...

On the Available Software dialog, click "Add". On the "Add Repository" box, enter Name: CVS Support; Location: https://download.eclipse.org/eclipse/updates/4.22

Click "Eclipse CVS Client" and install the CVS Client components.

Categories
Development Linux

Dart Sass WSL

Install Sass on WSL

Install Dart Sass instead of Ruby Sass on WSL.

I am following this instructions: https://www.geeksforgeeks.org/how-to-install-dart-sass-in-linux/

At the end it was supereasy with previousliy installed Node Package Manager (npm):

# install sass with npm
npm i -g sass

# check version
sass --version
1.55.0 compiled with dart2js 2.18.1

Sass

Homepage

Latest Sass Release on GitHub

Categories
Development

Node.js WSL

Install Node.js on WSL

Install Node.js on Windows Subsystem for Linux (WSL2)

I am following this instructions: https://learn.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-wsl

Install Node Version Manager (nvm)

sudo apt-get install curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash

## Open NEW TERMINAL windows

# test installation
command -v nvm
nvm


# check version
nvm ls

->       system
iojs -> N/A (default)
node -> stable (-> N/A) (default)
unstable -> N/A (default)


# check node version
node --version
v10.19.0

Install Node.js

# install LTS
nvm install --lts


# check version
nvm ls
->     v16.18.0
         system
default -> lts/* (-> v16.18.0)
iojs -> N/A (default)
unstable -> N/A (default)
node -> stable (-> v16.18.0) (default)
stable -> 16.18 (-> v16.18.0) (default)
lts/* -> lts/gallium (-> v16.18.0)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.24.1 (-> N/A)
lts/erbium -> v12.22.12 (-> N/A)
lts/fermium -> v14.20.1 (-> N/A)
lts/gallium -> v16.18.0


# check node version
node --version
v16.18.0


# check npm version
npm --version
8.19.2

Some npm commands

# Show installed Nodes
nvm ls
# Show available versions
nvm ls-remote
# Install latest version
nvm install node
# Install LTS version
nvm install --lts
# Install a specific version (list available -> example 16.18.0)
nvm install 16.18.0
# Use a specific version
nvm use 16.18.0
# Show npm version
npm --version

Categories
Development

OneLogin Logout

Um das Benutzererlebnis der cloudbasierten IAM Lösung OneLogin komplett zu machen, muss irgendwann halt auch mal der Logout folgen.

Das hatte ich bisher nicht so wirklich auf dem Schirm, schließlich heißt es ja "OneLogin" und nicht "OneLogout".

Der Logout stellte uns erstmal vor große Probleme, denn irgendwie wollte kein Ansatz so richtig funktionieren. Irgendwann hatte ich dann aber die richtigen Informationsteile richtig zusammengepuzzelt und dann war es super einfach.

Konfiguration OneLogin

In der Konfiguration im OneLogin unter OIDC -> Configuration -> Post Logout Redirect URI setzen.

Beispielsweise: https://www.meinetolleanwendung.de/logoutseite.html

Konfiguration Applikation

In der Applikation selbst muss eigentlich nichts konfiguriert werden.

Es muss lediglich ein Link aufgerufen werden.

In der Konfiguration des vorgeschalteten Reverse Proxies ist folgender Block für den OIDC-Connector:

	#this is required by mod_auth_openidc
    OIDCCryptoPassphrase <INSERT-HERE a-random-secret>

    OIDCProviderMetadataURL <INSERT-HERE>

    OIDCClientID <INSERT-HERE>
    OIDCClientSecret <INSERT-HERE>
    # OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
    OIDCRedirectURI https://www.meinetolleanwendung.de/private/redirect_uri

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

Wir benötigen die OIDCRedirectURI und fügen lediglich ein ?logout für den logout an, gefolgt von = https://www.meinetolleanwendung.de/logoutseite.html für den Redirect zur Post-Logoutseite.

Der ganze Link lautet also:

https://www.meinetolleanwendung.de/private/redirect_uri?logout=https://www.meinetolleanwendung.de/logoutseite.html

Durch Aufruf des Links wird die Session in OneLogin und im Reverse Proxy beendet.

So einfach kann es sein.

Categories
Development

YouTube API Key

Ich möchte den programmatischen Zugriff auf YouTube ausprobieren um ggf. später mit ein paar Ideen zu experimentieren.

Nach der ersten Erkenntnis, dass das gar nicht so gut dokumentiert ist, wie ich es erwartet hätte, folgt die zweite Erkenntnis, dass man dafür einen Api Key braucht.

Hier also die Notizen, wie ich meinen API Key angelegt habe.

Eine schöne Anleitung

Auf Kodi-Tipps gibt es eine schöne, bebilderte Anleitung, der ich hier folge.

Da dort eigentlich alles ausführlich beschrieben zu sein scheint, notiere ich hier nur knapp die Schritte, die ich ausführen werde.

Die Google Developer Console

Den YouTube API Key gibt es über Google und da ich bisher noch nicht viele Erfahrungen mit den Google Produkten als Entwickler gemacht habe, hier die erste wichtige URL zum Einstieg: Die Google Developer Console

Vor ewigen Zeiten hatte ich dort schon mal etwas getestet (Google Auth/Login in KeyCloak eingebunden) und daher ist schon ein Projekt vorhanden, in welchem ich direkt nach Seitenaufruf arbeite:

Natürlich möchte ich nicht in dem Projekt weiterarbeiten, sondern ein neues anlegen:

Meine Schritte in Bildern

User data vs Public data ?
Ich bin mir nicht ganz sicher, was ich benötige. Ich brauche keine persönlichen Informationen, aber ich würde gerne zB die letzten angesehenen Videos meines YouTube Accounts abrufen können.

Starten wir mit Public data.
Die Kodi Anleitung hat User data ausgewählt. Vermutlich werde ich später auch auf User data umsteigen müssen.

Public Data

Für Tests & Entwicklung richte ich keine Restrictions ein.
Sollte mal irgendwann irgendwas irgendwie produktiv gehen, wird eh ein neuer API Key angelegt werden und der wird dann runter restrictiert so weit es geht.

Ganz klasse: Beim Testen des Keys erhalte ich immer "The request is missing a valid API key.". Toll.
Könnte natürlich auch daran liegen, dass ich keine Ahnung habe, was ich da treibe und das falsch mache.
Nützt alles nix, nächster Test mit Personal Data, das wird hoffentlich besser laufen.

Einen Hinweis habe ich noch gefunden, bracht mich aber erstmal nicht weiter:

Personal Data

Entweder finde ich den Einstieg von vorhin nicht mehr, oder durch Anlegen eines ersten API Keys hat sich die Oberfläche verändert und ich muss anders vorgehen.

Create geklickt, paar Sachen ausgefüllt, unsicher geworden, abgebrochen.

Nix besseres gefunden, später wieder auf "OAuth client ID" geklickt, dann sah es wieder anders aus, das ist dann die nächste Seite:

Erstmal mit einer Desktop App starten, später wird dann vielleicht mal eine Web App daraus.

Belohnt wird man dann mit einer OAuth Client-ID und Client-Secret. Sogar im JSON-Format, das ich mir abspeicher.

Ich habe dann noch ein paar Sachen getestet, aber irgendwie war der Wurm drin, nix ging.

Workaround: Projekt löschen, neu anfangen.

Was ein Spaß.

Zweiter Anlauf

Direkt mit User data weiter:

Scopes -> nix geändert -> "Save and continue"

Tja, leider kann ich aber keine User hinzufügen. Toll.

Nächster Anlauf:

Und noch einen API Key generieren:

Frust

Ich habe die Keys mit Postman und in Java Code ausprobiert, aber nichts hat funktioniert. Sehr frustrierend.

So wird das nichts. Ich brauche ein gutes Tutorial.

Die YouTube API Keys konnte ich anlegen, aber gebracht hat es mir nichts.

Der Tag danach

So ganz wollte ich doch nicht aufgeben und habe am Tag nach meinen YouTubeAPIGedöns Experimenten doch noch einen Versuch gewagt.

Über einen Stackoverflow Post habe ich ein Beispiel für eine API Query gefunden:

https://www.googleapis.com/youtube/v3/search?key={KEY}&channelId={CHANNEL-ID}&part=snippet&maxResults=1&order=date&type=video

Als KEY habe ich meinen gestern generierten API Key genommen, als CHANNEL-ID habe ich aus meiner YouTube Abo Liste einen Link zu einem Kanal, und daraus den letzten Teil, genommen.

In meinem Beispiel https://www.youtube.com/channel/UC0RBJwg8ZfRM8TLGOKTpArA -> UC0RBJwg8ZfRM8TLGOKTpArA

Diese Informationen habe ich in Postman eingetragen und konnte endlich eine erfolgreiche Anfrage absenden:

In der Authorization Section hatte ich zuerst den Type auf No Auth zurückgestellt. Das scheint aber nicht erforderlich zu sein.

YAY!

Glück

Bei der Auswahl des Kanals hatte ich Glück!

Denn der Kanal verwendet eine Kanal-URL.
Beispiel: youtube.com/channel/UCUZHFZ9jIKrLroW8LcyJEQQ

Viele andere Kanäle in meiner Abo Liste verwenden noch eine alte benutzerdefinierte URL.
Beispiel: youtube.com/c/YouTubeCreators

Mit einer alten benutzerdefinierten URL funktioniert der Aufruf nämlich nicht, sondern nur mit der Kanal-URL.

Details zu den YouTube Kanal URLs: Link

Kanal URL

Wie findet man in so einem Fall die Kanal URL heraus?

Einen API Call konnte ich nicht finden. Und auch sonst keine elegante, programmatische Lösung.

Zum Glück existiert aber eine Website, die die Kanal URL für einen herausfinden kann. Wie auch immer die das macht.

YOUTUBE CHANNEL ID

Categories
Development Java Linux

SAPJCO 2

Problem

We have a very, very old application that needs to be migrated into AWS. So we copied all files into AWS EC2 instance and tried to start the application. After fixing a lot of minor problems we faced a tough challenge with a SAPJCO RFC Call.

The Exception message was something like this:

Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'
JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [sapjcorfc (Not found in java.library.path)]. java.library.path [/usr/lib/jvm/java-1.6.0-ibm.x86_64/jre/lib/amd64/default:/usr/lib/jvm/java-1.6.0-ibm.x86_64/jre/lib/amd64:/usr/lib]
        at com.sap.mw.jco.JCO.<clinit>(JCO.java:871)
        at java.lang.J9VMInternals.initializeImpl(Native Method)
        at java.lang.J9VMInternals.initialize(J9VMInternals.java:199)

I guess, with a JCO Version 3 we would not have much trouble, but in this ancient application JCO Version 2 is used and we cannot update to Version 3 without a huge efford. In other projects I had the luck that I could migrate to Version.

The application is running on a Linux system. But belive me: it would have been much harder on a Windows machine.

Analysis

To find the cause of the problem I wrote the simpliest JCO Test Programm I can image:

import com.sap.mw.jco.JCO;

public class TestMain {

    public static void main(String[] args) {
        System.out.println(JCO.getVersion());
    }

}

My analysis programm structure:

app
├─ JCo/
│  ├─ librfccm.so
│  ├─ libsapjcorfc.so
│  ├─ sapjco.jar
├─ TestMain.java

Compile from command line:

javac -cp ".:/app/JCo/sapjco.jar" TestMain.java

Run from command line:

java -cp ".:/app/JCo/sapjco.jar" TestMain

That gave me another error:

Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'
JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: librfccm.so: cannot open shared object file: No such file or directory]. java.library.path [/app/JCo]
        at com.sap.mw.jco.JCO.<clinit>(JCO.java:871)
        at TestMain.main(TestMain.java:11)

Need to set an environment property first:

export LD_LIBRARY_PATH=/app/JCo

Run command line to start programm again and got another error:

Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC'
JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: libstdc++.so.5: cannot open shared object file: No such file or directory]. java.library.path [/app/JCo]
        at com.sap.mw.jco.JCO.<clinit>(JCO.java:871)
        at TestMain.main(TestMain.java:11)

The interesting part of the error message:

Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: libstdc++.so.5

Solution

We need the libstdc++.so.5 library, but installed is libstdc++.so.6

To get libstdc++.so.5 we installed package compat-libstdc++-33-3.2.3-66.x86_64:

yum install compat-libstdc++-33-3.2.3-66.x86_64

## to be honest, I am not exactly 100%% sure, what I did in my investigations, so the command may be a little differend, ex:
# yum install compat-libstdc++-33-3
# yum install compat-libstdc++-33
# yum install compat-libstdc++-33 libstdc++.so.5

Test

Run from command line:

java -cp ".:/app/JCo/sapjco.jar" TestMain

That gave me no error, but SAPJCo Version number.:

2.1.10 (2011-05-10)

Finally it worked 😎

Anekdotum

Quote from an StackOverflow post from 2010:

libstdc++.so.5 is a very old version of the standard c++ library.

Some Analysis Details

Writing this article is giving me the feeling, that this was all super easy. But in reality it was a real pain in the allerwertesten.

To isolate the source of the problem, I did not only write the small Java (JCO.getVersion) application, I also set up a Docker environment.

One challenge was to find a useful Docker image to start from. I started with an OpenJDK Image that was already deprecated. Deprecated was not the problem, but I could not install libstdc++.so.5.

Next I tried to use the newer, undeprecated Eclipse-Temurin Image. But still could not install libstdc++.so.5

So I finally ended in a Debian Image and self installed Java where I was able to install libstdc++5.

FROM debian:bookworm-slim
 RUN apt-get update && apt-get install -y locales openjdk-11-jdk libstdc++5 \
     && rm -rf /var/lib/apt/lists/* \
     && localedef -i de_DE -c -f UTF-8 -A /usr/share/locale/locale.alias de_DE.UTF-8
 ENV LANG de_DE.utf8

COPY ./JCo/* /app/JCo/
COPY TestMain.java /app
WORKDIR /app
 ENV LD_LIBRARY_PATH=/app/JCo
 
 RUN javac -cp ".:/app/JCo/sapjco.jar" TestMain.java 

 CMD ["java", "-cp", ".:/app/JCo/sapjco.jar", "-Djava.library.path=/app/JCo", "TestMain"]

Note:

The locale might not be neccessary.
The "-Djava.library.path=/app/JCo" Parameter is not neccessary. But I leave it as an example, how one can use it.

Build and start the app:

docker build -t my-java-app .
docker run -it --rm --name my-running-app my-java-app
2.1.10 (2011-05-10)

Work in container

To work in the running container:

docker exec -it my-running-app /bin/sh

But there is one problem: You can only interact with a running container. But the TestMain-Programm is executed and immediately closed.

So I wrote another Test Programm, that keeps running, so I can enter the running container and test stuff (install packages, compile Java programm, etc.):

import java.io.BufferedReader;
import java.io.InputStreamReader;

import com.sap.mw.jco.JCO;

public class TestMain {

    public static void main(String[] args) {
        System.out.println("Hello World");
        //System.out.println("JCO Version: " + JCO.getVersion());

        while (true) {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("Enter Input : ");
            try {
                String s = br.readLine();
                System.out.println(s);
            }catch(Exception e) {
                System.out.println(e);
            }
        }
        
    }

}
Categories
Development

Mutual SSL

Bei der Mutual SSL Authentication authentifizieren sich der Server und der Client gegenseitig.

PoC Setup

In diesem PoC authentifizieren sich der Load Balancer und der Server 1, bzw. Load Balancer und der Server 2, gegenseitig.

Der User kann durch den Load Balancer die Seite des Server 1, bzw. Server 2, aufrufen.

Ein direkter Aufruf der Seite auf den Servern, also ohne Load Balancer, ist nicht möglich, da nur der Load Balancer sich an den Servern 1&2 authentifizieren kann.

Zu Testzwecken wird allerdings Server 2 offen gehalten, eine Authentifizierung ist nicht erforderlich.

Keys und Zertifikate

Es wird eine eigene CA zum signieren der Zertifikate benötigt.

Für jeden Server wird ein Private Key und signierter Public Key benötigt.

Der LoadBalancer benötigt noch einen kombinierten Key aus seinem Private und Public Key.

Alle Dateien werden im Ordner certs abgelegt.

## Create CA
# Generate CA Key:
openssl genrsa -out ca-key.pem 2048
# Generate CA Certificate:
openssl req -x509 -new -nodes -extensions v3_ca -key ca-key.pem -days 1024 -out ca-root.pem -sha512 -subj "/C=DE/ST=NRW/L=Leverkusen/O=DerIngo/OU=IT Department/CN=deringo.de"


## Generate LoadBalancer
# Generate LoadBalancer Key:
openssl genrsa -out loadbalancer-key.pem 2048

## Generate CSR LoadBalancer
vim loadbalancer_csr_details.txt
openssl req -new -key loadbalancer-key.pem -out loadbalancer.csr -config loadbalancer_csr_details.txt

## Sign CSR
vim loadbalancer_ssl-extensions-x509.conf
openssl x509 -req -in loadbalancer.csr -CA ca-root.pem -CAkey ca-key.pem -CAcreateserial -out loadbalancer.crt -days 365 -sha512  -extensions v3_ca -extfile loadbalancer_ssl-extensions-x509.conf

## Mutual SSL - Reverse Proxy
## Create a file with the private and the signed public key:
# copy private key
cp loadbalancer-key.pem loadbalancer-combined.key
# copy public key (without public key of CA)
cat loadbalancer.crt >> loadbalancer-combined.key

## Add Public Key of CA to Public Key of RP
cat ca-root.pem >> loadbalancer.crt



## Generate Server_1
# Generate Server_1 Key:
openssl genrsa -out server_1-key.pem 2048

## Generate CSR Server_1
vim server_1_csr_details.txt
openssl req -new -key server_1-key.pem -out server_1.csr -config server_1_csr_details.txt

## Sign CSR
vim server_1_ssl-extensions-x509.conf
openssl x509 -req -in server_1.csr -CA ca-root.pem -CAkey ca-key.pem -CAcreateserial -out server_1.crt -days 365 -sha512  -extensions v3_ca -extfile server_1_ssl-extensions-x509.conf

## Add Public Key of CA to Public Key of RP
cat ca-root.pem >> server_1.crt


## Generate Server_2
# Generate Server_2 Key:
openssl genrsa -out server_2-key.pem 2048

## Generate CSR Server_2
vim server_2_csr_details.txt
openssl req -new -key server_2-key.pem -out server_2.csr -config server_2_csr_details.txt

## Sign CSR
vim server_2_ssl-extensions-x509.conf
openssl x509 -req -in server_2.csr -CA ca-root.pem -CAkey ca-key.pem -CAcreateserial -out server_2.crt -days 365 -sha512  -extensions v3_ca -extfile server_2_ssl-extensions-x509.conf

## Add Public Key of CA to Public Key of RP
cat ca-root.pem >> server_2.crt

Mutual SSL

LoadBalancer

Create a file with the private and the signed public key (bereits im Schritt zuvor erfolgt):

## Mutual SSL - Reverse Proxy
## Create a file with the private and the signed public key:
# copy private key
cp loadbalancer-key.pem loadbalancer-combined.key
# copy public key (without public key of CA)
cat loadbalancer.crt >> loadbalancer-combined.key

To communicate to the application server with SSL we need this lines in our HTTP configuration:

SSLProxyEngine On
SSLProxyMachineCertificateFile /usr/local/apache2/conf/combined.key

Server_1

Die Verifizierung des Clients wird aktiviert.

SSLVerifyClient require
SSLCACertificateFile /usr/local/apache2/conf/server.crt

Server_2

Die Verifizierung des Clients wird nicht aktiviert.

Docker

Es wird Load Balancer, Server 1 und Server 2 definiert.

Für beide Server wird ein Port nach Außen zum Testen aufgemacht.

version: '3.8'
services:

  loadbalancer:
    build: ./loadbalancer
    hostname: loadbalancer
    volumes:
      - ${PWD}/loadbalancer/conf/loadbalancer_httpd.conf:/usr/local/apache2/conf/httpd.conf
      - ${PWD}/loadbalancer/conf/loadbalancer.conf:/usr/local/apache2/conf/loadbalancer.conf
      - ${PWD}/loadbalancer/conf/loadbalancer-ssl.conf:/usr/local/apache2/conf/loadbalancer-ssl.conf
      - ${PWD}/certs/loadbalancer.crt:/usr/local/apache2/conf/server.crt
      - ${PWD}/certs/loadbalancer-key.pem:/usr/local/apache2/conf/server.key
      - ${PWD}/certs/loadbalancer-combined.key:/usr/local/apache2/conf/combined.key
    ports:
      - 80:80
      - 443:443

  server_1: 
    build: ./server_1
    hostname: server_1
    volumes:
      - ./server_1/public_html:/usr/local/apache2/htdocs
      - ${PWD}/server_1/conf/server_httpd.conf:/usr/local/apache2/conf/httpd.conf
      - ${PWD}/server_1/conf/server.conf:/usr/local/apache2/conf/server.conf
      - ${PWD}/server_1/conf/server-ssl.conf:/usr/local/apache2/conf/server-ssl.conf
      - ${PWD}/certs/server_1.crt:/usr/local/apache2/conf/server.crt
      - ${PWD}/certs/server_1-key.pem:/usr/local/apache2/conf/server.key
    ports:
      - 8091:443

  server_2: 
    build: ./server_2
    hostname: server_2
    volumes:
      - ./server_2/public_html:/usr/local/apache2/htdocs
      - ${PWD}/server_2/conf/server_httpd.conf:/usr/local/apache2/conf/httpd.conf
      - ${PWD}/server_2/conf/server.conf:/usr/local/apache2/conf/server.conf
      - ${PWD}/server_2/conf/server-ssl.conf:/usr/local/apache2/conf/server-ssl.conf
      - ${PWD}/certs/server_2.crt:/usr/local/apache2/conf/server.crt
      - ${PWD}/certs/server_2-key.pem:/usr/local/apache2/conf/server.key
    ports:
      - 8092:443

Test

https://localhost

Man landet abwechselnd auf beiden Servern:

https://localhost:8091/

Ein direkter Zugriff auf Server_1 wird nicht gestattet, da der Client (unser Browser) nicht verifiziert werden konnte.

https://localhost:8092/

Ein direkter Zugriff auf Server_2 ist möglich, da wir auf diesem Server keine Verifizierung aktiviert haben.

GitHub

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

Categories
Development

SSL für Apache HTTP

Der letzte Post endete mit einer Fehlermeldung:

Analyse

Der Fehler trat auf, nachdem ich meinen PoC von meinem Entwicklungsrechner in die Cloud transferierte und dort laufen ließ und dort testen wollte.

Auf der Console des Servers sieht der Aufruf mittels "curl http://localhost/private/index.html" auch erstmal gut aus.

Der Zugriff aus dem Browser mittels IP "http://<server-ip>/private/index.html" schlägt mit obiger Fehlermeldung komplett fehl.

Lokal nachstellen

Um das Problem lokal auf dem Entwicklerlaptop nachstellen zu können, muss erstmal die lokale (Docker-)IP ermittelt werden:

ping host.docker.internal

Ping wird ausgeführt für host.docker.internal [192.168.2.149] mit 32 Bytes Daten:
Antwort von 192.168.2.149: Bytes=32 Zeit<1ms TTL=128
Antwort von 192.168.2.149: Bytes=32 Zeit<1ms TTL=128
Antwort von 192.168.2.149: Bytes=32 Zeit<1ms TTL=128

Der Aufruf der PRIVATE Page, nach OneLogin Login versteht sich, funktioniert über localhost:

Der Aufruf der PRIVATE Page funktioniert nicht über die IP:

Auszug aus dem Logfile des Apache HTTP Servers:

reverseproxy_1  | [Thu Jun 09 13:00:54.048377 2022] [auth_openidc:error] [pid 256:tid 140563693147904] [client 172.24.0.1:33902] oidc_authenticate_user:
 the URL hostname (localhost) of the configured OIDCRedirectURI does not match the URL hostname of the URL being accessed (192.168.2.149): the "state" a
nd "session" cookies will not be shared between the two!, referer: http://192.168.2.149/
reverseproxy_1  | 172.24.0.1 - - [09/Jun/2022:13:00:54 +0000] "GET /private/index.html HTTP/1.1" 500 531

Der Fehler besagt also, "the URL hostname (localhost) of the configured OIDCRedirectURI does not match the URL hostname of the URL being accessed (192.168.2.149)".

OneLogin Konfigurationsanpassung

Die Redirect URIs Liste muss um die IP des Servers erweitert werden:

Allerdings ist http als Protokoll lediglich für localhost zugelassen, nicht aber für alle anderen URIs, wie zB unsere Server IP oder später der Domain Name des Servers.

Die Redirect URI muss also mit https eingetragen werden.

Daraus ergibt sich allerdings auch, dass unser Server über https erreichbar sein muss!

Apache mit HTTPS

Ein selbstsigniertes Zertifikat ist für unseren PoC vollkommen ausreichend. Eine Signierung durch zB Let's Encrypt ist nicht notwendig.

Für diesen PoC brauchen wir lediglich den Reverse Proxy, der im ersten Schritt eine Seite über http und https ausliefern kann. Später kommt dann die Authentifizierung mit OneLogin hinzu.

Schritt 1: http

Das Dockerfile des Apache aus den vorherigen PoCs uA schon mit OpenID Module:

FROM httpd:2.4
RUN apt update && apt install -y \
	libapache2-mod-auth-openidc \
	ca-certificates
RUN cp /usr/lib/apache2/modules/mod_auth_openidc.so /usr/local/apache2/modules/
RUN mv conf/httpd.conf conf/container_httpd.conf
CMD ["httpd-foreground"]

Gestartet wird mit Docker Compose:

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
      - ${PWD}/reverseproxy/conf/reverseproxy-ssl.conf:/usr/local/apache2/conf/reverseproxy-ssl.conf
    ports:
      - 80:80
      - 443:443

Die Einbindung der Konfigurationsdateien erfolgt über reverseproxy_httpd.conf, die in den Container als httpd.conf hineinkopiert wird:

# load original configuration first
Include conf/container_httpd.conf

# customized configuration
ServerName reverseproxy
Include conf/reverseproxy.conf
Include conf/reverseproxy-ssl.conf

Die Konfiguration für http erfolgt in reverseproxy.conf:

<VirtualHost *:80>
    ServerAdmin deringo@github.com
    DocumentRoot "/usr/local/apache2/htdocs"
    ServerName localhost
</VirtualHost>

Die Konfiguration für https wird später in reverseproxy-ssl.conf erfolgen, die Datei ist im ersten Schritt leer.

Starten mittels Docker Compose:

docker-compose up

Die Seite ist sowohl über localhost, als auch über IP erreichbar:

Schritt 2: Port 443

Der Server soll über den https-Port 443 die Seiten ausliefern.

Dazu wird die http/Port 80-Konfiguration angepasst kopiert.

Wichtig ist auch das "Listen 443", das am Anfang hinzugefügt werden muss.

Listen 443
<VirtualHost *:443>
    ServerAdmin deringo@github.com
    DocumentRoot "/usr/local/apache2/htdocs"
    ServerName localhost
</VirtualHost>

Server neu durchstarten:

docker exec -it selfsignedtest_reverseproxy_1 bash
apachectl configtest && apachectl restart

Die Seite ist über http://localhost:443 erreichbar. Richtig: http, nicht https! Verschlüsselt wird hier noch nix.

Schritt 3: https

Als ersten Teilschritt wird die reverseproxy-ssl.conf mit der Default-SSL-Konfiguration überschrieben und alle Kommentare gelöscht.

Folgende Änderungen werden gemacht:

ServerName localhost:443
ServerAdmin deringo@github.com

SSLEngine off

#SSLCertificateFile "/usr/local/apache2/conf/server.crt"
#SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"

Server neu starten, anschließend ist die Seite weiterhin unverschlüsselt über http://localhost:443 erreichbar.

Als nächster Teilschritt wird SSLEngine on gestellt und die beiden CertificateFile Einträge wieder ent-kommentiert.
Außerdem müssen die Module mod_socache_shmcb.so und mod_ssl.so geladen werden.

LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

ServerName localhost:443
ServerAdmin deringo@github.com

SSLEngine on

SSLCertificateFile "/usr/local/apache2/conf/server.crt"
SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"

Ein Neustart des Servers schlägt jetzt fehl, da die CertificateFiles noch nicht existieren.

Certificate Files erzeugen lassen:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /usr/local/apache2/conf/server.key -out /usr/local/apache2/conf/server.crt

Server neu durchstarten und der unverschlüsselte Aufruf wird abgewiesen:

Der verschlüsselte Aufruf über https://localhost funktioniert:

Allerdings wird die Seite als "Nicht sicher" angezeigt und es musste einmalig ein entsprechender Hinweis weggeklickt werden, da das Zertifikat nicht von einer vertrauenswürdigen Stelle signiert wurde.

Da am Ende praktisch nichts von der Standard-Konfiguration für SSL geändert wurde, wird die Konfiguration wieder vereinfacht:

LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

Include conf/extra/httpd-ssl.conf

Schritt 4: OneLogin

Die OneLogin Konfiguration wird hinzugefügt:

## Default SSL
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

Include conf/extra/httpd-ssl.conf

##
LoadModule proxy_module modules/mod_proxy.so
LoadModule xml2enc_module modules/mod_xml2enc.so
LoadModule proxy_html_module modules/mod_proxy_html.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_http_module modules/mod_proxy_http.so

LoadModule auth_openidc_module modules/mod_auth_openidc.so

<VirtualHost localhost:443>
    ServerAdmin deringo@github.com
    DocumentRoot "/usr/local/apache2/htdocs"
    ServerName localhost:443
    ## mod_auth_openidc
    ## https://github.com/zmartzone/mod_auth_openidc
    
    #this is required by mod_auth_openidc
    OIDCCryptoPassphrase <INSERT-HERE a-random-secret>

    OIDCProviderMetadataURL <INSERT-HERE>

    OIDCClientID <INSERT-HERE>
    OIDCClientSecret <INSERT-HERE>
    # OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
    OIDCRedirectURI https://localhost/private/redirect_uri

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


    <Location /private/>
        AuthType openid-connect
        Require valid-user
    </Location>

</VirtualHost>                                  

Alle Seiten sind über https erreichbar.

Aber leider auch die Private Page OHNE dass vorher ein Login über OneLogin erfolgen musste.

Irgendwie klappt das nicht mit dem überschreiben/erweitern der default SSL Konfiguration. Also doch alles in eine Datei:

LoadModule proxy_module modules/mod_proxy.so
LoadModule xml2enc_module modules/mod_xml2enc.so
LoadModule proxy_html_module modules/mod_proxy_html.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_http_module modules/mod_proxy_http.so

LoadModule auth_openidc_module modules/mod_auth_openidc.so



LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

Listen 443

SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
SSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES

SSLHonorCipherOrder on 

SSLProtocol all -SSLv3
SSLProxyProtocol all -SSLv3

SSLPassPhraseDialog  builtin

SSLSessionCache        "shmcb:/usr/local/apache2/logs/ssl_scache(512000)"
SSLSessionCacheTimeout  300


##
## SSL Virtual Host Context
##

<VirtualHost _default_:443>

#   General setup for the virtual host
ServerName localhost:443
ServerAdmin deringo@github.com
DocumentRoot "/usr/local/apache2/htdocs"
ErrorLog /proc/self/fd/2
TransferLog /proc/self/fd/1

#   SSL Engine Switch:
#   Enable/Disable SSL for this virtual host.
SSLEngine on

SSLCertificateFile "/usr/local/apache2/conf/server.crt"
SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"

<FilesMatch "\.(cgi|shtml|phtml|php)$">
    SSLOptions +StdEnvVars
</FilesMatch>
<Directory "/usr/local/apache2/cgi-bin">
    SSLOptions +StdEnvVars
</Directory>

BrowserMatch "MSIE [2-5]" \
         nokeepalive ssl-unclean-shutdown \
         downgrade-1.0 force-response-1.0

CustomLog /proc/self/fd/1 \
          "%%t %%h %%{SSL_PROTOCOL}x %%{SSL_CIPHER}x \"%%r\" %%b"




    ## mod_auth_openidc
    ## https://github.com/zmartzone/mod_auth_openidc
    
    #this is required by mod_auth_openidc
    OIDCCryptoPassphrase <INSERT-HERE a-random-secret>

    OIDCProviderMetadataURL <INSERT-HERE>

    OIDCClientID <INSERT-HERE>
    OIDCClientSecret <INSERT-HERE>
    # OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
    OIDCRedirectURI https://localhost/private/redirect_uri

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


    <Location /private/>
        AuthType openid-connect
        Require valid-user
    </Location>

</VirtualHost>

So funktioniert es wie gewünscht und die Private Page wird erst nach Login angezeigt.

Allerdings nur über localhost. Nicht über die IP.

Die OIDCRedirectURI wird von https://localhost/private/redirect_uri auf https://192.168.2.149/private/redirect_uri geändert und schon funktioniert es genau anders herum: Nur über die IP, nicht über localhost.

Das ist nicht ganz so, wie ich mir das vorgestellt habe, aber für den PoC sollte es ausreichend sein.

Schritt 5: Nur https

Alles was über http rein kommt, soll über https weitergeleitet werden:

LoadModule rewrite_module modules/mod_rewrite.so
<VirtualHost *:80>
    ServerAdmin deringo@github.com
    DocumentRoot "/usr/local/apache2/htdocs"
    ServerName localhost
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %%{HTTPS} off
        RewriteRule (.*) https://%%{HTTP_HOST}%%{REQUEST_URI} [R=301,L]
    </IfModule>
</VirtualHost>

Schritt 6: Docker

Die Generierung der Zertifikate erfolgte im laufenden Container. Dieser Schritt soll schon beim Erzeugen des Images erfolgen.

Ein Problem war, dass dies interaktiv erfolgen musste. Mit dem Parameter -subj "/" können Key & Zertifikat ohne Interaktion generiert werden. Im Subject könnten Daten eingetragen werden, dies ist aber für den PoC gar nicht erforderlich.

Einmal der Befehl vollständig, wie er im Container ausgeführt werden könnte:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /usr/local/apache2/conf/server.key -out /usr/local/apache2/conf/server.crt -subj "/"

Der Befehl wird in das Dockerfile hinzugefügt.

Schritt 7: Trusted Certs

Aus den Erkenntnissen des letzten Posts folgt, dass die Möglichkeit vorgesehen werden muss, eine vertrauenswürdige Zertifikatskette einzubinden.

Dazu müssen die *.crt-Dateien in das conf-Verzeichnis kopiert werden.

FROM httpd:2.4
RUN apt update && apt install -y \
	libapache2-mod-auth-openidc \
	ca-certificates
RUN cp /usr/lib/apache2/modules/mod_auth_openidc.so /usr/local/apache2/modules/
RUN mv conf/httpd.conf conf/container_httpd.conf
RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /usr/local/apache2/conf/server.key -out /usr/local/apache2/conf/server.crt -subj "/"
COPY ./conf/*.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
CMD ["httpd-foreground"]

Schritt 8: ShowHeaders

Ich hatte ShowHeaders zwischendurch vermisst, so kommt es am Ende auch wieder mit rein.

Auch das ShowHeaders Image wird für Trusted Certs erweitert.

GitHub

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