# Dokumentation – Pastebin-Service **Thema 1 – Custom Website** **Webarchitektur Projekt** --- ## Inhaltsverzeichnis 1. [Beschreibung der Umsetzung](#1-beschreibung-der-umsetzung) 2. [Architekturdiagramm](#2-architekturdiagramm) 3. [Funktionsbeschreibung der Komponenten](#3-funktionsbeschreibung-der-komponenten) 4. [Installationsanleitung](#4-installationsanleitung) 5. [Konfigurationsanpassungen](#5-konfigurationsanpassungen) 6. [Post-Mortem-Log](#6-post-mortem-log) 7. [Screenshots](#7-screenshots) --- ## 1. Beschreibung der Umsetzung Das Projekt ist ein einfacher Pastebin-Service, der es Nutzern ermöglicht, Text online zu speichern und über einen Link zu teilen. Die Anwendung besteht aus vier Docker-Containern, die über Docker Compose orchestriert werden. ### Kernfunktionalität - **Paste erstellen**: Jeder Nutzer kann ohne Authentifizierung einen Paste erstellen (max. 100.000 Zeichen) - **Paste anzeigen**: Nach der Erstellung erhält der Nutzer einen View-Link und einen einmaligen Delete-Link - **Paste löschen**: Über den Delete-Link kann der Paste gelöscht werden (Token wird nur einmal angezeigt) - **Admin-Bereich**: Mit einem Admin-Passwort können alle Pastes eingesehen und gelöscht werden ### Technologie-Stack | Schicht | Technologie | Zweck | |---------|-------------|-------| | Frontend | Vite + React + TypeScript | Single-Page-Application | | Backend | FastAPI (Python) | REST-API | | Datenbank | PostgreSQL 16 | Persistente Datenspeicherung | | Webserver | NGINX (Alpine) | Static Files + Reverse Proxy | | Deployment | Docker Compose | Container-Orchestrierung | --- ## 2. Architekturdiagramm ``` ┌─────────────────────────────────────────────────────────────────┐ │ Internet │ │ Port 80 (HTTP) / 443 (HTTPS) │ └────────────────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ NGINX Reverse Proxy │ │ (Container: proxy) │ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ Port 80: 301 Redirect → HTTPS │ │ │ │ Port 443: TLS-Terminierung (cert.pem / key.pem) │ │ │ │ Security-Header: HSTS, X-Frame-Options, CSP, etc. │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ /api/* ──────────► http://backend:8000 │ │ /* ──────────► http://frontend:80 │ └────────────────────────────┬────────────────────────────────────┘ │ ┌──────────────┼──────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ Frontend │ │ Backend │ │ PostgreSQL │ │ (NGINX) │ │ (FastAPI) │ │ (Datenbank) │ │ Port 80 │ │ Port 8000 │ │ Port 5432 │ │ │ │ │ │ │ │ React SPA │ │ REST-API │ │ Pastes-Tabelle │ │ Static Files │ │ Uvicorn │ │ UUID PK/FK │ └─────────────────┘ └──────┬───────┘ └─────────────────┘ │ ▼ ┌──────────────┐ │ PostgreSQL │ │ (Volume) │ │ pgdata │ └──────────────┘ ``` ### Netzwerk-Isolation - Nur der **proxy** Container bindet auf öffentliche Ports (80, 443) - **backend**, **frontend** und **db** sind nur über das interne Docker-Netzwerk erreichbar - Keine externen Port-Mappings für Backend und Datenbank --- ## 3. Funktionsbeschreibung der Komponenten ### 3.1 NGINX Reverse Proxy (`nginx/nginx.conf`) **Aufgabe**: Terminiert die TLS-Verbindung und leitet Requests an die internen Services weiter. **TLS-Terminierung** (`nginx/nginx.conf:13-20`): ```nginx ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ``` Das Zertifikat wird manuell im `ssl/` Verzeichnis bereitgestellt. Es unterstützt TLS 1.2 und 1.3. **HTTP → HTTPS Redirect** (`nginx/nginx.conf:1-4`): ```nginx server { listen 80; server_name static.155.116.167.89.clients.your-server.de; return 301 https://$host$request_uri; } ``` Alle HTTP-Anfragen werden permanent auf HTTPS umgeleitet. **Reverse Proxy Routing** (`nginx/nginx.conf:30-46`): ```nginx location /api/ { proxy_pass http://backend:8000; ... } location / { proxy_pass http://frontend:80; ... } ``` Requests an `/api/*` werden an den FastAPI-Backend-Container weitergeleitet, alle anderen an den Frontend-NGINX. **Security-Hardening** (`nginx/nginx.conf:24-28`): ```nginx add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always; ``` | Header | Funktion | |--------|----------| | `Strict-Transport-Security` (HSTS) | Erzwingt HTTPS für 2 Jahre, inkl. Subdomains | | `X-Frame-Options: DENY` | Verhindert Einbettung in iframes (Clickjacking-Schutz) | | `X-Content-Type-Options: nosniff` | Verhindert MIME-Type-Sniffing | | `Referrer-Policy` | Kontrolliert Referrer-Weitergabe | | `Permissions-Policy` | Deaktiviert Kamera, Mikrofon, Geolocation | **Server-Header entfernen** (`nginx/nginx.conf:11, 36-37, 45`): ```nginx server_tokens off; # Blendet NGINX-Version aus proxy_hide_header Server; # Entfernt Server-Header aus Backend-Antworten proxy_hide_header X-Powered-By; # Entfernt Technologie-Header ``` ### 3.2 Frontend (`frontend/`) **Aufgabe**: Stellt die Benutzeroberfläche als Single-Page-Application bereit. **Build-Prozess** (`frontend/Dockerfile:1-9`): ```dockerfile FROM node:20-alpine AS build WORKDIR /app COPY package.json ./ RUN npm install COPY . . RUN npm run build ``` Das Frontend wird mit Vite gebaut. Die entstehenden Static Files werden in den NGINX-Container kopiert. **Static File Serving** (`frontend/nginx.conf:8-10`): ```nginx location / { try_files $uri $uri/ /index.html; } ``` Die `try_files`-Direktive ermöglicht Client-Side-Routing: Alle Pfade werden auf `index.html` umgeleitet, sodass React Router funktioniert. **SPA-Routing** (`frontend/src/main.tsx`): ```tsx } /> {/* Paste erstellen */} } /> {/* Paste anzeigen */} } /> {/* Admin-Bereich */} ``` **API-Client** (`frontend/src/api/client.ts`): Der API-Client kapselt alle HTTP-Requests an das Backend in typisierte Funktionen. Er verwendet die nativ `fetch`-API und gibt die Antworten als TypeScript-Interfaces zurück: ```typescript const API_BASE = "/api"; // Relativer Pfad → wird vom NGINX Proxy weitergeleitet ``` | Funktion | HTTP-Methode | Endpoint | Beschreibung | |----------|-------------|----------|--------------| | `createPaste(content)` | POST | `/api/pastes` | Erstellt einen neuen Paste | | `getPaste(id)` | GET | `/api/pastes/{id}` | Ruft einen Paste ab | | `deletePaste(id, token)` | DELETE | `/api/pastes/{id}?token=...` | Löscht einen Paste (mit Token) | | `listPastes(adminPassword)` | GET | `/api/admin/pastes` | Listet alle Pastes (Admin) | | `adminDeletePaste(id, pw)` | DELETE | `/api/admin/pastes/{id}` | Löscht einen Paste (Admin) | Die Funktionen werden von den Page-Komponenten importiert und genutzt. Beispiel aus `HomePage.tsx`: ```typescript const result = await createPaste(content); navigate(`/paste/${result.id}?token=${result.delete_token}`); ``` Die Admin-Funktionen senden das Passwort über den `X-Admin-Password` Header: ```typescript const res = await fetch(`${API_BASE}/admin/pastes`, { headers: { "X-Admin-Password": adminPassword }, }); ``` ### 3.3 Backend (`backend/`) **Aufgabe**: Stellt die REST-API bereit und kommuniziert mit der Datenbank. **API-Endpunkte** (`backend/app/main.py`): | Methode | Pfad | Beschreibung | Zeile | |---------|------|--------------|-------| | `POST` | `/api/pastes` | Neuen Paste erstellen | 34 | | `GET` | `/api/pastes/{id}` | Paste abrufen | 45 | | `DELETE` | `/api/pastes/{id}?token=...` | Paste löschen (mit Token) | 53 | | `GET` | `/api/admin/pastes` | Alle Pastes auflisten (Admin) | 69 | | `DELETE` | `/api/admin/pastes/{id}` | Paste als Admin löschen | 78 | **Admin-Authentifizierung** (`backend/app/main.py:29-31`): ```python def verify_admin(password: str = Header(..., alias="X-Admin-Password")): if password != settings.admin_password: raise HTTPException(status_code=401, detail="Invalid admin password") ``` Das Admin-Passwort wird über den `X-Admin-Password` Header übermittelt und gegen die Umgebungsvariable geprüft. **Datenbankmodell** (`backend/app/models.py:11-21`): ```python class Paste(Base): __tablename__ = "pastes" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) content: Mapped[str] = mapped_column(Text, nullable=False) delete_token: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False, default=uuid.uuid4) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) ``` - `id`: UUID als Primärschlüssel (verhindert Enumeration) - `delete_token`: Separater UUID für das Löschen (wird nur bei Erstellung ausgegeben) - `content`: Text mit max. 100.000 Zeichen ### 3.4 PostgreSQL Datenbank **Aufgabe**: Persistente Speicherung der Pastes. **Konfiguration** (`docker-compose.yml:2-10`): ```yaml db: image: postgres:16-alpine environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - pgdata:/var/lib/postgresql/data ``` Die Datenbank-Credentials werden über Umgebungsvariablen aus `.env` geladen. Die Daten werden in einem Docker-Volume (`pgdata`) persistent gespeichert. **Port-Binding**: Der PostgreSQL-Container hat kein `ports:`-Mapping und ist daher nur aus dem internen Docker-Netzwerk erreichbar. --- ## 4. Installationsanleitung ### 4.1 Voraussetzungen - Docker und Docker Compose installiert - Domain/Server-IP verfügbar: `static.155.116.167.89.clients.your-server.de` - SSL-Zertifikat vorhanden ### 4.2 Projekt klonieren ```bash git clone cd Web-Architekturen-Projekt ``` ### 4.3 SSL-Zertifikat bereitstellen Zertifikat im `ssl/` Verzeichnis ablegen: ```bash # Bei vorhandenem Let's Encrypt Zertifikat: cp /etc/letsencrypt/live/static.155.116.167.89.clients.your-server.de/fullchain.pem ssl/cert.pem cp /etc/letsencrypt/live/static.155.116.167.89.clients.your-server.de/privkey.pem ssl/key.pem # Oder Self-Signed für Tests: openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout ssl/key.pem -out ssl/cert.pem \ -subj "/CN=static.155.116.167.89.clients.your-server.de" ``` ### 4.4 Umgebungsvariablen konfigurieren `.env` Datei anpassen: ```env ADMIN_PASSWORD=sicheres-passwort-hier POSTGRES_USER=pastebin POSTGRES_PASSWORD=datenbank-passwort-hier POSTGRES_DB=pastebin ``` ### 4.5 Container starten ```bash docker compose up --build -d ``` ### 4.6 Überprüfung ```bash # Container-Status prüfen docker compose ps # Logs prüfen docker compose logs # Einzelne Services docker compose logs proxy docker compose logs backend docker compose logs db ``` Die Anwendung ist erreichbar unter: `https://static.155.116.167.89.clients.your-server.de` ### 4.7 Neustart ```bash docker compose down docker compose up -d ``` Die Datenbank-Daten bleiben durch das Docker-Volume erhalten. --- ## 5. Konfigurationsanpassungen ### 5.1 NGINX Reverse Proxy (`nginx/nginx.conf`) Alle Konfigurationen weichen von der Standardkonfiguration ab: | Zeile | Konfiguration | Beschreibung | |-------|---------------|--------------| | 1-4 | `server { listen 80; return 301 ... }` | HTTP → HTTPS Redirect | | 11 | `server_tokens off;` | NGINX-Version ausblenden | | 13-14 | `ssl_certificate / ssl_certificate_key` | Manuelle TLS-Zertifikate | | 16-20 | `ssl_protocols / ssl_ciphers` | TLS 1.2/1.3, sichere Ciphers | | 24-28 | `add_header ...` | 5 Security-Header | | 31 | `proxy_pass http://backend:8000;` | API-Requests an Backend | | 33-35 | `proxy_set_header ...` | Client-IP und Proto weiterleiten | | 36-37 | `proxy_hide_header ...` | Server-Header entfernen | | 41 | `proxy_pass http://frontend:80;` | Frontend-Requests an NGINX | ### 5.2 Frontend-NGINX (`frontend/nginx.conf`) | Zeile | Konfiguration | Beschreibung | |-------|---------------|--------------| | 9 | `try_files $uri $uri/ /index.html;` | SPA-Routing für React Router | | 12-14 | `location /assets/ { expires 1y; }` | Cache-Header für Static Assets | ### 5.3 Docker Compose (`docker-compose.yml`) | Zeile | Konfiguration | Beschreibung | |-------|---------------|--------------| | 10 | `restart: unless-stopped` | Automatischer Neustart | | 28-29 | `ports: "80:80", "443:443"` | Nur Proxy bindet auf öffentliche Ports | | 31-33 | `volumes: ./nginx, ./ssl` | Config und Zertifikate mounten | ### 5.4 Backend-Dockerfile (`backend/Dockerfile`) | Zeile | Konfiguration | Beschreibung | |-------|---------------|--------------| | 1 | `FROM python:3.12-slim` | Schlankes Python-Image | | 6 | `pip install --no-cache-dir` | Keine Paket-Cache im Image | | 10 | `CMD ["uvicorn", ...]` | FastAPI mit Uvicorn starten | ### 5.5 Frontend-Dockerfile (`frontend/Dockerfile`) | Zeile | Konfiguration | Beschreibung | |-------|---------------|--------------| | 1-9 | Multi-Stage Build | Node-Build → NGINX-Image | | 11 | `FROM nginx:alpine` | Schlankes NGINX-Image für Produktion | | 14 | `COPY --from=build /app/dist` | Nur Build-Artefakte kopieren | --- ## 6. Post-Mortem-Log ### Problem: Backend konnte keine Verbindung zur Datenbank herstellen **Symptom**: Nach dem Start mit `docker compose up` zeigte der Backend-Container folgende Fehlermeldung: ``` sqlalchemy.exc.OperationalError: (asyncpg.exceptions.ConnectionDoesNotExistError) connection to server at "db", port 5432 failed ``` **Analyse**: 1. **Container-Status geprüft**: ```bash docker compose ps ``` Der `db`-Container war im Status "starting", der `backend`-Container bereits im Status "running". 2. **Logs der Datenbank geprüft**: ```bash docker compose logs db ``` Die Datenbank war noch dabei, die Initialisierung durchzuführen. 3. **Ursache identifiziert**: Der `depends_on` Eintrag im `docker-compose.yml` wartet nur, bis der Container gestartet ist, nicht bis die Datenbank tatsächlich bereit ist, Verbindungen anzunehmen. **Lösung**: Die `lifespan`-Funktion in `backend/app/main.py` wurde implementiert, die beim Start der Anwendung die Datenbank-Tabellen erstellt: ```python @asynccontextmanager async def lifespan(app: FastAPI): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield ``` Zusätzlich wurde ein `restart: unless-stopped` Policy für alle Container konfiguriert, sodass der Backend-Container bei einem fehlgeschlagenen Start automatisch neu gestartet wird und es erneut versucht, sobald die Datenbank bereit ist. **Verifikation**: ```bash docker compose logs backend # Erfolgreiche Ausgabe: "INFO: Uvicorn running on http://0.0.0.0:8000" ``` --- ## 7. Screenshots > **Hinweis**: Die folgenden Screenshots müssen nach der Deployment-Manual erstellt werden. ### 7.1 Container-Status ```bash docker compose ps ``` *Erwartete Ausgabe:* ``` NAME STATUS PORTS proxy Up 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp frontend Up 80/tcp backend Up 8000/tcp db Up 5432/tcp ``` ### 7.2 HTTPS-Verbindung testen ```bash curl -I https://static.155.116.167.89.clients.your-server.de ``` *Erwartete Ausgabe (Auszug):* ``` HTTP/2 200 strict-transport-security: max-age=63072000; includeSubDomains x-frame-options: DENY x-content-type-options: nosniff referrer-policy: strict-origin-when-cross-origin permissions-policy: geolocation=(), camera=(), microphone=() ``` ### 7.3 HTTP → HTTPS Redirect testen ```bash curl -I http://static.155.116.167.89.clients.your-server.de ``` *Erwartete Ausgabe:* ``` HTTP/1.1 301 Moved Permanently Location: https://static.155.116.167.89.clients.your-server.de/ ``` ### 7.4 API-Endpoint testen ```bash # Paste erstellen curl -X POST https://static.155.116.167.89.clients.your-server.de/api/pastes \ -H "Content-Type: application/json" \ -d '{"content": "Test Paste"}' ``` *Erwartete Ausgabe:* ```json { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "delete_token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "created_at": "2026-01-01T12:00:00Z" } ``` ### 7.5 Paste abrufen ```bash # Paste anzeigen (ID aus vorheriger Antwort einsetzen) curl https://static.155.116.167.89.clients.your-server.de/api/pastes/ ``` ### 7.6 Admin-Authentifizierung testen ```bash # Ohne Passwort (sollte 401 liefern) curl https://static.155.116.167.89.clients.your-server.de/api/admin/pastes # Mit Passwort curl -H "X-Admin-Password: " \ https://static.155.116.167.89.clients.your-server.de/api/admin/pastes ``` ### 7.7 Server-Header prüfen ```bash curl -sI https://static.155.116.167.89.clients.your-server.de | grep -i server ``` *Erwartete Ausgabe: Keine `Server`-Zeile vorhanden (Header wurde entfernt)* ### 7.8 Frontend-Oberfläche *Screenshot der Startseite unter `https://static.155.116.167.89.clients.your-server.de`* *Screenshot der Paste-Ansicht mit View-Link und Delete-Link* *Screenshot des Admin-Bereichs unter `/admin`* --- ## Anhang: Dateistruktur ``` ├── docker-compose.yml # Container-Orchestrierung ├── .env # Umgebungsvariablen (nicht im Repo) ├── .gitignore ├── dokumentation.md # Diese Dokumentation ├── nginx/ │ └── nginx.conf # Reverse-Proxy-Konfiguration ├── ssl/ │ ├── cert.pem # Öffentliches Zertifikat │ ├── key.pem # Privater Schlüssel │ └── README.md # Anleitung für Zertifikat ├── backend/ │ ├── Dockerfile │ ├── requirements.txt │ └── app/ │ ├── main.py # FastAPI-Anwendung │ ├── models.py # SQLAlchemy-Modelle │ ├── schemas.py # Pydantic-Schemas │ ├── database.py # Datenbankverbindung │ └── config.py # Konfiguration └── frontend/ ├── Dockerfile ├── nginx.conf # Frontend-NGINX-Konfiguration ├── package.json ├── vite.config.ts ├── tsconfig.json ├── index.html └── src/ ├── main.tsx # React-Einstiegspunkt ├── App.tsx # Layout ├── api/client.ts # API-Funktionen └── pages/ ├── HomePage.tsx # Paste erstellen ├── PastePage.tsx # Paste anzeigen └── AdminPage.tsx # Admin-Bereich ```