Security ist in der IT ein Begriff, der unzählige Bereiche abdeckt. Diese Dokumentation widmet sich einerseits der Verwaltung und Sicherung von Benutzeridentitäten und Zugriffsrechten und andererseits Reverse Proxies.

1. Authentifizierung und Autorisierung

Autor: Moritz V. Gruber

Authentifizierung durch Eingabe von Username und Passwort → Wer bin ich?

Durch Rollen ist man autorisiert, auf Ressourcen zuzugreifen → Was darf ich?

2. Rollen

Autorisierung ohne Rollen

no roles

Autorisierung mit Rollen

roles

3. Standards

3.1. OAuth 2.0

oauth2 logo

Bezeichnung: Open Authorization 2.0

3.1.1. Definition

OAuth 2.0 ist ein Protokoll, welches für die Autorisierung verantwortlich ist. Der Vorgänger 0Auth 1.0 wurde ausschließlich für Webseiten entwickelt und wird nicht mehr häufig verwendet.

Mehr Informationen hier.

Die Authentifizierung von Benutzern wird bei OAuth 2.0 nicht abgedeckt.

oauth2 authorization code flow

Erklärung
  1. Die Applikation fordert vom Browser, dass er den User zu Keycloak umleitet.

  2. Der Browser leitet den User zu Keycloak um.

  3. Keycloak authentifiziert den User, falls er noch nicht mit Keycloak authentifiziert ist.

  4. Die Applikation erhält einen Autorisierungscode von Keycloak.

  5. Die Applikation tauscht diesen Autorisierungscode gegen einen Access-Token von Keycloak ein.

  6. Dieser Access-Token kann nun genutzt werden um auf Ressourcen auf dem Service zuzugreifen.

3.2. OIDC

oidc logo

Bezeichnung: OpenID Connect

3.2.1. Definition

OIDC ist ein Protokoll, welches auf 0Auth 2.0 aufbaut und eine zusätzliche Schicht für die Authentifizierung hinzufügt.

Mehr Informationen hier.

oidc authorization code flow

Erklärung
  1. Die Applikation fordert vom Browser, dass er den User zu Keycloak umleitet.

  2. Der Browser leitet den User zu Keycloak um.

  3. Keycloak authentifiziert den User, falls er noch nicht mit Keycloak authentifiziert ist.

  4. Die Applikation erhält einen Autorisierungscode von Keycloak.

  5. Die Applikation tauscht diesen Autorisierungscode gegen einen ID-Token und einen Access-Token von Keycloak ein.

  6. Die Applikation verfügt nun über den ID-Token, mit dem sie die Identität des Benutzers ermitteln und eine authentifizierte Sitzung für den User aufbauen kann.

3.3. SAML 2.0

saml logo

Bezeichnung: Security Assertion Markup Language 2.0

3.3.1. Definition

SAML 2.0 ist ein XML-basiertes Standardprotokoll für Authentifizierung und Autorisierung. Es wird häufig verwendet, um Single Sign-On in Unternehmens- und Regierungsanwendungen zu ermöglichen. Das Protokoll ermöglicht es bestehenden Benutzern sich schnell und einfach in neuen Anwendungen zu authentifizieren.

Mehr Informationen hier.

3.4. JWT

jwt logo

Bezeichnung: Json Web Token

3.4.1. Definition

JSON Web Token ist ein offener Standard, der eine kompakte Möglichkeit zur sicheren Übertragung von Informationen zwischen Parteien als JSON-Objekt definiert. Diese Information wird digital signiert und ist somit vertrauenswürdig.

3.4.2. Struktur

Ein JWT setzt sich aus 3 Teilen zusammen, welche jeweils durch einen . getrennt werden: xxxxx.yyyyy.zzzzz

Header: (XXXXX.yyyyy.zzzzz)

Besteht aus 2 Teilen:

Beispiel
{
  "alg": "HS256", (1)
  "typ": "JWT" (2)
}
1 Verwendeter Signieralgorithmus: Unterschied zwischen HS256 und RS256
2 Typ des Tokens

Dieses JSON wird Base64 verschlüsselt und macht somit den ersten Teil des JWTs aus.

Payload: (xxxxx.YYYYY.zzzzz)

Die Payload enthält die Claims. Claims sind meistens Daten über den User und zusätzliche Daten. Es gibt 3 Arten von Claims:

  • Registered Claims: Vordefinierte Claims wie z. B. exp (expiration time) und noch mehr

  • Public Claims: Sind eigens benutzerdefinierte Claims, welche öffentlich registriert werden. Dabei ist es wichtig darauf zu achten, dass keine Namenskonflikte entstehen.

  • Private Claims: Sind eigens benutzerdefinierte Claims, welche nicht veröffentlicht werden.

Beispiel
{
  "exp": "1734137921",
  "name": "John Doe",
  "admin": true
}

Dieses JSON wird Base64 verschlüsselt und macht somit den zweiten Teil des JWTs aus.

Signature: (xxxxx.yyyyy.ZZZZZ)

Im dritten Part wird der Header, die Payload und ein Secret mit den im Header angegebenen Signieralgorithmus signiert.

Beispiel für HS256
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

Die Signatur kann sicherstellen, dass die Nachricht nicht von Dritten geändert wurde. Dies macht den dritten Teil des JWTs aus.

Zusammenfassung:

Das Ergebnis sind 3 Base64 Strings, welche durch einen . separiert werden.

Mehr Informationen hier.

4. Keycloak

4.1. Was ist Keycloak?

Keycloak ist eine Open-Source-Identity- und Access-Management-Lösung, die Funktionen wie Single Sign-On (SSO), Benutzerverwaltung und Social Login bietet.

Single Sign-On ist ein Authentifizierungsverfahren, bei dem Benutzer sich nur einmal anmelden und anschließend auf mehrere Anwendungen oder Systeme zugreifen können, ohne sich erneut authentifizieren zu müssen.

4.2. Alternativen

Keycloak Auth0 Authelia authentik

Unternehmen

redhat logo

okta logo

404

authentik logo

Open Source

🟢

🔴

🟢

🟢

Protocol Support: OAuth 2.0 und OIDC

🟢

🟢

🟢

🟢

Protocol Support: SAML 2.0

🟢

🟢

🔴

🟢

Language

java logo

404

go logo

python logo

4.3. Wichtige Bestandteile in Keycloak

4.3.1. Realms

A realm manages a set of users, credentials, roles, and groups. A user belongs to and logs into a realm. Realms are isolated from one another and can only manage and authenticate the users that they control.

— Keycloak
realm description

4.3.2. Clients

Clients are applications and services that can request authentication of a user.

— Keycloak
client description

4.3.3. Realm roles

Realm roles are the roles that you define for use in the current realm.

— Keycloak
realm role description

4.3.4. Users

Users are the users in the current realm.

— Keycloak
user description

4.3.5. Groups

A group is a set of attributes and role mappings that can be applied to a user.

— Keycloak
group description

4.3.6. Sessions

Sessions are sessions of users in this realm and the clients that they access within the session.

— Keycloak
session description

5. Keycloak mit dem quarkus-keycloak-admin-rest-client

5.1. Problem

Die Konfiguration von Keycloak wird hauptsächlich mithilfe dessen GUI durchgeführt. Dies kann nervig und langsam sein.

5.2. Lösung

Keycloak bietet eine REST API an, um Konfigurationen vorzunehmen. Die quarkus-keycloak-admin-rest-client dependency bietet Methoden, um jene REST API in einer Quarkus-Applikation verwenden zu können.

5.3. Projekterstellung

Projekterstellung mit Quarkus CLI
quarkus create app at.htl:security-keycloak-admin-client \
    --extension='keycloak-admin-rest-client,rest-jackson' \
    --no-code
Projekterstellung mit Maven CLI
mvn io.quarkus.platform:quarkus-maven-plugin:3.17.3:create \
    -DprojectGroupId=at.htl \
    -DprojectArtifactId=security-keycloak-admin-client \
    -Dextensions='keycloak-admin-rest-client,rest-jackson' \
    -DnoCode
Dependencies aus pom.xml
<!-- ... -->
<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-keycloak-admin-rest-client</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-rest-jackson</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-arc</artifactId>
    </dependency>
</dependencies>
<!-- ... -->

5.4. Keycloak Container starten

  • keycloak Ordner auf der Ebene des Quarkus-Projekts erstellen

mkdir keycloak
  • Im keycloak Ordner eine Datei namens docker-compose.yml erstellen

cd keycloak
touch docker-compose.yml
  • Das docker-compose.yml mit folgendem Code befüllen

services:
  keycloak:
    container_name: keycloak-demo (1)
    build: (2)
      context: .
      dockerfile: Dockerfile
    environment: (3)
      - KEYCLOAK_ADMIN=admin
      - KEYCLOAK_ADMIN_PASSWORD=admin

      - KC_HOSTNAME_STRICT=false
      - KC_HTTP_ENABLED=true
      - KC_HOSTNAME=localhost

      - KC_DB=postgres
      - KC_DB_URL=jdbc:postgresql://keycloak-db/db
      - KC_DB_USERNAME=app
      - KC_DB_PASSWORD=app

      - KC_HEALTH_ENABLED=true
      - KC_METRICS_ENABLED=true
    command: ["start-dev", "--import-realm"] (4)
    volumes:
      - ./import:/opt/keycloak/data/import (5)
      - ./themes:/opt/keycloak/themes/ (6)
    ports:
      - "8000:8080"
      - "9000:9000"
    depends_on:
      - keycloak-db

  keycloak-db: (7)
    container_name: keycloak-db
    image: postgres:17.2
    restart: unless-stopped
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=app
      - POSTGRES_DB=db
    volumes:
      - ./keycloak-db/data:/var/lib/postgresql/data
Erklärung
1 Der Name des Containers
2 Für den Bauvorgang wird ein Dockerfile verwendet
3 Konfiguration in Keycloak
4 start-dev um Keycloak im Dev-Modus zu starten und --import-realm um einen Realm zu importieren
5 Bind mount für eine Realm-JSON Datei
6 Bind mount für ein eigenes Theme
7 Service für eine Datenbank
FROM quay.io/keycloak/keycloak:26.0.2 AS builder (1)
RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:26.0.2 (1)
COPY --from=builder /opt/keycloak/ /opt/keycloak/
Erklärung
1 Es wird die Version 26.0.2 verwendet, da die neuste Version (26.0.7) Änderungen enthält, welche die keycloak-admin-rest-client dependency in Quarkus noch nicht umgesetzt hat. (Stand: 2024-12-15)
  • Den Container mit folgendem Befehl starten

docker compose up
  • Nun kann auf http://localhost:8000, auf den laufenden Keycloak Container zugegriffen werden. In der Anmeldemaske kann man sich mithilfe der im docker-compose.yml angegebenen Username und Passwort als Admin anmelden.

Anmeldemaske
keycloak admin login
keycloak admin console
Figure 1. Admin-Konsole

5.5. Quarkus-Applikation mit Keycloak authentifizieren

keycloak quarkus connection Damit die Quarkus-App im Keycloak Änderungen vornehmen kann, muss sie mit diesem authentifiziert werden. Dafür müssen ein Realm (rot) und ein Client (grün) in diesem Realm erstellt werden. Dies wird in den folgenden 2 Abschnitten erklärt.

5.6. Realm erstellen

Um einen Realm zu erstellen, muss man im linken oberen Eck auf das Dropdown und anschließend auf den Button Create realm klicken.

Realm erstellen Button
keycloak create realm button
  • Namen vergeben (z. B. my-realm)

  • Enabled Option auf On lassen

  • Auf den Create Button klicken

Realm erstellen
keycloak create realm window

5.7. Quarkus-Client erstellen

Um einen Client zu erstellen, muss man im Clients Tab auf den Create client Button klicken.

Client erstellen
keycloak create client button

General Settings

  • Als Client ID wird in diesem Beispiel der Name quarkus-client gewählt

  • Name und Description sind optional

  • Always display in UI auf On, um Client dauerhaft im UI anzuzeigen

Client erstellen - General Settings
keycloak create client general settings

Capability config

  • Client Authentication

  • Authorization

  • Authentication flow

    • Standard flow

    • Direct access grants

    • Implicit flow

    • Service accounts roles

    • OAuth 2.0 Device Authorization Grant

    • OIDC CIBA Grant

Mehr Informationen hier.

Client erstellen - Capability config
keycloak create client capability config

Login settings

leer lassen

5.8. Probe

Nachdem nun ein Realm und ein Client in Keycloak existieren, können wir den keycloak-admin-rest-client ausprobieren.

application.properties
# Quarkus keycloak-admin-client
quarkus.keycloak.admin-client.enabled=true (1)
quarkus.keycloak.admin-client.server-url=http://localhost:8000 (2)
quarkus.keycloak.admin-client.realm=my-realm (3)
quarkus.keycloak.admin-client.client-id=quarkus-client (4)
1 true, wenn injection von Keycloak-Admin-Client unterstützt werden soll
2 Die URL des Keycloak-Servers
3 Der Name des Realms, worin sich der Client befindet
4 Der Name des erstellten Clients
Beispiels-Endpunkt
package at.htl.keycloakDemo.resources;

import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;
import org.keycloak.admin.client.Keycloak;

@Path("/admin")
public class RolesResource {
    @Inject
    Keycloak keycloak; (1)

    @GET
    @Path("/roles")
    public Response getRoles() { (2)
        return Response.ok(
            keycloak
                .realm("my-realm")
                .roles()
                .list()
        ).build();
    }
}
1 Ein Keycloak-Objekt wird injiziert.
2 Endpunkt um alle Rollen des Realms my-realm zu bekommen.

5.8.1. Response

{
  "details": "Error id bc6db0c5-e662-4e91-80e2-48e50026eb69-6, org.jboss.resteasy.reactive.ClientWebApplicationException: Received: 'Server response is: 401' when invoking REST Client method: 'org.keycloak.admin.client.token.TokenService#grantToken'"
}

Der Keycloak-Server gibt eine 401 - Unauthorized Response zurück. Um dies zu beheben, müssen wir unsere Quarkus-Applikation mit dem Keycloak authentifizieren.

5.8.2. Authentifizierung

Es gibt 2 Möglichkeiten zur Authentifizierung mit dem keycloak-admin-client.

  • Grant type: password

  • Grant type: client-credentials (empfohlen)

Ein grant type bezeichnet die Art und Weise wie ein Client sich mit Keycloak authentifiziert.
Grant type: password
application.properties
# Quarkus keycloak-admin-client
quarkus.keycloak.admin-client.enabled=true
quarkus.keycloak.admin-client.server-url=http://localhost:8000
quarkus.keycloak.admin-client.realm=my-realm
quarkus.keycloak.admin-client.client-id=admin-cli (1)
quarkus.keycloak.admin-client.grant-type=password (2)
quarkus.keycloak.admin-client.username=alice (3)
quarkus.keycloak.admin-client.password=alice (3)
1 Für die password Variante muss die client-id auf admin-cli geändert werden. Dieser Admin-Client ist ein default client, auf welchem die Client authentication und Authorization ausgeschaltet sind. Allerdings brauchen wir diese bei dieser Variante nicht.
2 Setzt den grant-type auf password.
3 Username und Passwort des erstellten Users.
User erstellen

Im erstellten Realm auf UsersAdd user klicken.

Obwohl nur Username ein Pflichtfeld ist, müssen auch Email, First name und Last name ausgefüllt werden. Anderenfalls gibt Keycloak bei sämtlichen Requests einen 400 - Bad Request zurück.

keycloak add user

Grant type: client-credentials
application.properties
# Quarkus keycloak-admin-client
quarkus.keycloak.admin-client.enabled=true
quarkus.keycloak.admin-client.server-url=http://localhost:8000
quarkus.keycloak.admin-client.realm=my-realm
quarkus.keycloak.admin-client.client-id=quarkus-client
quarkus.keycloak.admin-client.grant-type=client-credentials (1)
quarkus.keycloak.admin-client.client-secret=<secret> (2)
1 Legt fest, wie die Quarkus-Anwendung Zugangstokens von Keycloak erhält, um administrative Aufgaben auszuführen. Hier wird der client-credentials type gemeinsam mit den client-secret benutzt.
2 Der client-secret des erstellten Clients. Dieser ist unter folgendem Pfad zu finden: Clients<client-name>CredentialsClient Secret
Client secret

keycloak client secret

Jetzt probieren wir denselben Endpunkt nochmals aus.

5.8.3. Response

{
  "details": "Error id bc6db0c5-e662-4e91-80e2-48e50026eb69-5, org.jboss.resteasy.reactive.ClientWebApplicationException: Received: 'Server response is: 403' when invoking REST Client method: 'org.keycloak.admin.client.resource.RolesResource#list'"
}

Diesmal bekommen wir vom Keycloak-Server eine 403 - Forbidden Response. Wir sind nun authentifiziert, allerdings ist der quarkus-client bzw. der User alice nicht autorisiert alle Rollen anzuzeigen.

5.8.4. Autorisierung

Grant type: password

Unter Users<username>Role mappingAssign role können dem User Rollen zugewiesen werden. Wir weisen dem User die Rolle view-realm zu, um lesenden Zugriff auf den Realm zu bekommen.

User roles

keycloak user roles]

Grant type: client-credentials

Unter Clients<client-name>Service accounts rolesAssign role können dem Client Rollen zugewiesen werden. Wir weisen dem Client die Rolle view-realm zu, um lesenden Zugriff auf den Realm zu bekommen.

Client roles

keycloak client roles]

5.8.5. Response

[
  {
    "id": "1a6cc8e5-87ee-4871-a946-23f406bacea1",
    "name": "uma_authorization",
    "description": "${role_uma_authorization}",
    "scopeParamRequired": null,
    "composite": false,
    "composites": null,
    "clientRole": false,
    "containerId": "a22e79d4-1c88-4ce2-87a2-4757186910c1",
    "attributes": null
  },
  {
    "id": "6f822b0e-6db4-454d-a205-84f0bcd08aeb",
    "name": "offline_access",
    "description": "${role_offline-access}",
    "scopeParamRequired": null,
    "composite": false,
    "composites": null,
    "clientRole": false,
    "containerId": "a22e79d4-1c88-4ce2-87a2-4757186910c1",
    "attributes": null
  },
  {
    "id": "27fd8fbd-7bdf-4b5e-ad05-340a49c2c2f4",
    "name": "default-roles-my-realm",
    "description": "${role_default-roles}",
    "scopeParamRequired": null,
    "composite": true,
    "composites": null,
    "clientRole": false,
    "containerId": "a22e79d4-1c88-4ce2-87a2-4757186910c1",
    "attributes": null
  }
]

Jetzt werden die 3 default Realm roles als Response geliefert.

Falls man Zugriffsrechte, für zum Beispiel das Kreieren von Realms benötigt muss man in den application.properties den Realm master angeben. In diesem gibt es zusätzliche Rollen, wie Create realm oder auch admin um die Realms zu verwalten.

6. Custom Theme

Keycloak bietet eine Möglichkeit custom Themes in Form von css zu verwenden. Dafür benötigt man eine Datei namens theme.properties und natürlich ein styles.css.

theme.properties
parent=keycloak (1)
import=common/keycloak (2)
styles=css/login.css css/styles.css (3)
1 Das parent theme auf dem das custom Theme aufbaut
2 Mit dem import können common Ressourcen importiert werden
3 Alle hier angegeben Stylesheets werden für das custom Theme angewandt
styles.css
h1 {
    color: red;
}

Um dieses Theme nun in Keycloak verwenden zu können müssen wir eine spezifische Ordnerstruktur neben unserem docker-compose.yml anlegen:

custom theme folder structure

Nun müssen wir noch einen bind mount anlegen:

services:
  keycloak:
    # ...
    volumes:
      - ./themes:/opt/keycloak/themes/
    # ...

Nachdem man den Keycloak startet, kann man unter <realm-name>Realm settingsThemes sein eigenes Theme auswählen.

Vorher

keycloak v2 theme

Nachher

keycloak custom theme

Mehr zu custom Themes hier.

7. Demo

8. Keycloak Authentication Flows

Autor: Tobias J. Aichinger

8.1. Client Erstellung

authentication flow
  • Was ist ein Flow und was ein Grant?

    • Leicht gesagt handelt es sich bei beiden, um eine Methode um einen Zugriffstoken zu erhalten

  • Standard flow (Authorization Code flow)

Details
standard flow
  • Implicit flow

    • NICHT VERWENDEN, weil der Browser direkt den access token bekommt und ihn bei einem redirect zu unserem backend einfach in der URL setzt

  • OAuth 2.0 Device Authorization Grant

    • Wird für die Authentifizierung für Geräte ohne Eingabemedien wie smart TVs verwendet

    • Dies geschieht durch eine URL, die vom Gerät angezeigt wird und dann am smartphone oder tablet geöffnet werden muss (hierzu wird keine Kommunikation zwischen den beiden geräten benötigt)

Details
deviceflow
  • OIDC CIBA (Client Initiated Backchannel Authentication) Grant

    • Erfolgt durch ein externes authentifizierungs Device des Benutzers

Details
oidc ciba grant
  • Direct access grants

    • NICHT VERWENDEN

    • Da der Nutzer sich mit seinen Benutzernamen und Passwort bei der Applikation anmeldet, werden diese Daten mit anderen Applikation außer Keycloak ausgetauscht. Dadurch wird eine größere Angriffsfläche geboten.

  • Service accounts roles

    • Um einen access token ohne Nutzer zu erhalten, also nur für den Client

    • Eine mögliche Anwendung könnte eine Wetter-API sein

Details
service client grant
Alle Flows die nicht verwendet werden sollten sind in OAuth 2.1 bereits deprecated.

8.2. Flow Konfigurierung

Diese Flows beschreiben nun welche Schritte für die Authentifizierung in verschiedenen Umgebung verwendet werden. Unten sind die Flows welche von Keycloak bereits implementiert wurden und einfach verwendet werden können.

built in flows

Der "Create flow" button tut, was er sagt und erstellt einen Leeren Flow dies wird von Keycloak nicht empfohlen, da man eher bestehende Flows Duplizieren soll. Wenn man einen Flow dann Editieren oder Ansehen will, kann man auf den Namen drücken, um dessen Ansicht zu öffnen:

browser flow 1
browser flow 2

Hier kann man den Aufbau von Flows gut sehen den:

  • Steps

    • Sind hierarchisch aufgebaut

    • Können weiter Untergeordnete schritte haben

    • Beliebig verschiebbar

  • Requirements

    • Required: muss erfolgreich Ausgeführt werden

    • Optional: wenn es der User konfiguriert hat, sonst wird es ignoriert

    • Disabled: wird nicht ausgeführt

    • Alternative: es muss mindestendes ein Authentifizierungstyp auf dieser Ebene erfolgreich sein

8.3. Benutzerdefinierter Authentifikator

Hier wird gezeigt wie ein Authentifikator für Keycloak entwickelt werden kann. Genauer gesagt wird ein weiterer Autorisierungschritt im Browser-Flow hinzugefügt, bei dem eine Quiz-Frage richtig beantwortet, werden muss um sich Einloggen zu können.

Anmeldeinformation Tabelle von Keycloak
-----------------------------
| ID                        |
-----------------------------
| user_ID                   |
-----------------------------
| credential_type           |
-----------------------------
| created_date              |
-----------------------------
| user_label                |
-----------------------------
| secret_data               |
-----------------------------
| credential_data           |
-----------------------------
| priority                  |
-----------------------------

Für uns sind hier die Felder secret- und credential_data interessant, da wir unsere Frage mit Antwort irgendwo Speichern müssen. Die Anwort wird in secret_data gespeichert, da diese Daten nicht von Keycloak übertragen werden können.

credential_data
{
  "question":"aQuestion"
}
secret_data
{
  "answer":"anAnswer"
}

Um jetzt unseren Authentifikator zu entwickeln, müssen die "Service Provider Interfaces" von Keycloak implementiert werden, da wir auch credentials benötigen müssen wir auch die dafür entsprechenden Interfaces implementieren.

Durchlassen von Nutzern die das Quiz bereits beantwortet haben
@Override
public void authenticate(AuthenticationFlowContext context) {
    if (hasCookie(context)) {
        context.success();
        return;
    }
    Response challenge = context.form().createForm("secret-question.ftl");
    context.challenge(challenge);
}

protected boolean hasCookie(AuthenticationFlowContext context) {
    Cookie cookie = context
        .getHttpRequest()
        .getHttpHeaders()
        .getCookies()
        .get("SECRET_QUESTION_ANSWERED");
    boolean result = cookie != null;
    if (result) {
        System.out.println("Bypassing secret question because cookie is set");
    }
    return result;
}
Validierung der Antwort
@Override
public void action(AuthenticationFlowContext context) {
    boolean validated = validateAnswer(context);
    if (!validated) {
        Response challenge =  context.form()
                .setError("badSecret")
                .createForm("secret-question.ftl");
        context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challenge);
        return;
    }
    setCookie(context);
    context.success();
}
Beispiel für die Syntax einer .ftl Datei
<form id="kc-totp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
    <div class="${properties.kcFormGroupClass!}">
        <div class="${properties.kcLabelWrapperClass!}">
            <label for="totp" class="${properties.kcLabelClass!}">${msg("loginSecretQuestion")}</label>
        </div>

        <div class="${properties.kcInputWrapperClass!}">
            <input id="totp" name="secret_answer" type="text" class="${properties.kcInputClass!}" />
        </div>
    </div>
</form>
  • ${} wird für verschiedenste attribute und template funktionen verwendet

    • properties.* wird aus der theme.properties datei gelesen

    • msg("someValue") steht für Werte aus den anderen .properties files

8.4. Hinzufügen des Authentifikators zu einem Flow

  • Als Erstes müssen Dateien unter resources/META-INF/services erstellt werden die, die Namen der Factory-Klassen enthalten, wie zum Beispiel:

    • org.keycloak.examples.authenticator.SecretQuestionAuthenticatorFactory

  • Es soll alles innerhalb eines einzigen jar file sein, weswegen wir jar.type=uber-jar verwenden

quarkus.package.jar.type = uber-jar
  • Mit Docker kann das ganze auf diese weiße gelöst werden

FROM quay.io/keycloak/keycloak:26.0.2 AS builder
COPY keycloak-quickstarts/extension/authenticator/target/authenticator-example.jar \
    /opt/keycloak/providers/ \ (1)
RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:26.0.2
COPY --from=builder /opt/keycloak/ /opt/keycloak/

# default entrypoint
1 Einfach das jar zu den Keycloak providern kopieren

Anschließend kann wieder in der Keycloak Admin console weiter gearbeitet werden. Um den Authentifikator nun verwenden zu können, muss zuerst ein builtin-flow dupliziert werden, da wir ihn dann wieder im Browser verwenden macht es Sinn dafür den Browser-Flow zu verwenden.

Details
duplicate flow
add flow step
binding the new browser flow

Nachdem der Subflow hinzugefügt wurde, müssen noch die Requirements angepasst werden.

Bei jedem Step der auf Required gesetzt wird muss auch unter dem "Required actions"-Tab geschaut werden, ob diese Action auch enabled ist.
Details
required actions
secret question flow

9. Reverse Proxies

Autor: Lukas Sonnleitner

9.1. Was ist ein Reverse Proxy?

Ein Reverse Proxy ist ein Server, der die Anfragen von Clients (z. B. Browsern) entgegennimmt und diese an einen oder mehrere Upstream-Server weiterleitet. Er fungiert als eine Art "Schutzschild" für Server, indem er:

  • Anonymität für die Server wahrt, da die Clients keine direkte Verbindung zu den Back-End-Servern herstellen.

  • Sicherheit erhöht, indem er als erste Verteidigungslinie gegen potenzielle Angriffe dient.

Reverse Proxy

reverse proxy

Zum Vergleich: Ein "normaler" Proxy-Server arbeitet stellvertretend für die Clients, während ein Reverse Proxy im Auftrag der Server agiert.

Proxy

proxy

9.2. Warum braucht man einen Reverse Proxy?

  • Rate Limiting: Begrenzung der Anzahl von Anfragen pro Zeitspanne, um Missbrauch zu verhindern.

  • Routing: Weiterleitung von Anfragen an spezifische Server oder Dienste basierend auf Regeln.

  • Authentication: Überprüfung von Benutzeranfragen vor der Weiterleitung.

  • Request-Validierung: Sicherstellen, dass Anfragen gültig und sicher sind.

  • Loadbalancing: Gleichmäßige Verteilung von Anfragen auf mehrere Server.

  • Caching: Zwischenspeichern von Antworten, um die Leistung zu steigern.

  • Kompression: Reduzieren der Datenmenge durch Komprimierung von Antworten.

9.3. Reverse Proxies im Überblick

Nginx Traefik Caddy Pingora

Benutzerfreundlichkeit

🟡

🟢

🟢

🔴

Configuration

.conf

.yaml
.toml
docker labels …​
Alle Configurations Optionen

Caddyfile
.json
API

Rust

TLS/Https

Manuell / Automatisiert mit Certbot

Manuell / Automatisiert mit Certbot

Automatisiert

Manuell

Kubernetes 😈

🟢

🟢

🟡 WIP

🟡 Issue

Language

c logo

go logo

go logo

rust logo

9.4. Beispiele

reverse proxy demo

9.4.1. Nginx http

docker-compose.yml
services:
  nginx:
    container_name: nginx
    image: nginx:latest
    ports:
      - 80:80
    volumes:
      - ./conf.d:/etc/nginx/conf.d:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    restart: unless-stopped

  nginx-echo:
    image: nodstuff/gecho:latest
    restart: unless-stopped
conf.d/reverse-proxy.conf
server {
    listen 80; (1)

    server_name localhost; (2)

    location /some-path/ { (3)
        proxy_set_header Host $http_host; (4)
        proxy_set_header X-Real-IP $remote_addr; (4)
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; (4)
        proxy_set_header X-Forwarded-Proto $scheme; (4)

        proxy_pass http://nginx-echo:8080/;  (5)
        # Der "/" am Ende ist wichtig. Ohne diesen würden Anfragen auf /some-path/ genau so weitergeleitet werden.
        # z.B. /some-path/i-love-turtles => /some-path/i-love-turtles
        # Mit dem "/" am Ende würde die Anfrage am Upstream Server so aussehen.
        # z.B. /some-path/i-love-turtles => /i-love-turtles
    }
}

server {
    listen 80; (1)

    server_name echo.localhost; (2)

    location / { (3)
        proxy_set_header Host $http_host; (4)
        proxy_set_header X-Real-IP $remote_addr; (4)
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; (4)
        proxy_set_header X-Forwarded-Proto $scheme; (4)

        proxy_pass http://nginx-echo:8080; (5)
    }
}
1 Port auf den der Server hört
2 Der server_name definiert, für welche Hostnamen dieser Serverblock gilt.
3 Matched den Pfad und der Anfrage
4 Überträgt die Header der ursprünglichen Request an den Upstream-Server
5 Leitet Anfragen an den Upstream-Server weiter.
nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;

include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;

    ##
    # Cache Settings
    ##

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
}

9.4.2. Traefik http

docker-compose.yml
services:
  traefik:
    container_name: traefik
    image: traefik:latest
    environment:
      - TRAEFIK_API=true
      - TRAEFIK_API_INSECURE=true
      - TRAEFIK_PROVIDERS_DOCKER=true
      - LOG_LEVEL=DEBUG
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - 80:80
      - 8080:8080
    restart: unless-stopped
    labels:
      - traefik.http.routers.traefik.rule=Host(`localhost`) (1)
      - traefik.http.services.traefik.loadbalancer.server.port=8080 (2)

  traefik-echo-1:
    image: nodstuff/gecho:latest
    restart: unless-stopped
    labels:
      - traefik.http.routers.echo-1.rule=Host(`localhost`) && PathPrefix(`/some-path`) (1)
      - traefik.http.routers.echo-1.middlewares=strip-prefix@docker (4)
      - traefik.http.middlewares.strip-prefix.stripprefix.prefixes=/some-path (3)
      - traefik.http.services.echo-1.loadbalancer.server.port=8080 (2)

  traefik-echo-2:
    image: nodstuff/gecho:latest
    restart: unless-stopped
    ports:
      - 8000:8080 (3)
    labels:
      - traefik.http.routers.echo-2.rule=Host(`echo.localhost`) (1)
      # - traefik.http.services.echo.loadbalancer.server.port=8080
      # not needed because of traefik automatic service discovery magic
1 Definiert einen Router welcher aufgrund den definierten Regeln die Request an den Container weiterleitet
2 Port auf den der Container hört
3 Definiert eine Middleware, die den Prefix /some-path von den Anfragen wegschneidet
4 Definert, dass der Router die middleware benutzen soll mit <name>@<provider>
Mehr dazu
Providers
Traefik Alternativ configuration
traefik.toml
[http.middlewares]
[http.middlewares.strip-prefix.stripPrefix]
prefixes = ["/some-path"]

Mit dieser statischen Konfiguration würde der provider statt dockerfile sein.

9.4.3. Caddy (http / https)

docker-compose.yml
services:
  caddy:
    container_name: caddy
    image: caddy:latest
    cap_add:
      - NET_ADMIN (1)
    volumes:
      - ./config:/etc/caddy
      - ./data:/data
    ports:
      - 80:80
      - 443:443
      - 443:443/udp # https://hub.docker.com/_/caddy Linux capabilities
    restart: unless-stopped

  caddy-echo:
    image: nodstuff/gecho:latest
    restart: unless-stopped
config/Caddyfile
echo.localhost { (1)
	reverse_proxy caddy-echo:8080 (3)
}

localhost { (1)
	handle /some-path*  { (2)
		uri strip_prefix /some-path (4)
		reverse_proxy caddy-echo:8080 (3)
	}
}
1 Definieren die Domains oder Hostnamen, die von diesem Block verarbeitet werden.
2 Matched den Pfad der Anfrage
3 Leitet die Anfrage an den Upstream-Server weiter
4 Schneidet den Prefix /some-path von dem Pfad der Anfrage weg

Caddy ist der einzige der 3 welcher automatisch TLS Zertifikate erstellt. In dieser Demo sind diese selber signiert also nicht von einer Cerfificate Authority ausgestellt.

9.5. TLS?

TLS (Transport Layer Security) ist ein Verschlüsselungsprotokoll, das die Sicherheit der Kommunikation im Internet gewährleistet.
Wie funktioniert der genau?

TLS Handshake

tls handshake

9.5.1. Wie bekomme ich so ein Zertifikat?

Ganz einfach. Selber machen.

openssl genrsa -out private.key 4096 (1)
openssl req -new -key private.key -out request.csr -subj "/CN=DOMAIN_NAME" (2)
openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt (3)
openssl dhparam -out dhparam.pem 4096 (4)
1 Generiert einen 4096 byte langen private key
2 Generiert eine Certificate Signing Request (Zertifikat Unterschreibungs Anfrage)
DOMAIN_NAME sollte mit der tatsächlichen Domain ausgetauscht werden.
3 Generiert das tatsächliche Zertifikat welches für 365 Tage gültig ist.
4 Generiert eine 4096 byte lange Diffi-Hellman Parameter für den sicheren Schlüsselaustausch

9.5.2. Suppa. Wie benutzt ich das jetzt?

conf.d/reverse-proxy.conf
ssl_certificate /etc/ssl/certificate.crt;
ssl_certificate_key /etc/ssl/private.key;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_protocols TLSv1.3;

server {
    listen 443 ssl;

    server_name localhost;

    location /some-path/ {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_pass http://nginx-echo:8080/;
    }
}

server {
    listen 443 ssl;

    server_name echo.localhost;

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_pass http://nginx-echo:8080;
    }
}

So schwer war des jetzt ned. Wenn man im Browser jetzt https://echo.localhost aufruft wird man von dieser wunderschönen Warnung begrüßt.

self signed warning

Was kann man dagegen tun? Nichts. Außer man benutzt Zertifikate die von sogenanten "Certifcate Authorities" ausgestellt werden.
Dafür braucht man eine Öffentliche Domain, welche auf den Server zeigt, auf den der Reverse Proxy läuft.

Ein selbstsigniertes Zertifikat bedeutet, dass der Server selbst behauptet, seine Identität und Inhalte seien vertrauenswürdig.
Es ist geeignet für Development Zwecke oder in internen Netzwerken, wenn keine CA die Validität versichern kann.

trust me bro

9.6. Was ist eine Certifcate Authority

Eine Certificate Authority (Zertifikat Authorität), ist ein Unternehmen oder eine Organisation, die die Validität von Websiten, E-Mail-Adressen etc mithilfe von Zertifikaten sicherstellt, welche an kryptografische Schlüssel gebunden sind.

  • DigiCert $26 p.M. 🤑

  • Sectigo $67 p.a. 🤑

  • Let’s Encrypt 🦁

Warum ein Zertifikat kaufen, wenn man es auch kostenlos bekommen kann?

9.7. Certbot

Certbot ist ein Open-Source-Tool, das von der Electronic Frontier Foundation (EFF) entwickelt wurde. Es dient dazu, kostenlose TLS-Zertifikate von Let’s Encrypt zu generieren und automatisch zu erneuern.
Für die bequemlichkeit benutzen wir Certbot mit docker compose.

Voraussetzungen
  • eine Domain

    • optional: eine statische IP Adresse

  • Port 80 und 443 offen

In diesem Beispiel ist DOMAIN ein platzhalter für die wirkliche Domain.

docker-compose.yml
services:
  nginx:
    container_name: nginx
    image: nginx:latest
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./conf.d:/etc/nginx/conf.d:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certbot/www:/var/www/certbot:ro
      - ./certbot/conf:/etc/letsencrypt:ro
    restart: unless-stopped

  certbot:
    profiles:
      - certbot (1)
    container_name: cerbot
    image: certbot/certbot
    volumes:
      - ./certbot/www:/var/www/certbot:rw
      - ./certbot/conf:/etc/letsencrypt:rw

  nginx-echo:
    image: nodstuff/gecho:latest
    restart: unless-stopped
1 Ich benutze hier Profiles damit bei dem docker compose up command der Certbot Container nicht mit gestartet wird.
ACME

acme challenge

  • HTTP-01 Challenge
    Certbot erstellt eine spezielle Datei auf dem Webserver, die von Let’s Encrypt überprüft wird. Diese Methode ist besonders einfach einzurichten und erfordert lediglich einen funktionierenden Reverse Proxy.

  • DNS-01 Challenge
    Hier wird ein spezieller DNS-Eintrag erstellt, den Let’s Encrypt validiert. Diese Methode ist ideal für Wildcard-Zertifikate (*.domain.tld) oder wenn der Server nicht öffentlich erreichbar ist.

conf.d/reverse-proxy.conf
server {
    listen 80;

    server_name _;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
}
nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;

include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;

    ##
    # Cache Settings
    ##

    proxy_cache_path /var/cache/nginx keys_zone=api-cache:10m;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
}

Mit docker compose up -d werden die services gestartet.

DOMAINS=DOMAIN,SUB.DOMAIN... docker compose run --rm certbot certonly \
	--webroot --webroot-path /var/www/certbot \
	--dry-run \
	-d $DOMAINS \
	--register-unsafely-without-email --agree-tos

Dieser Command startet den certbot service und testet ob die Austellung eines Zertifikates möglich ist.

DOMAINS=DOMAIN,SUB.DOMAIN... docker compose run --rm certbot certonly \
	--webroot --webroot-path /var/www/certbot \
	-d $DOMAINS \
	--register-unsafely-without-email --agree-tos

certbot get certificate

Die Zertifikate findet man unter /etc/letsencrypt/live/DOMAIN/
Ergänzen wir also die reverse-proxy.conf um https hinzuzufügen.

conf.d/reverse-proxy.conf
server {
    listen 80;

    server_name _;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;

    server_name DOMAIN;

    ssl_certificate /etc/letsencrypt/live/DOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/DOMAIN/privkey.pem;

    location /some-path/ {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_pass http://nginx-echo:8080/;  # the trailing / is important...
    }
}

server {
    listen 443 ssl;

    server_name echo.DOMAIN;

    ssl_certificate /etc/letsencrypt/live/DOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/DOMAIN/privkey.pem;

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_pass http://nginx-echo:8080;
    }
}

Starten sie den nginx Container neu und bewundern sie ihr neues Zertifikat ohne Warnung.

9.7.1. Traefik TLS

services:
  traefik:
    container_name: traefik
    image: traefik:latest
    environment:
      - TRAEFIK_API=true
      - TRAEFIK_API_INSECURE=true
      - TRAEFIK_PROVIDERS_DOCKER=true
      - LOG_LEVEL=DEBUG
      - TRAEFIK_ENTRYPOINTS_WEB_ADDRESS=:80
      - TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS=:443
      - TRAEFIK_ENTRYPOINTS_WEB_HTTP_REDIRECTIONS_ENTRYPOINT_TO=websecure (1)
      - TRAEFIK_ENTRYPOINTS_WEB_HTTP_REDIRECTIONS_ENTRYPOINT_SCHEME=https
      - TRAEFIK_ENTRYPOINTS_WEB_HTTP_REDIRECTIONS_ENTRYPOINT_PERMANENT=true (3)
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./tls.toml:/providers/tls.toml (3)
      - ./certs/certificate.crt:/etc/ssl/certificate.crt:ro
      - ./certs/private.key:/etc/ssl/private.key:ro
    ports:
      - 80:80
      - 443:443
      - 8080:8080
    restart: unless-stopped
    labels:
      - traefik.http.routers.traefik.rule=Host(`localhost`)
      - traefik.http.services.traefik.loadbalancer.server.port=8080
      - traefik.http.routers.traefik.tls=true (2)
      - traefik.http.routers.traefik.entrypoints=websecure (1)

  traefik-echo-1:
    image: nodstuff/gecho:latest
    restart: unless-stopped
    labels:
      - traefik.http.routers.echo-1.rule=Host(`localhost`) && PathPrefix(`/some-path`)
      - traefik.http.routers.echo-1.middlewares=strip-prefix@docker
      - traefik.http.middlewares.strip-prefix.stripprefix.prefixes=/some-path
      - traefik.http.services.echo-1.loadbalancer.server.port=8080
      - traefik.http.routers.echo-1.tls=true (2)
      - traefik.http.routers.echo-1.entrypoints=websecure (1)

  traefik-echo-2:
    image: nodstuff/gecho:latest
    restart: unless-stopped
    ports:
      - 8000:8080
    labels:
      - traefik.http.routers.echo-2.rule=Host(`echo.localhost`)
      - traefik.http.routers.echo-2.tls=true (2)
      - traefik.http.routers.echo-2.entrypoints=websecure (1)
      # - traefik.http.services.echo.loadbalancer.server.port=8080
      # not needed because of traefik automatic service discovery magic
1 Definiert die Einstiegspunkte (web für HTTP und websecure für HTTPS), die Traefik nutzt.
2 Erzwingt TLS (HTTPS) für den jeweiligen Router.
3 Leitet alle http anfragen zu https dauerhaft um.

9.7.2. Nginx Ingress Controller

kubernetes ingress controller

Minikube
minikube start --addons=dashboard,metrics-server
ingress-controller.yml
apiVersion: v1
kind: Namespace
metadata:
  labels:
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx
---
apiVersion: v1
automountServiceAccountToken: true
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx
  namespace: ingress-nginx
---
apiVersion: v1
automountServiceAccountToken: true
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-admission
  namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx
  namespace: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - endpoints
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - networking.k8s.io
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - networking.k8s.io
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - networking.k8s.io
    resources:
      - ingressclasses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - coordination.k8s.io
    resourceNames:
      - ingress-nginx-leader
    resources:
      - leases
    verbs:
      - get
      - update
  - apiGroups:
      - coordination.k8s.io
    resources:
      - leases
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - discovery.k8s.io
    resources:
      - endpointslices
    verbs:
      - list
      - watch
      - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-admission
  namespace: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - secrets
    verbs:
      - get
      - create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
      - namespaces
    verbs:
      - list
      - watch
  - apiGroups:
      - coordination.k8s.io
    resources:
      - leases
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - networking.k8s.io
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - networking.k8s.io
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - networking.k8s.io
    resources:
      - ingressclasses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - discovery.k8s.io
    resources:
      - endpointslices
    verbs:
      - list
      - watch
      - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-admission
rules:
  - apiGroups:
      - admissionregistration.k8s.io
    resources:
      - validatingwebhookconfigurations
    verbs:
      - get
      - update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx
  namespace: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ingress-nginx
subjects:
  - kind: ServiceAccount
    name: ingress-nginx
    namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-admission
  namespace: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ingress-nginx-admission
subjects:
  - kind: ServiceAccount
    name: ingress-nginx-admission
    namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ingress-nginx
subjects:
  - kind: ServiceAccount
    name: ingress-nginx
    namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-admission
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ingress-nginx-admission
subjects:
  - kind: ServiceAccount
    name: ingress-nginx-admission
    namespace: ingress-nginx
---
apiVersion: v1
data: null
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-controller
  namespace: ingress-nginx
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  externalTrafficPolicy: Local
  ipFamilies:
    - IPv4
  ipFamilyPolicy: SingleStack
  ports:
    - appProtocol: http
      name: http
      port: 80
      protocol: TCP
      targetPort: http
    - appProtocol: https
      name: https
      port: 443
      protocol: TCP
      targetPort: https
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: LoadBalancer
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-controller-admission
  namespace: ingress-nginx
spec:
  ports:
    - appProtocol: https
      name: https-webhook
      port: 443
      targetPort: webhook
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  minReadySeconds: 0
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/component: controller
      app.kubernetes.io/instance: ingress-nginx
      app.kubernetes.io/name: ingress-nginx
  strategy:
    rollingUpdate:
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        app.kubernetes.io/component: controller
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
        app.kubernetes.io/version: 1.12.0-beta.0
    spec:
      containers:
        - args:
            - /nginx-ingress-controller
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
            - --election-id=ingress-nginx-leader
            - --controller-class=k8s.io/ingress-nginx
            - --ingress-class=nginx
            - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
            - --validating-webhook=:8443
            - --validating-webhook-certificate=/usr/local/certificates/cert
            - --validating-webhook-key=/usr/local/certificates/key
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: LD_PRELOAD
              value: /usr/local/lib/libmimalloc.so
          image: registry.k8s.io/ingress-nginx/controller:v1.12.0-beta.0@sha256:9724476b928967173d501040631b23ba07f47073999e80e34b120e8db5f234d5
          imagePullPolicy: IfNotPresent
          lifecycle:
            preStop:
              exec:
                command:
                  - /wait-shutdown
          livenessProbe:
            failureThreshold: 5
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          name: controller
          ports:
            - containerPort: 80
              name: http
              protocol: TCP
            - containerPort: 443
              name: https
              protocol: TCP
            - containerPort: 8443
              name: webhook
              protocol: TCP
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          resources:
            requests:
              cpu: 100m
              memory: 90Mi
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              add:
                - NET_BIND_SERVICE
              drop:
                - ALL
            readOnlyRootFilesystem: false
            runAsGroup: 82
            runAsNonRoot: true
            runAsUser: 101
            seccompProfile:
              type: RuntimeDefault
          volumeMounts:
            - mountPath: /usr/local/certificates/
              name: webhook-cert
              readOnly: true
      dnsPolicy: ClusterFirst
      nodeSelector:
        kubernetes.io/os: linux
      serviceAccountName: ingress-nginx
      terminationGracePeriodSeconds: 300
      volumes:
        - name: webhook-cert
          secret:
            secretName: ingress-nginx-admission
---
apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-admission-create
  namespace: ingress-nginx
spec:
  template:
    metadata:
      labels:
        app.kubernetes.io/component: admission-webhook
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
        app.kubernetes.io/version: 1.12.0-beta.0
      name: ingress-nginx-admission-create
    spec:
      containers:
        - args:
            - create
            - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
            - --namespace=$(POD_NAMESPACE)
            - --secret-name=ingress-nginx-admission
          env:
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.4@sha256:a9f03b34a3cbfbb26d103a14046ab2c5130a80c3d69d526ff8063d2b37b9fd3f
          imagePullPolicy: IfNotPresent
          name: create
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
            readOnlyRootFilesystem: true
            runAsGroup: 65532
            runAsNonRoot: true
            runAsUser: 65532
            seccompProfile:
              type: RuntimeDefault
      nodeSelector:
        kubernetes.io/os: linux
      restartPolicy: OnFailure
      serviceAccountName: ingress-nginx-admission
---
apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-admission-patch
  namespace: ingress-nginx
spec:
  template:
    metadata:
      labels:
        app.kubernetes.io/component: admission-webhook
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
        app.kubernetes.io/version: 1.12.0-beta.0
      name: ingress-nginx-admission-patch
    spec:
      containers:
        - args:
            - patch
            - --webhook-name=ingress-nginx-admission
            - --namespace=$(POD_NAMESPACE)
            - --patch-mutating=false
            - --secret-name=ingress-nginx-admission
            - --patch-failure-policy=Fail
          env:
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.4@sha256:a9f03b34a3cbfbb26d103a14046ab2c5130a80c3d69d526ff8063d2b37b9fd3f
          imagePullPolicy: IfNotPresent
          name: patch
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
            readOnlyRootFilesystem: true
            runAsGroup: 65532
            runAsNonRoot: true
            runAsUser: 65532
            seccompProfile:
              type: RuntimeDefault
      nodeSelector:
        kubernetes.io/os: linux
      restartPolicy: OnFailure
      serviceAccountName: ingress-nginx-admission
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: nginx
spec:
  controller: k8s.io/ingress-nginx
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.12.0-beta.0
  name: ingress-nginx-admission
webhooks:
  - admissionReviewVersions:
      - v1
    clientConfig:
      service:
        name: ingress-nginx-controller-admission
        namespace: ingress-nginx
        path: /networking/v1/ingresses
        port: 443
    failurePolicy: Fail
    matchPolicy: Equivalent
    name: validate.nginx.ingress.kubernetes.io
    rules:
      - apiGroups:
          - networking.k8s.io
        apiVersions:
          - v1
        operations:
          - CREATE
          - UPDATE
        resources:
          - ingresses
    sideEffects: None
---
kubectl apply -f ingress-controller.yml
---

oder

minikube addons enable ingress
echo.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
  labels:
    name: echo
spec:
  replicas: 1
  selector:
    matchLabels:
      name: echo
  template:
    metadata:
      name: echo
      labels:
        name: echo
    spec:
      containers:
        - name: echo
          image: nodstuff/gecho:latest
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: echo-service
spec:
  selector:
    name: echo
  ports:
    - name: http
      protocol: TCP
      port: 8080
      targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress
spec:
  ingressClassName: nginx
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: echo-service
                port:
                  number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress-rewrite
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /$2 (2)
spec:
  ingressClassName: nginx
  rules:
    - http:
        paths:
          - path: /some-path(/|$)(.*) (1)
            pathType: ImplementationSpecific
            backend:
              service:
                name: echo-service
                port:
                  number: 8080
1 Mit diesem Regex werden 2 Capture Groups gematched.
z.B. /some-path(/)(i-love-turtles)
2 Mit dieser Annotation wird der Pfad überschrieben mit der zweiter Capture Group
z.B. /some-path(/)(i-love-turtles) ⇒ /i-love-turtles
kubectl apply -f echo.yml

Die externe Ip erhält man mit diesem Command.

kubectl get svc -n ingress-nginx

Der Ingress Controller sollte unter der Ip erreichbar sein.

Falls nicht muss man in das minikube Netzwerk hineintunneln mit

minikube tunnel
Leocloud

Da die Leocloud schon einen Ingress-Controller zur verfügung stellt ist es nicht nötig einen weitern zu deployen.

Es wird angenommen, dass kubectl konfiguriert ist mit der leocloud zu arbeiten.
Setup

echo.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
  labels:
    name: echo
spec:
  replicas: 1
  selector:
    matchLabels:
      name: echo
  template:
    metadata:
      name: echo
      labels:
        name: echo
    spec:
      containers:
        - name: echo
          image: nodstuff/gecho:latest
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: echo-service
spec:
  selector:
    name: echo
  ports:
    - name: http
      protocol: TCP
      port: 8080
      targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: STUDENT_ID.cloud.htl-leonding.ac.at (1)
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: echo-service
                port:
                  number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress-rewrite
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
    - host: STUDENT_ID.cloud.htl-leonding.ac.at (1)
      http:
        paths:
          - path: /some-path(/|$)(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: echo-service
                port:
                  number: 8080
1 Alle Anfragen auf STUDENT_ID.cloud.htl-leonding.ac.at werden zu dem echo-pod service weitergeleitet
Die STUDENT_ID muss mit deiner eigenen ausgetauscht werden.
Weiters ist es nicht möglich (Stand 19.12.2024) Subdomains zu benützen.
z.B. echo.STUDENT_ID.cloud.htl-leonding.ac.at

leocloud subdomain error

9.7.3. Traefik Ingress Controller

Minikube
minikube start --addons=dashboard,metrics-server
ingress-controller.yml
apiVersion: v1
kind: Namespace
metadata:
  name: traefik

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: traefik-role
  namespace: traefik

rules:
  - apiGroups:
      - ""
    resources:
      - services
      - secrets
      - nodes
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - discovery.k8s.io
    resources:
      - endpointslices
    verbs:
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io
    resources:
      - ingresses
      - ingressclasses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.io
    resources:
      - middlewares
      - middlewaretcps
      - ingressroutes
      - traefikservices
      - ingressroutetcps
      - ingressrouteudps
      - tlsoptions
      - tlsstores
      - serverstransports
      - serverstransporttcps
    verbs:
      - get
      - list
      - watch
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-account
  namespace: traefik
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: traefik-role-binding

roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-role
subjects:
  - kind: ServiceAccount
    name: traefik-account
    namespace: traefik
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: traefik-deployment
  namespace: traefik
  labels:
    app: traefik

spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-account
      containers:
        - name: traefik
          image: traefik:v3.2
          args:
            - --api.insecure
            - --providers.kubernetesingress
          ports:
            - name: web
              containerPort: 80
            - name: dashboard
              containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: traefik-dashboard-service
  namespace: traefik

spec:
  type: LoadBalancer
  ports:
    - port: 8080
      targetPort: dashboard
  selector:
    app: traefik
---
apiVersion: v1
kind: Service
metadata:
  name: traefik-web-service
  namespace: traefik

spec:
  type: LoadBalancer
  ports:
    - targetPort: web
      port: 80
  selector:
    app: traefik
kubectl apply -f ingress-controller.yml
echo.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
  labels:
    name: echo
spec:
  replicas: 1
  selector:
    matchLabels:
      name: echo
  template:
    metadata:
      name: echo
      labels:
        name: echo
    spec:
      containers:
        - name: echo
          image: nodstuff/gecho:latest
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: echo-service
spec:
  selector:
    name: echo
  ports:
    - name: http
      protocol: TCP
      port: 8080
      targetPort: 8080

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress
spec:
  ingressClassName: traefik
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: echo-service
                port:
                  number: 8080
kubectl apply -f echo.yml

Die externe Ip erhält man mit diesem Command.

kubectl get svc -n ingress-nginx

Der Ingress Controller sollte unter der Ip erreichbar sein.

Falls nicht muss man in das minikube Netzwerk hineintunneln mit

minikube tunnel

10. Quellen