Files
factory/docs/adr/20260407-network-architecture.md
T
arcodangeandClaude Opus 5 b92da95701 docs(adr) — stockage objet MinIO : qui déclare quoi, et qui détient quoi
Trois questions indépendantes, tranchées lors du branchement de Kadans sur
MinIO (2026-07-26) : qui déclare les buckets d'une app, qui détient les
identifiants capables de les créer, et comment l'app lit les siens.

La décision de fond est du fondateur : CHACUN SON PÉRIMÈTRE. Une application
déclare ses buckets depuis son propre dépôt ; `tools` fournit le serveur, un
module de standardisation et un compte de provisionnement — pas la liste. Une
première version faisait tout porter par l'infra partagée : à ce rythme, chaque
bucket de chaque app devenait une PR sur le dépôt commun.

L'ADR consigne aussi les trois identités et leurs portées (root / provisionneur
/ compte de service), pourquoi la lecture des identifiants est une propriété
inconditionnelle de la plateforme plutôt qu'une déclaration par app, et pourquoi
les octets ne transitent pas par l'API — avec les conséquences que ça impose
(endpoint public, CORS aux origines exactes, pas de basic-auth sur l'ingress S3).

Les alternatives écartées sont listées avec leur motif, dont deux que j'avais
moi-même proposées et qui étaient plus faibles.

Deux limites assumées y figurent : le provisionneur est un secret PARTAGÉ entre
rôles CI (sa compromission permet de créer des buckets, pas de lire des objets),
et les noms d'actions d'administration MinIO n'ont pas été éprouvés contre le
serveur au moment d'écrire.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 10:19:48 +02:00

10 KiB

ADR 20260407: Network Architecture

Status

Proposed

Context

The home lab requires a secure and resilient network architecture to support:

  • Internal services (.lab domain).
  • External services (.arcodange.fr domain).
  • DNS resolution and ad-blocking (Pi-hole).
  • TLS certificate management (Step CA).
  • Ingress routing (Traefik).
  • CDN and DDoS protection (Cloudflare).

Decision

We will implement a multi-layered network architecture with the following components:

1. External Layer (Internet)

  • Cloudflare: CDN, DDoS protection, and DNS for .arcodange.fr.
  • DuckDNS: Dynamic DNS for external access.
  • Livebox: ISP-provided gateway (NAT, DHCP, firewall).

2. Internal Layer (Home Lab)

  • Pi-hole (pi1, pi3): DNS sinkhole for ad-blocking and internal DNS resolution.
  • Step CA (pi1): Internal certificate authority for .lab domain.
  • Traefik (k3s): Ingress controller with TLS termination.
  • k3s Cluster: Hosts internal services with Longhorn storage.

3. DNS Architecture

  • Pi-hole: Primary DNS for internal clients.
    • Forwards .lab queries to Step CA.
    • Forwards external queries to Cloudflare (1.1.1.1).
  • Step CA: Issues certificates for .lab services.
  • Cloudflare: Manages .arcodange.fr DNS records.

4. Ingress and TLS

  • Traefik: Terminates TLS for both .lab and .arcodange.fr domains.
    • Uses Let's Encrypt for .arcodange.fr.
    • Uses Step CA for .lab.
  • Helm Chart Annotations:
    • traefik.ingress.kubernetes.io/router.entrypoints: websecure
    • traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
    • traefik.ingress.kubernetes.io/router.middlewares: localIp@file

5. Security

  • Cloudflare Tunnel: Securely exposes internal services without port forwarding.
  • CrowdSec: Intrusion detection and banning.
  • Traefik Middlewares: IP filtering, rate limiting, and authentication.
  • Cloudflare Turnstile: CAPTCHA protection for public-facing services.

Architecture Diagrams

0. High-Level Network Architecture (Architecture Beta)

%%{init: {'theme': 'neutral', 'themeVariables': { 
  'primaryColor': '#f0f0f0', 
  'primaryBorderColor': '#333333', 
  'primaryTextColor': '#333333', 
  'lineColor': '#333333', 
  'tertiaryColor': '#e67e22'
}}}%%
architectureBeta
    %% External Layer
    box "Internet" #f9f9f9
        component Cloudflare["Cloudflare\n(CDN/DNS)"] #f9f9f9
        component DuckDNS["DuckDNS\n(DDNS)"] #f9f9f9
    end

    %% External Gateway
    box "External Gateway" #e6e6e6
        component Livebox["Livebox\n(NAT/Firewall)"] #e6e6e6
    end

    %% Internal Layer
    box "Internal Network\n(192.168.1.0/24)" #d4d4d4
        %% DNS Layer
        box "DNS" #ffff99
            component PiHole1["Pi-hole\n(pi1)"] #ffff99
            component PiHole3["Pi-hole\n(pi3)"] #ffff99
            component StepCA["Step CA\n(pi1)"] #ccccff
        end

        %% k3s Layer
        box "k3s Cluster" #ff9999
            component Traefik["Traefik\n(Ingress)"] #ff9999
            component CrowdSec["CrowdSec\n(Security)"] #ff9999
            component Gitea["Gitea\n(pi2)"] #ffcc99
            component Vault["Vault\n(Secrets)"] #ccccff
        end
    end

    %% Connections
    Cloudflare --> Livebox : "DNS"
    DuckDNS --> Livebox : "DDNS"
    Livebox --> PiHole1 : "NAT"
    Livebox --> PiHole3 : "NAT"
    Livebox --> Traefik : "NAT"
    PiHole1 --> StepCA : "Forward .lab"
    PiHole1 --> Cloudflare : "Forward External"
    PiHole3 --> StepCA : "Forward .lab"
    PiHole3 --> Cloudflare : "Forward External"
    Traefik --> Cloudflare : "TLS (Let's Encrypt)"
    Traefik --> StepCA : "TLS (Step CA)"
    CrowdSec --> Traefik : "Ban IPs"
    Traefik --> Gitea : "Route"
    Traefik --> Vault : "Route"

1. High-Level Network Architecture

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#f89136'}}}%%
graph TD
    %% Styles
    classDef internet fill:#f9f9f9,stroke:#999,color:#333;
    classDef external fill:#e6e6e6,stroke:#555,color:#333;
    classDef internal fill:#d4d4d4,stroke:#777,color:#333;
    classDef security fill:#ff9999,stroke:#cc0000,color:#333;
    classDef dns fill:#ffff99,stroke:#cccc00,color:#333;
    classDef ca fill:#ccccff,stroke:#6666cc,color:#333;

    %% Internet
    subgraph "Internet"
        Cloudflare["Cloudflare (CDN/DNS)"]:::internet
        DuckDNS["DuckDNS (DDNS)"]:::internet
    end

    %% External Gateway
    subgraph "External Gateway"
        Livebox["Livebox (NAT/Firewall)"]:::external
    end

    %% Internal Network
    subgraph "Internal Network (192.168.1.0/24)"
        %% Pi-hole DNS
        PiHole1["Pi-hole (pi1)"]:::dns
        PiHole3["Pi-hole (pi3)"]:::dns

        %% Step CA
        StepCA["Step CA (pi1)"]:::ca

        %% k3s Cluster
        k3s["k3s Cluster"]:::internal
        Traefik["Traefik (k3s)"]:::internal
        CrowdSec["CrowdSec (k3s)"]:::security

        %% Services
        Gitea["Gitea (pi2)"]:::internal
        Vault["Vault (k3s)"]:::internal
    end

    %% Connections
    Cloudflare -->|DNS| Livebox
    DuckDNS -->|DDNS| Livebox
    Livebox -->|NAT| PiHole1
    Livebox -->|NAT| PiHole3
    Livebox -->|NAT| k3s

    %% Internal DNS
    PiHole1 -->|Forward .lab| StepCA
    PiHole1 -->|Forward External| Cloudflare
    PiHole3 -->|Forward .lab| StepCA
    PiHole3 -->|Forward External| Cloudflare

    %% Ingress
    Traefik -->|"TLS (Let's Encrypt)"| Cloudflare
    Traefik -->|"TLS (Step CA)"| StepCA
    CrowdSec -->|Ban IPs| Traefik

    %% Service Access
    Traefik -->|Route| Gitea
    Traefik -->|Route| Vault

2. DNS Resolution Flow

sequenceDiagram
    participant Client
    participant PiHole
    participant StepCA
    participant Cloudflare
    participant ExternalDNS

    Client->>PiHole: Query example.lab
    PiHole->>StepCA: Forward .lab query
    StepCA-->>PiHole: Return A record
    PiHole-->>Client: Return response

    Client->>PiHole: Query example.com
    PiHole->>Cloudflare: Forward to 1.1.1.1
    Cloudflare->>ExternalDNS: Resolve externally
    ExternalDNS-->>Cloudflare: Return response
    Cloudflare-->>PiHole: Return response
    PiHole-->>Client: Return response

3. Ingress and TLS Flow

sequenceDiagram
    participant User
    participant Cloudflare
    participant Traefik
    participant StepCA
    participant Service

    User->>Cloudflare: HTTPS Request (webapp.arcodange.fr)
    Cloudflare->>Traefik: Forward to internal IP
    Traefik->>Let's Encrypt: Request Certificate
    Let's Encrypt-->>Traefik: Issue Certificate
    Traefik->>Service: Route request
    Service-->>Traefik: Return response
    Traefik-->>Cloudflare: Return HTTPS response
    Cloudflare-->>User: Return response

    User->>Traefik: HTTPS Request (webapp.arcodange.lab)
    Traefik->>StepCA: Request Certificate
    StepCA-->>Traefik: Issue Certificate
    Traefik->>Service: Route request
    Service-->>Traefik: Return response
    Traefik-->>User: Return HTTPS response

4. Security Flow (CrowdSec + Traefik)

sequenceDiagram
    participant Attacker
    participant Traefik
    participant CrowdSec
    participant BannedIPs

    Attacker->>Traefik: Malicious Request
    Traefik->>CrowdSec: Log suspicious activity
    CrowdSec->>BannedIPs: Add IP to ban list
    BannedIPs-->>Traefik: Update middleware
    Traefik-->>Attacker: Block request (403)

Playbook and Role Analysis

1. Pi-hole Deployment

  • Playbook: playbooks/system/pihole.yml
  • Role: arcodange.factory.pihole
  • Configuration:
    • Upstream DNS: Cloudflare (1.1.1.1) and Step CA for .lab.
    • Blocklists: Ad-blocking and malware domains.

2. Step CA Deployment

  • Playbook: playbooks/ssl/ssl.yml
  • Role: step_ca
  • Configuration:
    • Internal CA for .lab domain.
    • Short-lived certificates (default: 24h).

3. Traefik Deployment

  • Playbook: playbooks/system/system_k3s.yml (via k3s)
  • Helm Chart: traefik (installed via k3s)
  • Key Annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
    traefik.ingress.kubernetes.io/router.middlewares: localIp@file
    

4. CrowdSec Deployment

  • Playbook: playbooks/tools/crowdsec.yml
  • Role: arcodange.factory.crowdsec
  • Configuration:
    • Bouncer integration with Traefik.
    • Custom scenarios for brute-force and bot detection.

Consequences

Positive

  • Resilient DNS: Pi-hole provides ad-blocking and internal DNS resolution.
  • Secure TLS: Step CA for internal services, Let's Encrypt for external.
  • DDoS Protection: Cloudflare absorbs external attacks.
  • Intrusion Detection: CrowdSec bans malicious IPs automatically.

Negative

  • Complexity: Multiple layers require careful configuration.
  • Single Point of Failure: Pi-hole is critical for internal DNS.
  • Certificate Management: Step CA requires maintenance for .lab domain.

Alternatives Considered

Alternative 1: Public DNS for .lab

  • Rejected: Exposing internal domains is a security risk.

Alternative 2: No Ad-Blocking

  • Rejected: Pi-hole provides essential security and privacy.

Alternative 3: Self-Signed Certificates

  • Rejected: Step CA provides better usability with short-lived certs.

5. Cloudflare Turnstile + CrowdSec Flow

sequenceDiagram
    participant User
    participant Cloudflare
    participant Turnstile
    participant Traefik
    participant CrowdSec
    participant BannedIPs

    User->>Cloudflare: Request protected endpoint
    Cloudflare->>Turnstile: Challenge (CAPTCHA)
    Turnstile-->>Cloudflare: Return token
    Cloudflare->>Traefik: Forward request with token

    alt Valid Token
        Traefik->>Service: Route request
        Service-->>Traefik: Return response
        Traefik-->>Cloudflare: Return response
        Cloudflare-->>User: Return success
    else Invalid Token
        Traefik->>CrowdSec: Log suspicious activity
        CrowdSec->>BannedIPs: Add IP to ban list
        BannedIPs-->>Traefik: Update middleware
        Traefik-->>Cloudflare: Block request (403)
        Cloudflare-->>User: Return "Access Denied"
    end

Success Metrics

  • Pi-hole blocks >50% of ads and trackers.
  • Step CA issues certificates without downtime.
  • Traefik routes 100% of external traffic via Cloudflare.
  • CrowdSec bans >10 malicious IPs per day.
  • Cloudflare Turnstile blocks >90% of bot traffic.