SEC.SI CARE

Cloud-Architektur · High-Safety

Konzept · v1.0

K8s Auto-Scaling · Global Load Balancer · Multi-Region · E2E AES-256

K8s HPA + KEDA
Global Accelerator
Failover < 1s
AES-256-GCM E2E

Region-Status · Live Preview

Frankfurtprimary
eu-central-1
Latenz24ms
Pods8
CPU-Load42%
Irelandstandby
eu-west-1
Latenz31ms
Pods3
CPU-Load12%
Amsterdamedge
eu-west-3
Anycast Edge PoP
Kein eigener Cluster

24ms

Audio-Latenz

< 1s

Failover RTO

AES-256

Verschlüsselung

99.99%

Uptime SLA

architecture · multi-region overview
# ─── ARCHITEKTUR-DIAGRAMM · GESAMTÜBERSICHT ─────────────────────

                    INTERNET
         ┌─────────────────────────────────────┐
         │  Nutzer-Device (iOS / Android / PWA) │
         │  ┌─────────────────────────────────┐ │
         │  │ AES-256-GCM verschlüsselter     │ │
         │  │ Audio-Stream (WebRTC + TURN)     │ │
         │  └──────────────┬──────────────────┘ │
         └─────────────────┼───────────────────-┘
                           │ WSS / WebRTC (TLS 1.3)
                           ▼
         ┌─────────────────────────────────────────────────┐
         │           AWS GLOBAL ACCELERATOR               │
         │         (Anycast IP — < 15ms zum Edge)          │
         └────────────────┬──────────────────┬────────────┘
                          │                  │
              ┌───────────▼──────┐  ┌────────▼──────────┐
              │  PoP Frankfurt   │  │   PoP Amsterdam    │
              │  (Primary Edge)  │  │   (Overflow Edge)  │
              └───────────┬──────┘  └────────┬──────────┘
                          │ AWS Backbone       │
                          ▼                   ▼
         ╔═══════════════════════════════════════════════╗
         ║        REGION PRIMARY: eu-central-1           ║
         ║               AWS FRANKFURT                   ║
         ║                                               ║
         ║  ┌─────────────────────────────────────────┐  ║
         ║  │     Application Load Balancer (ALB)     │  ║
         ║  │   (SSL Termination, Health Checks)       │  ║
         ║  └─────────────┬───────────┬───────────────┘  ║
         ║                │           │                   ║
         ║   ┌────────────▼──┐  ┌────▼─────────────┐    ║
         ║   │ K8s Cluster   │  │  TURN-Server Pool │    ║
         ║   │ ───────────── │  │  ─────────────────│    ║
         ║   │ Signal-Server │  │  coturn (WebRTC)  │    ║
         ║   │ Pods: 3-50    │  │  Pods: 5-20       │    ║
         ║   │ (HPA + KEDA)  │  │                   │    ║
         ║   └────────────┬──┘  └────────────────── ┘    ║
         ║                │                               ║
         ║   ┌────────────▼──────────────────────────┐   ║
         ║   │          Redis Cluster (Primary)       │   ║
         ║   │    Session State · GPS · Audio Meta    │   ║
         ║   └────────────────────────────────────────┘   ║
         ║                │                               ║
         ║   ┌────────────▼──────────────────────────┐   ║
         ║   │        PostgreSQL (RDS Multi-AZ)       │   ║
         ║   │      Alarms · Users · Audit-Logs       │   ║
         ║   └────────────────────────────────────────┘   ║
         ╚══════════════════╦════════════════════════════╝
                            ║ Async Replication (< 50ms)
         ╔══════════════════╩════════════════════════════╗
         ║         REGION BACKUP: eu-west-1              ║
         ║                AWS IRELAND                    ║
         ║         (Identische Infrastruktur)            ║
         ║         STANDBY — Failover < 1 Sekunde        ║
         ╚════════════════════════════════════════════════╝

  LATENZ-BUDGET (Europa):
  Anycast Edge:     5-15ms
  ALB Frankfurt:    5-10ms
  Signal-Pod:        2-5ms
  WebRTC TURN:      3-8ms
  Guardian-Device:  5-15ms
  ───────────────────────
  GESAMT:         20-53ms  ✅  Ziel: < 100ms

3

Min. Pods (HA)

50

Max. Pods (Peak)

60%

CPU Scale-Trigger

HPA: Skaliert bei CPU >60% oder >3000 WebSocket-Verbindungen pro Pod.

KEDA Cron: Freitag 22:00 Uhr — 20 Pods pre-gestartet, bevor Wochenend-Last kommt.

PodAntiAffinity: Pods verteilt auf verschiedene Nodes — ein Node-Ausfall = kein Ausfall.

yaml · k8s-hpa + keda-scaledobject
# ─── MODUL 1: KUBERNETES AUTO-SCALING · SIGNAL-SERVER ──────────
# HPA (Horizontal Pod Autoscaler) + KEDA für WebSocket-Metriken

# ── signal-server-deployment.yaml ───────────────────────────────
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secsi-signal-server
  namespace: secsi-prod
spec:
  replicas: 3                        # Minimum: 3 Pods (HA)
  selector:
    matchLabels:
      app: signal-server
  template:
    spec:
      containers:
      - name: signal-server
        image: secsi/signal-server:latest
        resources:
          requests:
            cpu: "500m"
            memory: "512Mi"
          limits:
            cpu: "2000m"
            memory: "2Gi"
        env:
        - name: REGION
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['topology.kubernetes.io/region']
        - name: MAX_WEBSOCKET_CONNS
          value: "5000"              # Pro Pod max. 5000 WS-Verbindungen
      affinity:
        podAntiAffinity:             # Pods auf verschiedene Nodes verteilen
          requiredDuringScheduling:
            - labelSelector:
                matchLabels: {app: signal-server}
              topologyKey: kubernetes.io/hostname

---
# ── HPA: CPU + Custom WebSocket Metric ──────────────────────────
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: signal-server-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: secsi-signal-server
  minReplicas: 3
  maxReplicas: 50                    # Wochenende Nacht: bis zu 50 Pods
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60       # Scale-out bei >60% CPU
  - type: External
    external:
      metric:
        name: websocket_active_connections
      target:
        type: AverageValue
        averageValue: "3000"         # Scale-out bei >3000 WS/Pod

---
# ── KEDA ScaledObject: Cron-basiertes Pre-Scaling ────────────────
# Wochenende Nacht vorausschauend skalieren (bevor Last kommt)
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: signal-server-weekend-scaler
spec:
  scaleTargetRef:
    name: secsi-signal-server
  triggers:
  - type: cron
    metadata:
      timezone: Europe/Berlin
      start: "0 22 * * 5"           # Freitag 22:00 Uhr
      end:   "0 5  * * 1"           # Montag  05:00 Uhr
      desiredReplicas: "20"         # Wochenende: 20 Pods bereit
  - type: cron
    metadata:
      start: "0 5 * * 1"
      end:   "0 22 * * 5"
      desiredReplicas: "3"          # Wochentag: 3 Pods
User-Device → Anycast Edge5–15ms
Edge → ALB Frankfurt5–10ms
ALB → Signal-Pod2–5ms
WebRTC ICE → TURN3–8ms
TURN → Guardian-Device5–15ms
Gesamt End-to-End20–53ms ✅
yaml · global-accelerator + turn-config
# ─── MODUL 2: GLOBAL LOAD BALANCER · ANYCAST + EDGE ────────────
# Ziel: Audio-Stream Latenz < 100ms überall in Europa
# Technologie: AWS Global Accelerator + CloudFront + ALB

# ── Global Accelerator (Anycast IP) ─────────────────────────────
# Zwei statische Anycast-IPs — DNS löst zur nächsten Edge auf

AWS::GlobalAccelerator::Accelerator:
  Name: secsi-global
  IpAddressType: IPV4
  Enabled: true

AWS::GlobalAccelerator::Listener:
  Protocol: TCP
  PortRanges:
    - FromPort: 443    # HTTPS / WSS
      ToPort:   443
    - FromPort: 3478   # STUN/TURN für WebRTC Audio
      ToPort:   3479

AWS::GlobalAccelerator::EndpointGroup:
  # Region 1: Frankfurt (Primary)
  - EndpointGroupRegion: eu-central-1
    TrafficDialPercentage: 100
    HealthCheckProtocol: TCP
    HealthCheckPort: 443
    ThresholdCount: 2              # 2 Fehler → Failover
    Endpoints:
      - EndpointId: arn:aws:elasticloadbalancing:eu-central-1:...
        Weight: 100

  # Region 2: Ireland (Backup / Overflow)
  - EndpointGroupRegion: eu-west-1
    TrafficDialPercentage: 0       # Normalfall: kein Traffic
    ThresholdCount: 2
    Endpoints:
      - EndpointId: arn:aws:elasticloadbalancing:eu-west-1:...
        Weight: 100

# ── WebRTC TURN-Server: Geo-Routing ─────────────────────────────
# ICE-Kandidaten liefern immer den nächsten TURN-Server

TURN_CONFIG:
  servers:
    - url: "turn:fra.turn.sec-si.de:3478"   # Frankfurt
      region: eu-central-1
      priority: 1
    - url: "turn:dub.turn.sec-si.de:3478"   # Dublin
      region: eu-west-1
      priority: 2
    - url: "turn:ams.turn.sec-si.de:3478"   # Amsterdam
      region: eu-west-3
      priority: 3

  # NGINX Stream-Proxy für UDP/TCP Load Balancing
  nginx_stream:
    upstream turn_backends:
      least_conn;                   # Least-Connection für Streams
      server turn1.internal:3478 weight=10;
      server turn2.internal:3478 weight=10;
      server turn3.internal:3478 weight=10;

# ── Latenz-Budget: User → Audio beim Partner ────────────────────
#
#   User-Device ──(Anycast)──► Edge PoP:          5-15ms
#   Edge PoP ────(AWS Backbone)─► ALB Frankfurt:  5-10ms
#   ALB ────────────────────────► Signal-Pod:      2-5ms
#   Signal-Pod ──(WebRTC ICE)───► TURN:            3-8ms
#   TURN ─────────────────────── Guardian:         5-15ms
#                                                ─────────
#   GESAMT:                                     20-53ms  ✅ < 100ms

Global Accelerator statt DNS: Failover in <1s — kein DNS-TTL-Warten.

Redis Cross-Region: Session-State async repliziert (<50ms Lag) — Client reconnectet nahtlos.

Health-Check: 1 Fehler genügt für Failover (nicht erst nach 3) — maximale Reaktionsgeschwindigkeit.

bash + pseudocode · multi-region-failover
# ─── MODUL 3: MULTI-REGION REDUNDANZ · 1-SEKUNDEN-FAILOVER ────
# Primär: AWS Frankfurt (eu-central-1)
# Backup:  AWS Ireland  (eu-west-1)
# RTO: < 1 Sekunde · RPO: 0 (kein Datenverlust)

# ── Route 53 Health Checks + Failover Policy ─────────────────────
aws route53 create-health-check   --caller-reference "secsi-fra-$(date +%s)"   --health-check-config '{
    "Type":                   "HTTPS",
    "FullyQualifiedDomainName": "api-fra.sec-si.de",
    "Port":                   443,
    "RequestInterval":        10,        # Check alle 10s
    "FailureThreshold":       1,         # 1 Fehler = sofort Failover
    "EnableSNI":              true,
    "Regions":                ["eu-central-1","eu-west-1","ap-southeast-1"]
  }'

# ── DNS Failover Records ─────────────────────────────────────────
# Primary → Frankfurt
aws route53 change-resource-record-sets   --change-batch '{
    "Changes": [{
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name":          "api.sec-si.de",
        "Type":          "A",
        "Failover":      "PRIMARY",
        "SetIdentifier": "Frankfurt",
        "HealthCheckId": "HEALTH-CHECK-FRA-ID",
        "AliasTarget": {
          "HostedZoneId":         "Z215JYRZR1TBD5",
          "DNSName":              "secsi-alb-fra.eu-central-1.elb.amazonaws.com",
          "EvaluateTargetHealth": true
        }
      }
    }]
  }'

# Secondary → Ireland (automatisch aktiv wenn Primary down)
# (identische Config mit Failover: "SECONDARY" + Ireland ALB)

# ── Active Session Migration bei Failover ────────────────────────
# Problem: WebSocket-Verbindungen brechen bei Failover ab
# Lösung: Session-State in Redis Cluster (Cross-Region Replication)

Redis:
  Primary:   redis-fra.sec-si.internal   # Frankfurt
  Replica:   redis-dub.sec-si.internal   # Dublin (async Replikation)
  ReplicationLag: < 50ms

SESSION_MIGRATION_PROTOCOL:
  1. Health-Check schlägt fehl → Route 53 schaltet um (≈ 5-10s DNS TTL!)
     → LÖSUNG: AWS Global Accelerator (kein DNS) → < 1s Umschaltung

  2. Client-SDK erkennt WS-Disconnect:
     ws.onclose = () => {
       reconnect({ servers: FALLBACK_ENDPOINTS, maxRetries: 20 })
       // Session-Token im Client gespeichert → nahtlose Wiederverbindung
     }

  3. Ireland-Server lädt Session aus Redis-Replica:
     session = await redis.get("session:" + token)
     // Alle Alarm-Daten, GPS, Audio-Buffer sofort verfügbar

  FAILOVER-ZEITLINIE:
    T+0ms:   Frankfurt-ALB antwortet nicht
    T+200ms: Global Accelerator erkennt Ausfall (2x Health-Check)
    T+300ms: Traffic-Switch zu Ireland-Endpoint
    T+500ms: Client-SDK reconnect, Session aus Redis geladen
    T+800ms: Audio-Stream aktiv auf Ireland-TURN-Server
    GESAMT:  < 1 Sekunde ✅

Zero-Knowledge Server

ECDH P-384 Key-Exchange — Server sieht nur verschlüsselte Pakete. Selbst wir als Betreiber können nicht mithören.

Emergency Protocol

2-of-3 Shamir Secret Sharing — Entschlüsselung nur mit richterlichem Beschluss + 2 Key-Custodians.

pseudocode · ecdh + aes-256-gcm + shamir
# ─── MODUL 4: END-TO-END VERSCHLÜSSELUNG · AES-256-GCM ─────────
# Ziel: Selbst Server-Operator kann Audio NICHT mithören
# Ausnahme: Emergency Protocol (richterliche Anordnung oder aktiver SOS)

# ── Key-Exchange: ECDH P-384 (Perfect Forward Secrecy) ───────────

CLIENT_SETUP (beim Session-Start):
  # 1. Ephemeral Keypair generieren (nie persistent gespeichert!)
  clientPrivKey, clientPubKey = crypto.generateKeyPair("P-384")

  # 2. Öffentlichen Key an Guardian senden (via Signal-Server)
  Signal.send({
    type:       "KEY_EXCHANGE_INIT",
    public_key: clientPubKey.export("base64"),
    session_id: SESSION.id
  })

GUARDIAN_RESPONSE:
  guardianPrivKey, guardianPubKey = crypto.generateKeyPair("P-384")

  # 3. ECDH Shared Secret ableiten
  sharedSecret = ECDH.derive(guardianPrivKey, clientPubKey)

  # 4. AES-256-GCM Key aus SharedSecret ableiten (HKDF)
  sessionKey = HKDF(
    inputKeyMaterial: sharedSecret,
    salt:             SESSION.id + now(),
    info:             "secsi-audio-v1",
    length:           32               # 256 Bit
  )

  Signal.send({
    type:       "KEY_EXCHANGE_COMPLETE",
    public_key: guardianPubKey.export("base64")
  })

CLIENT_FINALIZE:
  sessionKey = HKDF(ECDH.derive(clientPrivKey, guardianPubKey), ...)
  # Beide Seiten haben denselben sessionKey — Server hat ihn NIE gesehen!

# ── Audio Encryption: AES-256-GCM ────────────────────────────────

AUDIO_ENCRYPTION (pro Audio-Chunk, alle ~20ms):
  IV = crypto.randomBytes(12)          # 96-Bit IV — NIEMALS wiederverwenden

  encryptedChunk = AES_256_GCM.encrypt(
    key:          sessionKey,
    iv:           IV,
    plaintext:    audioChunk,
    aad:          JSON.stringify({     # Authenticated Additional Data
      session_id: SESSION.id,
      seq:        chunkSequenceNumber, # Replay-Schutz
      ts:         now()
    })
  )

  # Paket = IV (12B) + AuthTag (16B) + EncryptedAudio (variabel)
  packet = IV + encryptedChunk.authTag + encryptedChunk.ciphertext
  WebRTC.send(packet)

AUDIO_DECRYPTION (Guardian-Seite):
  IV            = packet.slice(0, 12)
  authTag       = packet.slice(12, 28)
  ciphertext    = packet.slice(28)

  plainAudio = AES_256_GCM.decrypt(
    key:      sessionKey,
    iv:       IV,
    authTag:  authTag,
    aad:      ...,
    input:    ciphertext
  )
  # TAMPERING: Wenn authTag falsch → Exception → Paket verworfen

# ── Emergency Protocol: Temporäre Entschlüsselung ────────────────
# Nur bei aktivem SOS + richterlicher Anordnung (§100a StPO)

EMERGENCY_DECRYPT_PROTOCOL:
  # Session-Key wird mit 2-of-3 Shamir Secret Sharing gesichert
  # Drei Key-Custodians: CEO + CTO + Externer Datenschutzbeauftragter
  # Mindestens 2 müssen zustimmen

  keyShares = ShamirSecretSharing.split(
    secret:    sessionKey,
    shares:    3,
    threshold: 2
  )

  # Shares gespeichert bei:
  share_1 → HSM (Hardware Security Module) Frankfurt
  share_2 → HSM Ireland
  share_3 → Externer Treuhänder (Notar / Datenschützer)

  # Rekonstruktion nur mit gerichtlichem Beschluss + 2 Custodians
  sessionKey = ShamirSecretSharing.combine([share_1, share_2])
POST

/emergency/sync

GET

/emergency/session

POST

/emergency/audio/signal

WSS

/ws/emergency/{id}

yaml · emergency-sync-api-spec
# ─── EMERGENCY_SYNC API-SPEZIFIKATION ──────────────────────────
# Alle Endpunkte: TLS 1.3 · Bearer JWT · Rate-Limit 1000 req/min

# ═══════════════════════════════════════════════════════════════
# POST /api/v2/emergency/sync
# Haupt-Sync-Endpunkt: Session-State zwischen Regionen
# ═══════════════════════════════════════════════════════════════
REQUEST:
  Authorization: Bearer {session_jwt}
  Content-Type:  application/json
  {
    "session_id":    "ses_abc123",
    "alert_id":      "alr_xyz789",
    "event_type":    "sos_triggered" | "gps_update" | "audio_chunk" | "heartbeat",
    "payload": {
      "gps":         { "lat": 48.137, "lng": 11.575, "accuracy_m": 8 },
      "audio_meta":  { "codec": "opus", "bitrate": 24000, "encrypted": true },
      "escalation":  "intervention",
      "timestamp":   "2026-03-11T22:14:33.123Z"
    },
    "integrity_hmac": "sha256=abc123..."   # HMAC über payload+ts
  }

RESPONSE 200:
  {
    "synced":        true,
    "region":        "eu-central-1",
    "failover_ready": true,           # Backup-Region hat State
    "latency_ms":    18,
    "next_heartbeat": 5000            # ms bis nächster Heartbeat erwartet
  }

RESPONSE 503 (Region down → Failover):
  Retry-After: 0                      # Sofort zu Backup
  X-Failover-Endpoint: https://api-dub.sec-si.de/api/v2/emergency/sync

# ═══════════════════════════════════════════════════════════════
# GET /api/v2/emergency/session/{session_id}
# Session-State abrufen (für Failover-Reconnect)
# ═══════════════════════════════════════════════════════════════
RESPONSE 200:
  {
    "session_id":    "ses_abc123",
    "status":        "active",
    "alert_id":      "alr_xyz789",
    "user_id":       "usr_111",
    "guardians":     ["guardian@sec-si.de"],
    "last_gps":      { "lat": 48.137, "lng": 11.575 },
    "escalation":    "intervention",
    "encryption": {
      "algorithm":   "AES-256-GCM",
      "pfs":         true,
      "key_id":      "key_2026_03_11"  # Nur Key-Referenz, nie Key selbst!
    },
    "region_primary":  "eu-central-1",
    "region_backup":   "eu-west-1",
    "created_at":    "2026-03-11T22:00:00Z",
    "last_sync_at":  "2026-03-11T22:14:33Z"
  }

# ═══════════════════════════════════════════════════════════════
# POST /api/v2/emergency/audio/signal
# WebRTC Signaling für Audio-Kanal (SDP Exchange)
# ═══════════════════════════════════════════════════════════════
REQUEST:
  {
    "session_id":  "ses_abc123",
    "signal_type": "offer" | "answer" | "ice_candidate",
    "sdp":         "v=0
o=- ...",    # SDP für offer/answer
    "candidate":   { "candidate": "...", "sdpMid": "audio" }
  }

RESPONSE 200:
  { "forwarded": true, "to_peer": "guardian@sec-si.de" }

# ═══════════════════════════════════════════════════════════════
# POST /api/v2/emergency/escalate
# Eskalation: Pre-Alarm → Validation → Intervention → Police
# ═══════════════════════════════════════════════════════════════
REQUEST:
  {
    "session_id":   "ses_abc123",
    "from_level":   "validation",
    "to_level":     "intervention",
    "trigger":      "no_checkin" | "audio_score" | "guardian_manual" | "duress_pin",
    "auto_police":  false             # true = 110 automatisch auslösen
  }

RESPONSE 200:
  {
    "new_level":         "intervention",
    "notified":          ["guardian@sec-si.de", "leitstelle@sec-si.de"],
    "police_dispatched": false,
    "eta_police_s":      null
  }

# ═══════════════════════════════════════════════════════════════
# WebSocket: wss://api.sec-si.de/ws/emergency/{session_id}
# Bi-direktionaler Echtzeit-Kanal für alle Events
# ═══════════════════════════════════════════════════════════════
EVENTS CLIENT → SERVER:
  { "type": "GPS_UPDATE",     "gps": {...} }
  { "type": "HEARTBEAT",      "ts": "..." }
  { "type": "AUDIO_READY",    "turn_candidates": [...] }
  { "type": "ALARM_CANCEL",   "pin_type": "normal" | "duress" }

EVENTS SERVER → CLIENT:
  { "type": "GUARDIAN_JOINED",   "guardian": "..." }
  { "type": "ESCALATION_CHANGE", "level": "intervention" }
  { "type": "ALIBI_CALL",        "payload": {...} }
  { "type": "FAILOVER",          "endpoint": "wss://api-dub.sec-si.de/..." }
  { "type": "SESSION_CLOSED",    "reason": "resolved" }

Compliance & Zertifizierungen (Ziel)

ISO 27001

ISMS — Informationssicherheits-Managementsystem

DSGVO Art. 32

Technisch-organisatorische Maßnahmen (TOMs)

BSI IT-Grundschutz

Kritische Infrastruktur — KRITIS-relevant

SOC 2 Type II

Audit: Security, Availability, Confidentiality

TLS 1.3 only

Kein TLS 1.2 — nur aktuelle Cipher-Suites

§100a StPO

Notfall-Entschlüsselung nur mit Gerichtsbeschluss

SEC.SI GERMANY · Cloud Architecture v1.0 · Confidential

SEC.SI CARE — powered by SEC.SI GERMANY

SEC.SI GERMANY

GESUNDHEIT IST SEC.SI.