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
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
# This file is maintained automatically by "tofu init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/vault" {
|
||||
version = "4.4.0"
|
||||
constraints = "4.4.0"
|
||||
hashes = [
|
||||
"h1:IhKDv0pTgpy89K3QYmDX872H75Wl7kZKR2scUQynuiA=",
|
||||
"zh:0309ea8f81386e17ab13c06c5991ca959708c55c815b0cfba2bbcd865e0d606e",
|
||||
"zh:40e56199ccd266bffa216e8ebbcdc2e29b6ef5145b39377be766e763cac759c8",
|
||||
"zh:6fad1f073bd2e53e34736e000f98db581137e153ac80bbb5c4f1a1e38b46a1d2",
|
||||
"zh:74564fd4759decccf7f3c952aa2feba1012f103a66ec354aa3b3292a2f1b2412",
|
||||
"zh:7aae012c1a43e6e5dae6f608ec0f08cdb3f95fa121a32e413fe7ee37cb99947f",
|
||||
"zh:7c83f508e164844b1dd9bafe9de0fe60c7be7b55a02e704a6e2f50cff38b7d96",
|
||||
"zh:873a42322b68d9fba4a38217b97ee04a1eb617e811d7f9954016f5c3eb6cb0bc",
|
||||
"zh:9db2b13472cf91a5f18f0a7c6ae532277c05b0980d87f492341426b981679f7b",
|
||||
"zh:ac1cbd2926265db80efe3f1814bed82901f7d8a7d4e5b1e22592e1eef234b1c7",
|
||||
"zh:f465a955cc96f640e7426a648ba672c169a4a2959bad6146fe61583d67642561",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
# minio-values.yaml - Configuration MinIO pour k3s
|
||||
# À placer dans factory/argocd/apps/minio/
|
||||
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: minio
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://charts.bitnami.com/bitnami
|
||||
chart: minio
|
||||
targetRevision: 12.13.0
|
||||
helm:
|
||||
values: |
|
||||
global:
|
||||
storageClass: longhorn
|
||||
|
||||
auth:
|
||||
rootUser: kadans-admin
|
||||
rootPassword: "${MINIO_PASSWORD}" # À définir dans Vault
|
||||
|
||||
mode: standalone
|
||||
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 50Gi
|
||||
storageClass: longhorn
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 250m
|
||||
limits:
|
||||
memory: 1Gi
|
||||
cpu: 500m
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 9000
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
hostname: minio.arcodange.lab
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
tls: true
|
||||
|
||||
buckets:
|
||||
- name: kadans-videos
|
||||
policy: none
|
||||
purge: false
|
||||
- name: kadans-thumbnails
|
||||
policy: none
|
||||
purge: false
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: storage
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
@@ -0,0 +1,111 @@
|
||||
[← ADRs](.) · [factory](../..) · **20260726 — stockage objet (MinIO) : qui déclare quoi**
|
||||
|
||||
> **Cross-references** (bidirectionnel : chaque fichier listé doit citer cette ADR en tête)
|
||||
>
|
||||
> - **Infra partagée** (repo `arcodange-org/tools`) :
|
||||
> [`minio/iac/modules/minio_app/`](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/minio/iac/modules/minio_app) ·
|
||||
> [`minio/iac/provisioner.tf`](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/minio/iac/provisioner.tf) ·
|
||||
> [`minio/values.yaml`](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/minio/values.yaml) ·
|
||||
> [`hashicorp-vault/iac/modules/app_policy/main.tf`](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/hashicorp-vault/iac/modules/app_policy/main.tf)
|
||||
> - **Premier consommateur** (repo `arcodange/kadans`) :
|
||||
> [`iac/main.tf`](https://gitea.arcodange.lab/arcodange/kadans/src/branch/main/iac/main.tf)
|
||||
> - **API consommatrice** (repo `arcodange/kadans-api`) :
|
||||
> [`stockage.go`](https://gitea.arcodange.lab/arcodange/kadans-api/src/branch/main/stockage.go) ·
|
||||
> [`chart/values.yaml`](https://gitea.arcodange.lab/arcodange/kadans-api/src/branch/main/chart/values.yaml)
|
||||
> - **Related ADR** :
|
||||
> [`04_tool_hashicorp_vault.md`](04_tool_hashicorp_vault.md) (rôles et politiques Vault) ·
|
||||
> [`20260407-network-architecture.md`](20260407-network-architecture.md) (Cloudflare / Traefik / CrowdSec)
|
||||
|
||||
# ADR 20260726 : stockage objet (MinIO) — qui déclare quoi, et qui détient quoi
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
|
||||
## Context
|
||||
|
||||
MinIO est déployé dans le namespace `tools` (chart officiel, standalone, volume Longhorn). Le premier consommateur est Kadans, qui doit téléverser des rendus vidéo depuis le navigateur pour qu'ils suivent l'utilisateur d'un appareil à l'autre.
|
||||
|
||||
Trois questions se posaient, et elles sont indépendantes :
|
||||
|
||||
1. **Qui déclare les buckets** d'une application ?
|
||||
2. **Qui détient les identifiants** capables de les créer ?
|
||||
3. **Comment l'application lit** les siens à l'exécution ?
|
||||
|
||||
Une première version faisait tout porter par `tools` : une liste de consommateurs dans son Terraform, les buckets dans son chart. Elle a été rejetée — à ce rythme, chaque bucket de chaque application devient une PR sur l'infra partagée, et le dépôt commun devient le goulot de tout le monde.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. 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**.
|
||||
|
||||
```hcl
|
||||
# iac/main.tf de l'application
|
||||
module "stockage" {
|
||||
source = "git::…/tools.git//minio/iac/modules/minio_app?depth=1&ref=main"
|
||||
app = "kadans"
|
||||
buckets = ["kadans-videos"]
|
||||
providers = { minio = minio }
|
||||
}
|
||||
```
|
||||
|
||||
Le module crée les buckets (privés), une politique bornée à ces buckets, un compte de service, et écrit ses clés dans `kvv2/minio/<app>`.
|
||||
|
||||
### 2. Trois identités, trois portées
|
||||
|
||||
| Identité | Peut | Ne peut pas | Qui la lit |
|
||||
|---|---|---|---|
|
||||
| **root** MinIO | tout | — | le seul pipeline `minio` (`kvv2/minio/config`) |
|
||||
| **provisionneur** | créer bucket, politique, compte de service | lire ou écrire un objet | le rôle **CI** de chaque app (`kvv2/minio/provisioner`) |
|
||||
| **compte de service** d'une app | lire/écrire dans **ses** buckets | tout le reste | le **pod** de l'app (`kvv2/minio/<app>`) |
|
||||
|
||||
C'est la pièce qui rend le point 1 possible. Provisionner demande des droits d'administration ; confier le **root** aurait donné à chaque application la lecture des objets de **toutes** les autres. Le provisionneur, lui, peut créer des buckets — une nuisance si une app est compromise — mais **pas lire les vidéos d'une autre**.
|
||||
|
||||
### 3. La lecture est une propriété de la plateforme
|
||||
|
||||
Le module Vault central `app_policy` accorde à **toute** application la lecture de `kvv2/data/minio/<son nom>`, **inconditionnellement**.
|
||||
|
||||
Pas de drapeau, pas de déclaration par app : le chemin porte le nom de l'application, donc la règle **ne peut jamais exposer que ses propres clés**. Une app qui ne stocke rien y lit un chemin qui n'existe pas — une règle inerte, pas un privilège.
|
||||
|
||||
Conséquence pratique : déclarer un consommateur se fait à **un seul endroit**, son propre `iac/`. Rien à synchroniser, donc rien à oublier.
|
||||
|
||||
### 4. Les octets ne passent pas par l'API
|
||||
|
||||
L'application signe des **URL présignées** ; le navigateur téléverse **directement** vers MinIO. Faire transiter 50 à 200 Mo par un pod applicatif doublerait le transit et exposerait l'API à un seul gros fichier.
|
||||
|
||||
Corollaires :
|
||||
|
||||
- l'endpoint signé doit être **joignable par le navigateur**, donc **public** (`s3.arcodange.fr`) — une page servie en HTTPS ne peut pas téléverser vers `http://` (contenu mixte), et un TLD interne ne se résout pas hors du LAN ;
|
||||
- **CORS** liste les origines **exactes** de l'application, jamais `*` : une URL présignée qui fuiterait serait sinon rejouable depuis n'importe quel site ;
|
||||
- l'ingress public ne porte **pas** de basic-auth, contrairement aux autres : une requête S3 porte sa propre signature, et un défi HTTP Basic casserait un PUT présigné auquel le navigateur ne peut pas répondre.
|
||||
|
||||
### 5. Un bucket par cycle de vie, pas par application
|
||||
|
||||
Une application peut avoir plusieurs buckets. Deux contenus aux durées de vie différentes méritent deux politiques de purge — Kadans en aura deux (un rendu de travail à garder, un aperçu régénérable).
|
||||
|
||||
Le compte de service est **par application** : ajouter un bucket ne crée aucune clé, le compte existant gagne l'accès.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Le dépôt `tools` n'est plus modifié** quand une application change ses buckets. C'était l'objet de la décision.
|
||||
- **Ordre de déploiement contraint** : le module doit exister sur `main` de `tools` avant qu'une application l'appelle (`?ref=main`), et le provisionneur doit exister avant le premier plan d'application.
|
||||
- **Le provisionneur est un secret partagé** entre les rôles CI. Sa compromission permet de créer des buckets et des comptes, pas de lire des objets. Si ce risque devient inacceptable, la suite est une identité de provisionnement **par application**, bornée par préfixe de bucket — MinIO ne le permet pas simplement aujourd'hui.
|
||||
- **Non vérifié à la rédaction** : les noms d'actions d'administration MinIO de la politique du provisionneur viennent de la documentation, pas d'un essai. Le premier `apply` les confirmera ou les corrigera.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
| Option | Pourquoi non |
|
||||
|---|---|
|
||||
| `tools` détient la liste des consommateurs | Chaque bucket de chaque app devient une PR sur l'infra partagée — rejeté par le fondateur, et c'est le cœur de cette ADR |
|
||||
| Les buckets déclarés dans le chart de MinIO (`values.yaml`) | Même défaut : la déclaration vit chez l'infra, pas chez l'application |
|
||||
| Chaque app crée son compte de service avec le **root** | Le root lit et écrit tous les objets de toutes les apps : le distribuer à chaque rôle CI revient à ne plus avoir de cloisonnement |
|
||||
| Déclarer la lecture Vault par app (`kv_read_paths`) | Mécanisme réel, mais c'est la trappe pour lire un secret appartenant à une **autre** app (creds GCS de Longhorn pour l'ERP). Y ranger un motif standard le rend invisible et oblige à le redéclarer partout |
|
||||
| Un drapeau `object_storage = true` par app | Une déclaration de plus à tenir synchronisée avec le `iac/` de l'app — donc une à oublier. La règle inerte ne coûte rien |
|
||||
| Une identité de provisionnement par app | Souhaitable, mais MinIO ne borne pas simplement les actions d'administration par préfixe. À reconsidérer si le modèle de menace change |
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- Ajouter une application consommatrice ne touche **aucun** fichier de `tools`.
|
||||
- Un compte de service compromis ne donne accès qu'aux objets qu'il gérait déjà.
|
||||
- Le root de MinIO n'apparaît dans aucune politique Vault en dehors du pipeline `minio`.
|
||||
@@ -15,6 +15,7 @@
|
||||
- [x] gitea packages
|
||||
- [ ] devsecops tools
|
||||
- [x] [hashicorp vault](./04_tool_hashicorp_vault.md)
|
||||
- [x] [stockage objet MinIO — qui déclare quoi](./20260726-stockage-objet-minio.md)
|
||||
- [ ] terrakube
|
||||
- [ ] prometheus/grafana
|
||||
- [ ] ansible AWX
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# ADR 20260407: CI/CD Architecture with ArgoCD, Gitea, and Vault
|
||||
|
||||
## Status
|
||||
Proposed
|
||||
|
||||
## Context
|
||||
The home lab requires a secure and automated CI/CD pipeline to deploy applications to the k3s cluster. The pipeline must integrate with:
|
||||
- **Gitea**: For Git repository management and CI runners.
|
||||
- **ArgoCD**: For GitOps-based continuous deployment.
|
||||
- **Vault**: For secrets management and OIDC authentication.
|
||||
- **Gitea Act Runner**: For executing CI jobs.
|
||||
|
||||
## Decision
|
||||
We will implement a **GitOps-driven CI/CD pipeline** with the following components:
|
||||
|
||||
### 1. Gitea OIDC Authentication with Vault
|
||||
- Gitea is registered as an OIDC application in Vault.
|
||||
- Vault issues short-lived tokens for Gitea users.
|
||||
- The `gitea_oidc_auth.yml` playbook automates this setup using Playwright and OpenTofu.
|
||||
- **OIDC Workflow**:
|
||||
1. The `oidc_jwt_token.sh` script (base64-encoded in `secrets.vault_oauth__sh_b64`) handles the OIDC flow.
|
||||
2. Gitea Act Runner executes the script to obtain an ID token from Gitea.
|
||||
3. The ID token is used to authenticate with Vault and retrieve secrets.
|
||||
|
||||
### 2. Gitea Act Runner
|
||||
- Deployed on `pi1` and `pi3` (not on the Gitea host, which is `pi2`).
|
||||
- Uses Docker-in-Docker for job execution.
|
||||
- **Custom Runner Image (`ubuntu-latest-ca`)**: Required due to the self-signed `.lab` domain. The custom image includes the local CA certificate to trust the Gitea instance (`gitea.arcodange.lab`).
|
||||
- Managed via Docker Compose (`03_cicd.yml`).
|
||||
|
||||
### 3. ArgoCD
|
||||
- Deployed on the k3s cluster (via HelmChart in `/var/lib/rancher/k3s/server/manifests/argocd.yaml`).
|
||||
- Uses Gitea as the source of truth for GitOps.
|
||||
- Synchronizes the `factory` repository to deploy applications.
|
||||
- Configured with Traefik for TLS termination.
|
||||
|
||||
### 4. Vault Secrets Operator
|
||||
- Deployed in the `tools` namespace.
|
||||
- Manages secrets for applications deployed via ArgoCD.
|
||||
- Integrates with Gitea OIDC for authentication.
|
||||
- **Helm Chart Integration**:
|
||||
- `VaultAuth`: Authenticates with Vault using Kubernetes service accounts.
|
||||
- `VaultStaticSecret`: Retrieves static secrets (e.g., `kvv2/webapp/config`).
|
||||
- `VaultDynamicSecret`: Generates dynamic secrets (e.g., PostgreSQL credentials).
|
||||
|
||||
### 5. Security
|
||||
- **TLS**: Traefik terminates TLS using Let's Encrypt.
|
||||
- **OIDC**: Gitea authentication via Vault.
|
||||
- **Secrets**: Stored in Vault, injected via the Vault Secrets Operator.
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#e67e22'}}}%%
|
||||
graph TD
|
||||
%% Styles
|
||||
classDef gitea fill:#ffcc99,stroke:#cc9966,color:#333;
|
||||
classDef argocd fill:#99ffcc,stroke:#66cc99,color:#333;
|
||||
classDef vault fill:#ccccff,stroke:#6666cc,color:#333;
|
||||
classDef k3s fill:#ff9999,stroke:#cc0000,color:#333;
|
||||
classDef runner fill:#ffff99,stroke:#cccc00,color:#333;
|
||||
|
||||
%% Components
|
||||
Gitea["Gitea (pi2)"]:::gitea
|
||||
ArgoCD["ArgoCD (k3s)"]:::argocd
|
||||
Vault["Vault (k3s/tools)"]:::vault
|
||||
Runner1["Gitea Act Runner (pi1)"]:::runner
|
||||
Runner2["Gitea Act Runner (pi3)"]:::runner
|
||||
VaultOperator["Vault Secrets Operator (k3s/tools)"]:::vault
|
||||
k3s["k3s Cluster"]:::k3s
|
||||
|
||||
%% Workflow
|
||||
Gitea -->|OIDC Auth| Vault
|
||||
Gitea -->|Trigger CI| Runner1
|
||||
Gitea -->|Trigger CI| Runner2
|
||||
Runner1 -->|Deploy to| k3s
|
||||
Runner2 -->|Deploy to| k3s
|
||||
ArgoCD -->|GitOps Sync| Gitea
|
||||
ArgoCD -->|Deploy Apps| k3s
|
||||
VaultOperator -->|Inject Secrets| k3s
|
||||
Vault -->|Secrets| VaultOperator
|
||||
|
||||
%% Annotations
|
||||
linkStyle 0,1,2,3,4,5,6,7 stroke:#999,stroke-width:1px;
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- **Automated Deployments**: ArgoCD ensures the cluster state matches Git.
|
||||
- **Secure Secrets**: Vault centralizes secret management.
|
||||
- **Scalable CI**: Gitea Act Runners can be added to any host.
|
||||
- **OIDC Integration**: Secure authentication via Vault.
|
||||
|
||||
### Negative
|
||||
- **Complexity**: Multiple moving parts (Gitea, ArgoCD, Vault).
|
||||
- **Dependency on Vault**: If Vault fails, CI/CD may be disrupted.
|
||||
- **Learning Curve**: Requires familiarity with GitOps and Vault.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Alternative 1: GitHub Actions
|
||||
- **Rejected**: Self-hosted Gitea aligns better with the home lab's privacy goals.
|
||||
|
||||
### Alternative 2: Jenkins
|
||||
- **Rejected**: ArgoCD + Gitea Act Runner is lighter and more GitOps-native.
|
||||
|
||||
### Alternative 3: No CI/CD
|
||||
- **Rejected**: Manual deployments are error-prone and unscalable.
|
||||
|
||||
## Sequence Diagrams
|
||||
|
||||
### 1. CI/CD Workflow for OpenTofu/Terraform
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Gitea
|
||||
participant Runner as Gitea Act Runner (pi1/pi3)
|
||||
participant Vault
|
||||
participant WebApp as WebApp (k3s)
|
||||
|
||||
Gitea->>Runner: Trigger vault.yaml workflow
|
||||
Runner->>Gitea: Execute vault_oauth__sh_b64 (OIDC)
|
||||
Gitea-->>Runner: Return ID Token
|
||||
Runner->>Vault: Authenticate with ID Token
|
||||
Vault-->>Runner: Return Vault Token
|
||||
Runner->>Runner: Run OpenTofu/Terraform
|
||||
Runner->>Vault: Fetch Secrets (via Vault Action)
|
||||
Vault-->>Runner: Return Secrets
|
||||
Runner->>WebApp: Deploy Changes
|
||||
```
|
||||
|
||||
### 2. Vault Secrets Operator Workflow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ArgoCD
|
||||
participant WebApp as WebApp (k3s)
|
||||
participant VaultOperator as Vault Secrets Operator
|
||||
participant Vault
|
||||
|
||||
ArgoCD->>WebApp: Deploy Helm Chart
|
||||
WebApp->>VaultOperator: Create VaultAuth (K8s Auth)
|
||||
VaultOperator->>Vault: Authenticate (K8s Service Account)
|
||||
Vault-->>VaultOperator: Return Vault Token
|
||||
WebApp->>VaultOperator: Create VaultStaticSecret (kvv2/webapp/config)
|
||||
VaultOperator->>Vault: Fetch Static Secret
|
||||
Vault-->>VaultOperator: Return Secret
|
||||
VaultOperator->>WebApp: Inject Secret (secretkv)
|
||||
WebApp->>VaultOperator: Create VaultDynamicSecret (postgres/creds/webapp)
|
||||
VaultOperator->>Vault: Generate Dynamic Secret
|
||||
Vault-->>VaultOperator: Return Credentials
|
||||
VaultOperator->>WebApp: Inject Credentials (vso-db-credentials)
|
||||
WebApp->>WebApp: Restart Pods (Rollout)
|
||||
```
|
||||
|
||||
## Success Metrics
|
||||
- Gitea Act Runners successfully execute CI jobs.
|
||||
- ArgoCD synchronizes the `factory` repository without errors.
|
||||
- Vault Secrets Operator injects secrets into deployed applications.
|
||||
@@ -0,0 +1,334 @@
|
||||
# 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)
|
||||
|
||||
```mermaid
|
||||
%%{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
|
||||
|
||||
```mermaid
|
||||
%%{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
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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)
|
||||
|
||||
```mermaid
|
||||
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**:
|
||||
```yaml
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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.
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
# Script pour générer un token Gitea et configurer Hermes
|
||||
# Usage: ./get_gitea_token.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔑 Génération d'un token Gitea pour Hermes..."
|
||||
|
||||
# Créer l'inventaire
|
||||
cat > ~/Work/Arcodange/factory/inventory.ini << 'EOF'
|
||||
[gitea]
|
||||
gitea.arcodange.lab ansible_host=192.168.1.202 ansible_user=git
|
||||
|
||||
[gitea:vars]
|
||||
ansible_ssh_private_key_file=~/.ssh/id_rsa_arcodange
|
||||
EOF
|
||||
|
||||
# Créer le playbook
|
||||
cat > ~/Work/Arcodange/factory/get_token.yml << 'EOF'
|
||||
- name: Générer un token Gitea pour Hermes
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: true
|
||||
|
||||
vars:
|
||||
gitea_user_name: arcodange
|
||||
gitea_token_name: hermes-{{ ansible_date_time.iso8601 }}
|
||||
gitea_token_scopes: "write:repository,write:user"
|
||||
gitea_token_fact_name: gitea_api_token
|
||||
gitea_base_url: "https://gitea.arcodange.lab"
|
||||
|
||||
tasks:
|
||||
- name: Inclure le role gitea_token
|
||||
include_role:
|
||||
name: .claude/worktrees/focused-dirac-151213/ansible/arcodange/factory/roles/gitea_token
|
||||
|
||||
- name: Afficher le token généré
|
||||
debug:
|
||||
var: gitea_api_token
|
||||
when: gitea_api_token is defined and gitea_api_token != 'deleted'
|
||||
|
||||
- name: Sauvegarder le token dans un fichier temporaire
|
||||
copy:
|
||||
content: "{{ gitea_api_token }}"
|
||||
dest: /tmp/gitea_token.txt
|
||||
when: gitea_api_token is defined and gitea_api_token != 'deleted'
|
||||
EOF
|
||||
|
||||
# Exécuter le playbook
|
||||
echo "📜 Exécution du playbook Ansible..."
|
||||
cd ~/Work/Arcodange/factory
|
||||
ansible-playbook -i inventory.ini get_token.yml
|
||||
|
||||
# Récupérer le token depuis le fichier temporaire
|
||||
if [ -f /tmp/gitea_token.txt ]; then
|
||||
TOKEN=$(cat /tmp/gitea_token.txt)
|
||||
echo "✅ Token généré avec succès !"
|
||||
|
||||
# Configurer Hermes
|
||||
echo "🛠 Configuration de Hermes..."
|
||||
hermes config set gitea.token "$TOKEN"
|
||||
hermes config set gitea.base_url "https://gitea.arcodange.lab"
|
||||
hermes config set gitea.username "arcodange"
|
||||
|
||||
# Nettoyer
|
||||
rm -f /tmp/gitea_token.txt
|
||||
rm -f inventory.ini
|
||||
rm -f get_token.yml
|
||||
|
||||
echo "✨ Configuration terminée !"
|
||||
echo "Token stocké dans la configuration Hermes."
|
||||
else
|
||||
echo "⚠️ Impossible de récupérer le token. Vérifie que :"
|
||||
echo " 1. Le serveur Gitea est accessible"
|
||||
echo " 2. La clé SSH est configurée (~/.ssh/id_rsa_arcodange)"
|
||||
echo " 3. L'utilisateur 'git' a les permissions nécessaires"
|
||||
fi
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# Script pour générer un token Gitea via API REST et configurer Hermes
|
||||
# Usage: ./get_gitea_token_rest.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔑 Génération d'un token Gitea via API REST..."
|
||||
|
||||
# Demander les informations nécessaires
|
||||
read -p "Nom d'utilisateur Gitea (arcodange) : " GITEA_USER
|
||||
GITEA_USER=${GITEA_USER:-arcodange}
|
||||
|
||||
read -s -p "Mot de passe Gitea : " GITEA_PASSWORD
|
||||
echo ""
|
||||
|
||||
read -p "Nom du token (hermes-access) : " TOKEN_NAME
|
||||
TOKEN_NAME=${TOKEN_NAME:-hermes-access}
|
||||
|
||||
read -p "Portées (write:repository,write:user) : " SCOPES
|
||||
SCOPES=${SCOPES:-write:repository,write:user}
|
||||
|
||||
# Appeler l'API Gitea pour créer un token
|
||||
echo "📡 Appel à l'API Gitea..."
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
"https://gitea.arcodange.lab/api/v1/users/${GITEA_USER}/tokens" \
|
||||
-H "Content-Type: application/json" \
|
||||
-u "${GITEA_USER}:${GITEA_PASSWORD}" \
|
||||
-d "{\"name\":\"${TOKEN_NAME}\",\"scopes\":[\"${SCOPES//,/\",\"}\"]}")
|
||||
|
||||
# Extraire le token de la réponse
|
||||
TOKEN=$(echo "$RESPONSE" | jq -r '.sha1' 2>/dev/null)
|
||||
|
||||
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
|
||||
echo "❌ Échec de la création du token"
|
||||
echo "Réponse de l'API :"
|
||||
echo "$RESPONSE" | jq .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Token généré avec succès !"
|
||||
echo "Token: $TOKEN"
|
||||
|
||||
# Configurer Hermes
|
||||
echo "🛠 Configuration de Hermes..."
|
||||
hermes config set gitea.token "$TOKEN"
|
||||
hermes config set gitea.base_url "https://gitea.arcodange.lab"
|
||||
hermes config set gitea.username "$GITEA_USER"
|
||||
|
||||
echo "✨ Configuration terminée !"
|
||||
echo "Token stocké dans la configuration Hermes."
|
||||
echo ""
|
||||
echo "Pour vérifier la configuration :"
|
||||
echo " hermes config get gitea"
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Script pour générer un token Gitea via API REST avec fichier temporaire
|
||||
# Usage: ./get_gitea_token_tmp.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔑 Génération d'un token Gitea via API REST..."
|
||||
|
||||
# Utiliser le fichier temporaire existant
|
||||
PASS_FILE="/tmp/gitea_pass.txt"
|
||||
|
||||
if [ ! -f "$PASS_FILE" ]; then
|
||||
echo "❌ Fichier de mot de passe introuvable : $PASS_FILE"
|
||||
echo "Créez-le avec : echo 'votre_mot_de_passe' > $PASS_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Variables
|
||||
GITEA_USER="arcodange"
|
||||
TOKEN_NAME="hermes-$(date +%Y%m%d-%H%M%S)"
|
||||
SCOPES="write:repository,write:user"
|
||||
|
||||
# Appeler l'API Gitea pour créer un token
|
||||
echo "📡 Appel à l'API Gitea..."
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
"https://gitea.arcodange.lab/api/v1/users/${GITEA_USER}/tokens" \
|
||||
-H "Content-Type: application/json" \
|
||||
-u "${GITEA_USER}:$(cat $PASS_FILE)" \
|
||||
-d "{\"name\":\"${TOKEN_NAME}\",\"scopes\":[\"${SCOPES//,/\",\"}\"]}")
|
||||
|
||||
# Nettoyer le fichier temporaire
|
||||
rm -f "$PASS_FILE"
|
||||
|
||||
# Extraire le token de la réponse
|
||||
TOKEN=$(echo "$RESPONSE" | jq -r '.sha1' 2>/dev/null)
|
||||
|
||||
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
|
||||
echo "❌ Échec de la création du token"
|
||||
echo "Réponse de l'API :"
|
||||
echo "$RESPONSE" | jq .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Token généré avec succès !"
|
||||
echo "Token: $TOKEN"
|
||||
|
||||
# Configurer Hermes
|
||||
echo "🛠 Configuration de Hermes..."
|
||||
hermes config set gitea.token "$TOKEN"
|
||||
hermes config set gitea.base_url "https://gitea.arcodange.lab"
|
||||
hermes config set gitea.username "$GITEA_USER"
|
||||
|
||||
echo "✨ Configuration terminée !"
|
||||
echo "Token stocké dans la configuration Hermes."
|
||||
echo ""
|
||||
echo "Pour vérifier la configuration :"
|
||||
hermes config get gitea
|
||||
@@ -0,0 +1,27 @@
|
||||
- name: Générer un token Gitea pour Hermes
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: true
|
||||
|
||||
vars:
|
||||
gitea_user_name: arcodange
|
||||
gitea_token_name: hermes-{{ ansible_date_time.iso8601 }}
|
||||
gitea_token_scopes: "write:repository,write:user"
|
||||
gitea_token_fact_name: gitea_api_token
|
||||
gitea_base_url: "https://gitea.arcodange.lab"
|
||||
|
||||
tasks:
|
||||
- name: Inclure le role gitea_token
|
||||
include_role:
|
||||
name: .claude/worktrees/focused-dirac-151213/ansible/arcodange/factory/roles/gitea_token
|
||||
|
||||
- name: Afficher le token généré
|
||||
debug:
|
||||
var: gitea_api_token
|
||||
when: gitea_api_token is defined and gitea_api_token != 'deleted'
|
||||
|
||||
- name: Sauvegarder le token dans un fichier temporaire
|
||||
copy:
|
||||
content: "{{ gitea_api_token }}"
|
||||
dest: /tmp/gitea_token.txt
|
||||
when: gitea_api_token is defined and gitea_api_token != 'deleted'
|
||||
@@ -0,0 +1,5 @@
|
||||
[gitea]
|
||||
gitea.arcodange.lab ansible_host=192.168.1.202 ansible_user=git
|
||||
|
||||
[gitea:vars]
|
||||
ansible_ssh_private_key_file=~/.ssh/id_rsa_arcodange
|
||||
Generated
+59
@@ -0,0 +1,59 @@
|
||||
# This file is maintained automatically by "tofu init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/cyrilgdn/postgresql" {
|
||||
version = "1.24.0"
|
||||
constraints = "1.24.0"
|
||||
hashes = [
|
||||
"h1:GDmie2p7Ii7BOPHeMUtM6bwSvr3YvsIWUQ30TsSc+5I=",
|
||||
"zh:023153dbb689296f3edbc34cb6d57355bbfa8af6faa3e20ab71a71a909c8ff56",
|
||||
"zh:0d222293c1c30b0af5b9ac9a662f7fd6bbbdca0859b02046527ad133838fafd8",
|
||||
"zh:1234c6ec6bd49d8a2afc80fecc748ebdd01555ac4f1b4618dc7fce670bf18858",
|
||||
"zh:18d047ffa30156da8281206b977d23dc9690e87137b0a9dd7049d2776e603ce6",
|
||||
"zh:2f009822960405a20f2d304f70187f77338bf3e633f522da35021f4b83bc6a8f",
|
||||
"zh:39e540af29165e9dc28712651d308fdf0fcac018850a0841f7c5380f58aafb64",
|
||||
"zh:4930ac482b2b0f06681228932d0ed91ac4f34555b15285787f048e33ffa72d39",
|
||||
"zh:4d33f4a9f449166a5cb495e764c8a09bf9710927162d30f5db46283a88023cad",
|
||||
"zh:55d6584517ebaf7b4ecefedbf600af0d12077a00834b777f884b4631f77b7e13",
|
||||
"zh:5f6ab45cbb3f2b62f82f0377c7f10d4aa0f35db7f426af7529915401097c252f",
|
||||
"zh:5fac6d4d676425d9491a42b992b166bdbae5ecd1fe6815bc75435f7c3024015b",
|
||||
"zh:a13f1a7cc763053da2317178c7ba1d57f5fddf14f9be282881da63fc28fd0e57",
|
||||
"zh:d566eaae8ba33542f7b290e8fa0f2eb7f2cfb51d5ae7cafaae261f76f3f3e422",
|
||||
"zh:ea4d50591767696b6f27d977c44b17390415d21b761e00f591d67c6248c18949",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/random" {
|
||||
version = "3.7.2"
|
||||
hashes = [
|
||||
"h1:cFGCdxTlsrteTiaOV/iOQdql7eJkD3F/vtJxenkj9IE=",
|
||||
"zh:2ffeb1058bd7b21a9e15a5301abb863053a2d42dffa3f6cf654a1667e10f4727",
|
||||
"zh:519319ed8f4312ed76519652ad6cd9f98bc75cf4ec7990a5684c072cf5dd0a5d",
|
||||
"zh:7371c2cc28c94deb9dba62fbac2685f7dde47f93019273a758dd5a2794f72919",
|
||||
"zh:9b0ac4c1d8e36a86b59ced94fa517ae9b015b1d044b3455465cc6f0eab70915d",
|
||||
"zh:c6336d7196f1318e1cbb120b3de8426ce43d4cacd2c75f45dba2dbdba666ce00",
|
||||
"zh:c71f18b0cb5d55a103ea81e346fb56db15b144459123f1be1b0209cffc1deb4e",
|
||||
"zh:d2dc49a6cac2d156e91b0506d6d756809e36bf390844a187f305094336d3e8d8",
|
||||
"zh:d5b5fc881ccc41b268f952dae303501d6ec9f9d24ee11fe2fa56eed7478e15d0",
|
||||
"zh:db9723eaca26d58c930e13fde221d93501529a5cd036b1f167ef8cff6f1a03cc",
|
||||
"zh:fe3359f733f3ab518c6f85f3a9cd89322a7143463263f30321de0973a52d4ad8",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/vault" {
|
||||
version = "4.4.0"
|
||||
constraints = "4.4.0"
|
||||
hashes = [
|
||||
"h1:IhKDv0pTgpy89K3QYmDX872H75Wl7kZKR2scUQynuiA=",
|
||||
"zh:0309ea8f81386e17ab13c06c5991ca959708c55c815b0cfba2bbcd865e0d606e",
|
||||
"zh:40e56199ccd266bffa216e8ebbcdc2e29b6ef5145b39377be766e763cac759c8",
|
||||
"zh:6fad1f073bd2e53e34736e000f98db581137e153ac80bbb5c4f1a1e38b46a1d2",
|
||||
"zh:74564fd4759decccf7f3c952aa2feba1012f103a66ec354aa3b3292a2f1b2412",
|
||||
"zh:7aae012c1a43e6e5dae6f608ec0f08cdb3f95fa121a32e413fe7ee37cb99947f",
|
||||
"zh:7c83f508e164844b1dd9bafe9de0fe60c7be7b55a02e704a6e2f50cff38b7d96",
|
||||
"zh:873a42322b68d9fba4a38217b97ee04a1eb617e811d7f9954016f5c3eb6cb0bc",
|
||||
"zh:9db2b13472cf91a5f18f0a7c6ae532277c05b0980d87f492341426b981679f7b",
|
||||
"zh:ac1cbd2926265db80efe3f1814bed82901f7d8a7d4e5b1e22592e1eef234b1c7",
|
||||
"zh:f465a955cc96f640e7426a648ba672c169a4a2959bad6146fe61583d67642561",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
# Home lab
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#e67e22'}}}%%
|
||||
graph LR
|
||||
%% Styles
|
||||
classDef internet fill:#f9f9f9,stroke:#999,color:#333,font-size:12px;
|
||||
classDef arcodange fill:#e6e6e6,stroke:#555,color:#333,font-size:12px;
|
||||
classDef cluster fill:#d4d4d4,stroke:#777,color:#333,font-size:12px;
|
||||
|
||||
%% Internet
|
||||
subgraph "Internet / Le monde"
|
||||
direction TB
|
||||
DuckDNS["🌐 DuckDNS<br>(duckdns.org)"]:::internet
|
||||
GoogleCloud["☁️ Google Cloud<br>(cloud.google.com)"]:::internet
|
||||
Cloudflare["🛡️ Cloudflare<br>(cloudflare.com)"]:::internet
|
||||
OVHcloud["🖥️ OVHcloud<br>(ovhcloud.com)"]:::internet
|
||||
Zoho["📧 Zoho<br>(zoho.eu)"]:::internet
|
||||
CrowdSec["🔒 CrowdSec<br>(crowdsec.net)"]:::internet
|
||||
end
|
||||
|
||||
%% Bureaux Arcodange
|
||||
subgraph "Bureaux Arcodange"
|
||||
direction TB
|
||||
Livebox["📡 Livebox<br>(gateway)"]:::arcodange
|
||||
MacBook["💻 MacBook Pro M4"]:::arcodange
|
||||
|
||||
subgraph "Cluster Raspberry Pi"
|
||||
direction LR
|
||||
Pi1["🐧 pi1"]:::cluster
|
||||
Pi2["🐧 pi2"]:::cluster
|
||||
Pi3["🐧 pi3"]:::cluster
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Network
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#e67e22'}}}%%
|
||||
graph LR
|
||||
%% Styles
|
||||
classDef internet fill:#f9f9f9,stroke:#999,color:#333,font-size:12px;
|
||||
classDef arcodange fill:#e6e6e6,stroke:#555,color:#333,font-size:12px;
|
||||
classDef k3sServer fill:#ff9999,stroke:#cc0000,color:#333,font-size:12px;
|
||||
classDef k3sAgent fill:#99ff99,stroke:#00cc00,color:#333,font-size:12px;
|
||||
classDef traefik fill:#9999ff,stroke:#0000cc,color:#333,font-size:12px;
|
||||
classDef dns fill:#ffff99,stroke:#cccc00,color:#333,font-size:12px;
|
||||
classDef cloudflared fill:#ccccff,stroke:#6666cc,color:#333,font-size:12px;
|
||||
|
||||
%% Internet
|
||||
subgraph "Internet / Le monde"
|
||||
direction TB
|
||||
DuckDNS["🌐 DuckDNS<br>(duckdns.org)"]:::internet
|
||||
subgraph "Cloudflare"
|
||||
CloudflareService["🛡️ Cloudflare<br>(cloudflare.com)"]:::internet
|
||||
DNSNote@{ shape: text, label: "redirige *.arcodange.fr<br>→ Cloudflared Tunnel" }
|
||||
end
|
||||
end
|
||||
|
||||
%% Bureaux Arcodange
|
||||
subgraph "Bureaux Arcodange"
|
||||
direction TB
|
||||
Livebox["📡 Livebox<br>(passerelle)"]:::arcodange
|
||||
MacBook["💻 MacBook Pro M4"]:::arcodange
|
||||
|
||||
%% Cluster Raspberry Pi
|
||||
subgraph "Cluster Raspberry Pi"
|
||||
direction LR
|
||||
|
||||
%% Noeud pi1
|
||||
subgraph "pi1"
|
||||
Pi1["🐧 pi1"]:::k3sServer
|
||||
subgraph "K3s (Server)"
|
||||
Traefik["🚀 Traefik"]:::traefik
|
||||
Cloudflared["☁️ Cloudflared Tunnel"]:::cloudflared
|
||||
end
|
||||
PiHole1["🧹 Pi-hole"]:::dns
|
||||
NS1@{ shape: text, label: "nameserver : pi3" }
|
||||
DNSArcodange@{ shape: text, label: ".arcodange.lab<br>→ Traefik", color:#cccc00 }
|
||||
end
|
||||
|
||||
%% Noeud pi2
|
||||
subgraph "pi2"
|
||||
Pi2["🐧 pi2"]:::k3sAgent
|
||||
end
|
||||
|
||||
%% Noeud pi3
|
||||
subgraph "pi3"
|
||||
Pi3["🐧 pi3"]:::k3sAgent
|
||||
PiHole3["🧹 Pi-hole"]:::dns
|
||||
NS3@{ shape: text, label: "nameserver : pi1" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%% Connexions
|
||||
%% Pi-hole → Livebox (DNS)
|
||||
PiHole1 -.->|DNS| Livebox
|
||||
PiHole3 -.->|DNS| Livebox
|
||||
|
||||
%% Cloudflare → Cloudflared Tunnel → Traefik
|
||||
CloudflareService -.-> Cloudflared
|
||||
Cloudflared --> Traefik
|
||||
|
||||
%% Livebox → Traefik (HTTPS)
|
||||
Livebox -->|redirige HTTPS| Traefik
|
||||
|
||||
```
|
||||
|
||||
## with Gitea
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#e67e22'}}}%%
|
||||
graph LR
|
||||
%% Styles
|
||||
classDef internet fill:#f9f9f9,stroke:#999,color:#333,font-size:12px;
|
||||
classDef arcodange fill:#e6e6e6,stroke:#555,color:#333,font-size:12px;
|
||||
classDef k3sServer fill:#ff9999,stroke:#cc0000,color:#333,font-size:12px;
|
||||
classDef k3sAgent fill:#99ff99,stroke:#00cc00,color:#333,font-size:12px;
|
||||
classDef traefik fill:#9999ff,stroke:#0000cc,color:#333,font-size:12px;
|
||||
classDef dns fill:#ffff99,stroke:#cccc00,color:#333,font-size:12px;
|
||||
classDef cloudflared fill:#ccccff,stroke:#6666cc,color:#333,font-size:12px;
|
||||
classDef gitea fill:#ffcc99,stroke:#cc9966,color:#333,font-size:12px;
|
||||
classDef cms fill:#99ffcc,stroke:#66cc99,color:#333,font-size:12px;
|
||||
classDef domainFR fill:#99ccff,stroke:#0066cc,color:#333,font-size:12px;
|
||||
classDef domainLAB fill:#ccff99,stroke:#66cc66,color:#333,font-size:12px;
|
||||
|
||||
%% Internet
|
||||
subgraph "Internet / Le monde"
|
||||
direction TB
|
||||
DuckDNS["🌐 DuckDNS<br>(duckdns.org)"]:::internet
|
||||
subgraph "Cloudflare"
|
||||
CloudflareService["🛡️ Cloudflare<br>(cloudflare.com)"]:::internet
|
||||
DNSNote@{ shape: text, label: "redirige *.arcodange.fr<br>→ Cloudflared Tunnel" }
|
||||
end
|
||||
end
|
||||
|
||||
%% Bureaux Arcodange
|
||||
subgraph "Bureaux Arcodange"
|
||||
direction TB
|
||||
Livebox["📡 Livebox<br>(passerelle)"]:::arcodange
|
||||
MacBook["💻 MacBook Pro M4"]:::arcodange
|
||||
|
||||
%% Cluster Raspberry Pi
|
||||
subgraph "Cluster Raspberry Pi"
|
||||
direction LR
|
||||
|
||||
%% Noeud pi1
|
||||
subgraph "pi1"
|
||||
Pi1["🐧 pi1"]:::k3sServer
|
||||
subgraph "K3s (Server)"
|
||||
Traefik["🚀 Traefik"]:::traefik
|
||||
Cloudflared["☁️ Cloudflared Tunnel"]:::cloudflared
|
||||
end
|
||||
PiHole1["🧹 Pi-hole"]:::dns
|
||||
GiteaRunner1["🏃 Gitea Act Runner"]:::gitea
|
||||
NS1@{ shape: text, label: "nameserver : pi3" }
|
||||
DNSArcodange@{ shape: text, label: ".arcodange.lab<br>→ Traefik", color:#cccc00 }
|
||||
end
|
||||
|
||||
%% Noeud pi2
|
||||
subgraph "pi2"
|
||||
Pi2["🐧 pi2"]:::k3sAgent
|
||||
Gitea["🦊 Gitea"]:::gitea
|
||||
subgraph "K3s (Agent)"
|
||||
CMS["📝 CMS"]:::cms
|
||||
end
|
||||
end
|
||||
|
||||
%% Noeud pi3
|
||||
subgraph "pi3"
|
||||
Pi3["🐧 pi3"]:::k3sAgent
|
||||
PiHole3["🧹 Pi-hole"]:::dns
|
||||
GiteaRunner3["🏃 Gitea Act Runner"]:::gitea
|
||||
NS3@{ shape: text, label: "nameserver : pi1" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%% Connexions Pi-hole → Livebox (DNS)
|
||||
PiHole1 -.->|DNS| Livebox
|
||||
PiHole3 -.->|DNS| Livebox
|
||||
|
||||
%% Cloudflare → Cloudflared Tunnel → Traefik
|
||||
CloudflareService -.-> Cloudflared
|
||||
Cloudflared --> Traefik
|
||||
|
||||
%% Livebox → Traefik (HTTPS)
|
||||
Livebox -->|redirige HTTPS| Traefik
|
||||
|
||||
%% Gitea Act Runner → Gitea
|
||||
GiteaRunner1 -.->|communication| Gitea
|
||||
GiteaRunner3 -.->|communication| Gitea
|
||||
|
||||
%% Domains → CMS via Traefik
|
||||
Traefik -->|cms-rec.arcodange.fr| CMS:::domainFR
|
||||
Traefik -->|www.arcodange.lab| CMS:::domainLAB
|
||||
|
||||
```
|
||||
|
||||
### with security
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#05e2ff1c'}}}%%
|
||||
graph LR
|
||||
%% Styles
|
||||
classDef internet fill:#f9f9f9,stroke:#999,color:#333,font-size:12px;
|
||||
classDef arcodange fill:#e6e6e6,stroke:#555,color:#333,font-size:12px;
|
||||
classDef k3sServer fill:#ff9999,stroke:#cc0000,color:#333,font-size:12px;
|
||||
classDef k3sAgent fill:#99ff99,stroke:#00cc00,color:#333,font-size:12px;
|
||||
classDef traefik fill:#9999ff,stroke:#0000cc,color:#333,font-size:12px;
|
||||
classDef dns fill:#ffff99,stroke:#cccc00,color:#333,font-size:12px;
|
||||
classDef cloudflared fill:#ccccff,stroke:#6666cc,color:#333,font-size:12px;
|
||||
classDef gitea fill:#ffcc99,stroke:#cc9966,color:#333,font-size:12px;
|
||||
classDef cms fill:#99ffcc,stroke:#66cc99,color:#333,font-size:12px;
|
||||
classDef domainFR fill:#99ccff,stroke:#0066cc,color:#333,font-size:12px;
|
||||
classDef domainLAB fill:#ccff99,stroke:#66cc66,color:#333,font-size:12px;
|
||||
classDef crowdsec fill:#ff99cc,stroke:#cc6699,color:#333,font-size:12px;
|
||||
classDef security fill:#ffcccc,stroke:#cc9999,color:#333,font-size:12px;
|
||||
|
||||
%% Internet
|
||||
subgraph "Internet / Le monde"
|
||||
direction TB
|
||||
subgraph "Cloudflare"
|
||||
CloudflareService["🛡️ Cloudflare<br>(cloudflare.com)"]:::internet
|
||||
DNSNote@{ shape: text, label: "redirige *.arcodange.fr<br>→ Cloudflared Tunnel" }
|
||||
end
|
||||
end
|
||||
|
||||
%% Bureaux Arcodange
|
||||
subgraph "Bureaux Arcodange"
|
||||
direction TB
|
||||
Livebox["📡 Livebox<br>(passerelle)"]:::arcodange
|
||||
DNSArcodange@{ shape: text, label: ".arcodange.lab<br>→ Traefik" }
|
||||
|
||||
%% Cluster Raspberry Pi
|
||||
subgraph "Cluster Raspberry Pi"
|
||||
direction LR
|
||||
|
||||
%% Noeud pi1
|
||||
subgraph "pi1"
|
||||
Pi1["🐧 pi1"]:::k3sServer
|
||||
subgraph "K3s (Server)"
|
||||
Traefik["🚀 Traefik"]:::traefik
|
||||
Cloudflared["☁️ Cloudflared Tunnel"]:::cloudflared
|
||||
end
|
||||
PiHole1["🧹 Pi-hole"]:::dns
|
||||
GiteaRunner1["🏃 Gitea Act Runner"]:::gitea
|
||||
NS1@{ shape: text, label: "nameserver : pi3" }
|
||||
end
|
||||
|
||||
%% Noeud pi2
|
||||
subgraph "pi2"
|
||||
Pi2["🐧 pi2"]:::k3sAgent
|
||||
Gitea["🦊 Gitea"]:::gitea
|
||||
subgraph "K3s (Agent)"
|
||||
CMS["📝 CMS"]:::cms
|
||||
end
|
||||
end
|
||||
|
||||
%% Noeud pi3
|
||||
subgraph "pi3"
|
||||
Pi3["🐧 pi3"]:::k3sAgent
|
||||
PiHole3["🧹 Pi-hole"]:::dns
|
||||
GiteaRunner3["🏃 Gitea Act Runner"]:::gitea
|
||||
subgraph "K3s (Agent)"
|
||||
CrowdSec["🔒 CrowdSec"]:::crowdsec
|
||||
end
|
||||
NS3@{ shape: text, label: "nameserver : pi1" }
|
||||
end
|
||||
end
|
||||
|
||||
%% Sécurité combinée
|
||||
subgraph "Sécurité"
|
||||
Turnstile["🛡️ Cloudflared Turnstile<br>(CAPTCHA)"]:::security
|
||||
BannedIPs["🚫 CrowdSec<br>(IP bannies)"]:::security
|
||||
end
|
||||
end
|
||||
|
||||
%% Connexions Pi-hole → Livebox (DNS)
|
||||
PiHole1 -.->|DNS| Livebox
|
||||
PiHole3 -.->|DNS| Livebox
|
||||
|
||||
%% Internet → Livebox
|
||||
CloudflareService --> Livebox
|
||||
|
||||
%% Gitea Act Runner → Gitea
|
||||
GiteaRunner1 -.->|communication| Gitea
|
||||
GiteaRunner3 -.->|communication| Gitea
|
||||
|
||||
%% Cloudflare → Cloudflared Tunnel → Sécurité → Traefik
|
||||
CloudflareService -.-> Cloudflared
|
||||
Cloudflared --> Turnstile
|
||||
Turnstile --> BannedIPs
|
||||
BannedIPs --> Traefik
|
||||
|
||||
%% Livebox → Sécurité → Traefik
|
||||
Livebox --> Turnstile
|
||||
|
||||
%% Domaines → Gitea/CMS via Traefik
|
||||
Traefik -->|gitea.arcodange.fr| Gitea:::domainFR
|
||||
Traefik -->|gitea.arcodange.lab| Gitea:::domainLAB
|
||||
Traefik -->|cms-rec.arcodange.fr| CMS:::domainFR
|
||||
Traefik -->|www.arcodange.lab| CMS:::domainLAB
|
||||
|
||||
```
|
||||
## CI/CD Architecture
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#333333', 'edgeLabelBackground':'#f0f0f0', 'tertiaryColor': '#e67e22'}}}%%
|
||||
graph TD
|
||||
%% Styles
|
||||
classDef gitea fill:#ffcc99,stroke:#cc9966,color:#333;
|
||||
classDef argocd fill:#99ffcc,stroke:#66cc99,color:#333;
|
||||
classDef vault fill:#ccccff,stroke:#6666cc,color:#333;
|
||||
classDef k3s fill:#ff9999,stroke:#cc0000,color:#333;
|
||||
classDef runner fill:#ffff99,stroke:#cccc00,color:#333;
|
||||
|
||||
%% Components
|
||||
Gitea["Gitea (pi2)"]:::gitea
|
||||
ArgoCD["ArgoCD (k3s)"]:::argocd
|
||||
Vault["Vault (k3s/tools)"]:::vault
|
||||
Runner1["Gitea Act Runner (pi1)"]:::runner
|
||||
Runner2["Gitea Act Runner (pi3)"]:::runner
|
||||
VaultOperator["Vault Secrets Operator (k3s/tools)"]:::vault
|
||||
k3s["k3s Cluster"]:::k3s
|
||||
WebApp["WebApp (k3s)"]:::k3s
|
||||
|
||||
%% Workflow
|
||||
Gitea -->|OIDC Auth| Vault
|
||||
Gitea -->|Trigger CI| Runner1
|
||||
Gitea -->|Trigger CI| Runner2
|
||||
Runner1 -->|Deploy to| k3s
|
||||
Runner2 -->|Deploy to| k3s
|
||||
ArgoCD -->|GitOps Sync| Gitea
|
||||
ArgoCD -->|Deploy Apps| k3s
|
||||
VaultOperator -->|Inject Secrets| WebApp
|
||||
Vault -->|Secrets| VaultOperator
|
||||
WebApp -->|Uses| VaultOperator
|
||||
|
||||
%% Annotations
|
||||
linkStyle 0,1,2,3,4,5,6,7 stroke:#999,stroke-width:1px;
|
||||
|
||||
%% Notes
|
||||
note1["Custom runner image includes<br>.lab CA cert"]
|
||||
Runner1 --> note1
|
||||
Runner2 --> note1
|
||||
|
||||
%% OIDC Workflow
|
||||
note2["OIDC Flow:<br>1. Runner executes vault_oauth__sh_b64<br>2. Obtains ID token from Gitea<br>3. Authenticates with Vault"]
|
||||
Runner1 --> note2
|
||||
Runner2 --> note2
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user