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?
3. Standards
3.1. OAuth 2.0
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. |
Erklärung
-
Die Applikation fordert vom Browser, dass er den User zu Keycloak umleitet.
-
Der Browser leitet den User zu Keycloak um.
-
Keycloak authentifiziert den User, falls er noch nicht mit Keycloak authentifiziert ist.
-
Die Applikation erhält einen Autorisierungscode von Keycloak.
-
Die Applikation tauscht diesen Autorisierungscode gegen einen Access-Token von Keycloak ein.
-
Dieser Access-Token kann nun genutzt werden um auf Ressourcen auf dem Service zuzugreifen.
3.2. OIDC
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.
Erklärung
-
Die Applikation fordert vom Browser, dass er den User zu Keycloak umleitet.
-
Der Browser leitet den User zu Keycloak um.
-
Keycloak authentifiziert den User, falls er noch nicht mit Keycloak authentifiziert ist.
-
Die Applikation erhält einen Autorisierungscode von Keycloak.
-
Die Applikation tauscht diesen Autorisierungscode gegen einen ID-Token und einen Access-Token von Keycloak ein.
-
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
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
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:
{
"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.
{
"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.
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 |
404 |
|||
Open Source |
🟢 |
🔴 |
🟢 |
🟢 |
Protocol Support: OAuth 2.0 und OIDC |
🟢 |
🟢 |
🟢 |
🟢 |
Protocol Support: SAML 2.0 |
🟢 |
🟢 |
🔴 |
🟢 |
Language |
404 |
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.
realm description
4.3.2. Clients
Clients are applications and services that can request authentication of a user.
client description
4.3.3. Realm roles
Realm roles are the roles that you define for use in the current realm.
realm role 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
quarkus create app at.htl:security-keycloak-admin-client \
--extension='keycloak-admin-rest-client,rest-jackson' \
--no-code
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>
<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 namensdocker-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 imdocker-compose.yml
angegebenen Username und Passwort als Admin anmelden.
Anmeldemaske


5.5. Quarkus-Applikation mit Keycloak authentifizieren
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

-
Namen vergeben (z. B. my-realm)
-
Enabled
Option aufOn
lassen -
Auf den
Create
Button klicken
Realm erstellen

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

General Settings
-
Als
Client ID
wird in diesem Beispiel der Namequarkus-client
gewählt -
Name und Description sind optional
-
Always display in UI
aufOn
, um Client dauerhaft im UI anzuzeigen
Client erstellen - 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

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.
# 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 |
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
# 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 Users
→ Add 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. |
Grant type: client-credentials
# 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> → Credentials → Client Secret |
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
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
.
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 |
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:
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 settings
→ Themes
sein eigenes Theme auswählen.
Vorher
Nachher
Mehr zu custom Themes hier.
8. Keycloak Authentication Flows
Autor: Tobias J. Aichinger
8.1. Client Erstellung

-
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

-
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

-
OIDC CIBA (Client Initiated Backchannel Authentication) Grant
-
Erfolgt durch ein externes authentifizierungs Device des Benutzers
-
Details

-
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

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.

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:


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.
-----------------------------
| 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.
{
"question":"aQuestion"
}
{
"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.
@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;
}
@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();
}
<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



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


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.
Zum Vergleich: Ein "normaler" Proxy-Server arbeitet stellvertretend für die Clients, während ein Reverse Proxy im Auftrag der Server agiert.
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 |
Caddyfile |
Rust |
TLS/Https |
Manuell / Automatisiert mit Certbot |
Manuell / Automatisiert mit Certbot |
Automatisiert |
Manuell |
Kubernetes 😈 |
🟢 |
🟢 |
🟡 WIP |
🟡 Issue |
Language |
9.4. Beispiele
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
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
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 |
9.4.3. Caddy (http / https)
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
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?
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?
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.
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.
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.
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
|
In diesem Beispiel ist DOMAIN ein platzhalter für die wirkliche Domain.
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
-
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.
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
Die Zertifikate findet man unter /etc/letsencrypt/live/DOMAIN/
Ergänzen wir also die reverse-proxy.conf
um https hinzuzufügen.
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
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. |
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 |
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
-
Keycloak - Identity and Access Management for Modern Applications (Second Edition)
-
https://www.okta.com/de/identity-101/whats-the-difference-between-oauth-openid-connect-and-saml/
-
https://www.microsoft.com/de-de/security/business/security-101/what-is-oauth
-
https://auth0.com/de/intro-to-iam/what-is-openid-connect-oidc
-
https://medium.com/@nishada/securing-a-javascript-app-using-keycloak-263fdbbc9d02
-
https://traefik.io/glossary/reverse-proxy/ (a bissl marketing halluzinationen oba is ned so schlecht)
-
https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/
-
https://www.ssldragon.com/de/blog/wie-werde-zertifizierungs-autoritaet/
-
https://www.researchgate.net/figure/TLS-handshake-protocol_fig1_298065605
-
https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/