250 lines
6.2 KiB
TeX
Executable File
250 lines
6.2 KiB
TeX
Executable File
\documentclass[a4paper,11pt]{article}
|
||
|
||
\usepackage[utf8]{inputenc}
|
||
\usepackage[T1]{fontenc}
|
||
\usepackage{geometry}
|
||
\usepackage{hyperref}
|
||
\usepackage{graphicx}
|
||
\usepackage{longtable}
|
||
\usepackage{textcomp}
|
||
|
||
\geometry{margin=2.5cm}
|
||
|
||
\title{Service-Portal Architektur}
|
||
\author{schooltech.ch}
|
||
\date{\today}
|
||
|
||
\begin{document}
|
||
\maketitle
|
||
|
||
\section{Zielsetzung}
|
||
Ziel ist der Aufbau eines zentralen Login- und Navigations-Portals für mehrere unabhängige Web-Services,
|
||
die in Docker-Containern betrieben werden.
|
||
Benutzer authentifizieren sich einmal zentral und können anschließend zwischen Services wechseln,
|
||
die jeweils unter eigenen Subdomains bereitgestellt werden.
|
||
|
||
\section{Architekturübersicht (final)}
|
||
|
||
\subsection{Domänenstruktur}
|
||
\begin{itemize}
|
||
\item \texttt{server.schooltech.ch} – Login- und Navigations-Portal (SPA)
|
||
\item \texttt{<service>.server.schooltech.ch} – einzelne Services (Docker Container)
|
||
\end{itemize}
|
||
|
||
\subsection{High-Level Architektur}
|
||
\begin{verbatim}
|
||
Internet
|
||
|
|
||
| HTTPS (*.server.schooltech.ch)
|
||
v
|
||
+---------------------+
|
||
| Nginx ReverseProxy |
|
||
| TLS, Auth, Routing |
|
||
+---------------------+
|
||
| |
|
||
| +--> Service Container (abc)
|
||
| abc.server.schooltech.ch
|
||
|
|
||
+--> Portal / Auth Service
|
||
server.schooltech.ch
|
||
\end{verbatim}
|
||
|
||
\subsection{Zentrale Prinzipien}
|
||
\begin{itemize}
|
||
\item Ein Einstiegspunkt (Nginx)
|
||
\item Zentrale Authentifikation (auth\_request)
|
||
\item Services kennen kein Login
|
||
\item Keine iFrames, kein Subpath-Rewrite
|
||
\item Services laufen auf Root-Pfad (\texttt{/})
|
||
\end{itemize}
|
||
|
||
\section{Authentifikation (Variante A: auth\_request)}
|
||
|
||
\subsection{Prinzip}
|
||
Nginx prüft jeden Request zu einem Service mittels \texttt{auth\_request} gegen den Auth-Service.
|
||
Der Auth-Service entscheidet ausschließlich anhand der Session (Cookie).
|
||
|
||
\subsection{Ablauf}
|
||
\begin{enumerate}
|
||
\item Benutzer loggt sich am Portal ein
|
||
\item Portal setzt Session-Cookie:
|
||
\begin{verbatim}
|
||
Domain=.server.schooltech.ch
|
||
Secure; HttpOnly; SameSite=None
|
||
\end{verbatim}
|
||
\item Benutzer ruft Service-Subdomain auf
|
||
\item Nginx fragt \texttt{/internal/auth} beim Auth-Service an
|
||
\item Bei Erfolg: Request wird an Service weitergeleitet
|
||
\end{enumerate}
|
||
|
||
\subsection{Header-Weitergabe}
|
||
Optional injiziert Nginx folgende Header:
|
||
\begin{itemize}
|
||
\item \texttt{X-Remote-User}
|
||
\item \texttt{X-User-Roles}
|
||
\end{itemize}
|
||
|
||
\section{Nginx Konfiguration}
|
||
|
||
\subsection{Wildcard Server Block}
|
||
\begin{verbatim}
|
||
server {
|
||
listen 443 ssl;
|
||
server_name *.server.schooltech.ch;
|
||
|
||
ssl_certificate /etc/letsencrypt/live/server.schooltech.ch/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/server.schooltech.ch/privkey.pem;
|
||
|
||
location / {
|
||
auth_request /internal/auth;
|
||
proxy_pass http://$upstream;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
}
|
||
|
||
location = /internal/auth {
|
||
proxy_pass http://auth:3000/internal/auth;
|
||
proxy_pass_request_body off;
|
||
proxy_set_header Content-Length "";
|
||
proxy_set_header X-Original-URI $request_uri;
|
||
}
|
||
}
|
||
\end{verbatim}
|
||
|
||
\subsection{Upstream Mapping}
|
||
\begin{verbatim}
|
||
map $host $upstream {
|
||
default portal:3000;
|
||
abc.server.schooltech.ch abc:8080;
|
||
xyz.server.schooltech.ch xyz:8080;
|
||
}
|
||
\end{verbatim}
|
||
|
||
\section{Docker Compose (Übersicht)}
|
||
|
||
\begin{verbatim}
|
||
version: "3.9"
|
||
|
||
services:
|
||
nginx:
|
||
image: nginx:alpine
|
||
ports:
|
||
- "443:443"
|
||
volumes:
|
||
- ./nginx:/etc/nginx/conf.d
|
||
- ./certs:/etc/letsencrypt
|
||
depends_on:
|
||
- auth
|
||
- portal
|
||
networks:
|
||
- internal
|
||
|
||
auth:
|
||
image: node:18
|
||
command: node auth.js
|
||
networks:
|
||
- internal
|
||
|
||
portal:
|
||
image: node:18
|
||
command: node portal.js
|
||
networks:
|
||
- internal
|
||
|
||
abc:
|
||
image: service-abc
|
||
networks:
|
||
- internal
|
||
|
||
networks:
|
||
internal:
|
||
driver: bridge
|
||
\end{verbatim}
|
||
|
||
\section{UI-Konzept (Navigations-Portal)}
|
||
|
||
\subsection{Funktion}
|
||
\begin{itemize}
|
||
\item Login
|
||
\item Anzeige verfügbarer Services
|
||
\item Wechsel per Full Navigation
|
||
\item Kollabierbare Kopfzeile
|
||
\item Floating Launcher (Bookmark)
|
||
\end{itemize}
|
||
|
||
\subsection{UI-Layout}
|
||
\begin{verbatim}
|
||
+------------------------------------------------+
|
||
| LOGO | Service A | Service B | User |
|
||
+------------------------------------------------+
|
||
| |
|
||
| Service Overview / Last Service / Status |
|
||
| |
|
||
+------------------------------------------------+
|
||
\end{verbatim}
|
||
|
||
\subsection{HTML-Prototyp (Portal)}
|
||
\begin{verbatim}
|
||
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>Service Portal</title>
|
||
<style>
|
||
header {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0;
|
||
height: 60px;
|
||
backdrop-filter: blur(6px);
|
||
background: rgba(0,0,0,0.3);
|
||
color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 20px;
|
||
}
|
||
main {
|
||
padding-top: 80px;
|
||
}
|
||
.service {
|
||
display: inline-block;
|
||
margin: 10px;
|
||
padding: 20px;
|
||
border: 1px solid #ccc;
|
||
cursor: pointer;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<strong>Portal</strong>
|
||
</header>
|
||
<main>
|
||
<div class="service" onclick="go('abc')">Service ABC</div>
|
||
<div class="service" onclick="go('xyz')">Service XYZ</div>
|
||
</main>
|
||
|
||
<script>
|
||
function go(name) {
|
||
localStorage.setItem("lastService", name);
|
||
window.location.href = "https://" + name + ".server.schooltech.ch";
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
\end{verbatim}
|
||
|
||
\subsection{Portal als Docker Container}
|
||
Das Portal wird als eigener Container betrieben und statisch oder via Node.js ausgeliefert.
|
||
Es besitzt keine direkte Kopplung zu den Services außer über URLs.
|
||
|
||
\section{Zusammenfassung}
|
||
Diese Architektur ermöglicht:
|
||
\begin{itemize}
|
||
\item saubere Trennung von Portal und Services
|
||
\item zentrale Sicherheit
|
||
\item einfache Erweiterbarkeit
|
||
\item wartbare Reverse-Proxy-Konfiguration
|
||
\end{itemize}
|
||
|
||
\end{document}
|