Repository navigation
Expand file tree
/
Copy pathcompose.yaml
More file actions
164 lines (156 loc) · 6.43 KB
/
Copy pathcompose.yaml
File metadata and controls
164 lines (156 loc) · 6.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# CyberSAS : la pile qui tourne sur le serveur public (le VPS).
#
# sasd est tout le VPN : le moteur du tunnel, l'API où les appareils
# s'inscrivent, le relais chiffré entre appareils, le pare-feu du serveur et
# le DNS du réseau.
#
# porte ne fait rien : elle possède l'espace réseau, les ports et les noms,
# et ne redémarre jamais. sasd et Nginx s'y greffent. Si l'un d'eux
# redémarre, l'autre garde son réseau : c'est le conteneur « pause » des
# pods Kubernetes.
#
# Comme Nginx partage l'interface du VPN que sasd crée, il atteint les
# services de la maison par le tunnel, sans qu'aucun port ne soit ouvert
# là-bas.
#
# oauth2-proxy protège les pages web publiées avec la connexion Google.
#
# Durcissement, pour tous : aucune capacité par défaut (seules celles
# nécessaires sont rendues), pas d'élévation de privilèges, système de
# fichiers en lecture seule, images épinglées par empreinte. Plus besoin
# de routage IP sur le serveur : rien ne le traverse en clair.
#
# Rien ne se lance à la main : scripts/sas.sh prépare les secrets et les
# configurations dans etat/, puis démarre la pile.
name: cybersas
x-commun: &commun
restart: unless-stopped
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
services:
porte:
<<: *commun
image: alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6
command: ["sleep", "infinity"]
init: true
read_only: true
ports:
- "${PORT_HTTP:-80}:80"
- "${PORT_HTTPS:-443}:443"
- "${PORT_TUNNEL:-51820}:51820/udp"
networks:
public:
aliases: &noms ["vpn.${DOMAINE}", "auth.${DOMAINE}", "maison.${DOMAINE}"]
interne:
aliases: *noms
sasd:
<<: *commun
build: .
image: cybersas:dev
command: ["sasd"]
network_mode: service:porte
depends_on: [porte]
# NET_ADMIN : l'interface du VPN et le pare-feu. NET_BIND_SERVICE : le
# DNS du réseau, sur le port 53. DAC_READ_SEARCH : lire, sans pouvoir y
# écrire, la configuration que sas.sh laisse en 0600 à son utilisateur.
cap_add: [NET_ADMIN, NET_BIND_SERVICE, DAC_READ_SEARCH]
devices: ["/dev/net/tun:/dev/net/tun"]
read_only: true
tmpfs: [/tmp]
environment:
SAS_DOMAINE: ${DOMAINE}
SAS_POINT: vpn.${DOMAINE}:${PORT_TUNNEL:-51820}
# Des dossiers, pas des fichiers : un fichier monté seul suit son inode,
# et un fichier remplacé (sas.sh écrit à côté puis renomme) resterait
# invisible au conteneur. Retirer quelqu'un de l'équipe n'aurait alors
# aucun effet.
#
# etat/equipe est le seul dossier de la configuration où sasd écrit :
# l'appli de l'admin y change l'équipe (equipe.txt, et emails.txt pour
# oauth2-proxy). Il écrit à côté puis renomme, d'où le dossier. Le
# root du conteneur n'a pas DAC_OVERRIDE : sas.sh ouvre ce dossier-là
# en écriture à « autres » (0733), et etat/, en 0700, le ferme aux
# autres comptes de l'hôte. En retour, sas.sh traite tout ce qui s'y
# trouve comme venant peut-être d'un sasd piraté : pas de lien
# symbolique suivi, rien écrit en place, et un verrou de fichier
# (.verrou) que les deux prennent avant de réécrire l'équipe (voir
# dossier_equipe dans sas.sh). Plus fermé encore : le remappage des
# utilisateurs de Docker (userns-remap), pour que le root de sasd soit
# un uid de l'hôte à lui seul, et ce dossier ouvert à son groupe.
volumes:
- sasd:/var/lib/sasd
- ./etat/sasd:/config:ro
- ./etat/equipe:/equipe
- ./politique:/politique:ro
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/api/v1/sante"]
interval: 5s
retries: 12
nginx:
<<: *commun
image: nginx:1.30.5-alpine@sha256:bf3201ab56f23e5954646379c775d511fc466e9f11376d9725361064ad07ed35
network_mode: service:porte
depends_on: [porte]
# Le maître démarre en root, écrit sa configuration, puis ses ouvriers
# passent sous l'utilisateur nginx. Pas de NET_RAW : un Nginx compromis
# ne peut pas écouter l'interface du VPN qu'il partage.
# DAC_READ_SEARCH : lire la clé TLS, en 0600, sans pouvoir l'écrire.
cap_add: [CHOWN, SETUID, SETGID, NET_BIND_SERVICE, DAC_READ_SEARCH]
read_only: true
tmpfs: [/etc/nginx/conf.d, /var/cache/nginx, /var/run, /tmp]
environment:
DOMAINE: ${DOMAINE}
# L'empreinte SHA-256 du certificat qui signe l'appli publiée, pour
# /.well-known/assetlinks.json (App Links). Un fork signé par sa
# propre clé la change dans .env.
EMPREINTE_APPLI: ${EMPREINTE_APPLI:-C2:8F:5B:BC:18:E0:AD:95:71:53:10:F4:DC:9C:AD:12:84:1B:89:62:4F:04:DE:72:97:AB:80:EE:67:F2:E1:18}
# Seules DOMAINE et EMPREINTE_APPLI sont remplacées dans les modèles :
# $server_name et les autres variables de Nginx restent intactes.
NGINX_ENVSUBST_FILTER: "^(DOMAINE|EMPREINTE_APPLI)$$"
# Les modèles ne sont lus qu'au démarrage de Nginx. sas.sh met ici
# l'empreinte de nginx/ : quand un fichier change, elle change, et
# compose recrée le conteneur au lieu de garder l'ancienne config.
SAS_EMPREINTE_NGINX: ${SAS_EMPREINTE_NGINX:-}
volumes:
- ./nginx/modeles:/etc/nginx/templates:ro
- ./nginx/extraits:/etc/nginx/extraits:ro
- ./nginx/pages:/etc/nginx/pages:ro
- ./etat/tls:/etc/nginx/tls:ro
- journaux-nginx:/var/log/nginx
oauth2-proxy:
<<: *commun
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.4@sha256:b1b2021fe8f4004573e8d690dec6c7bb29cc44364572cf8510a05bf3a0ae2ded
command: ["--config=/config/oauth2-proxy.cfg"]
# Il tourne sous l'utilisateur qui a lancé sas.sh : ses secrets restent
# en 0600, lisibles par cet utilisateur seul sur l'hôte.
user: "${SAS_UID:-65532}:${SAS_GID:-65532}"
read_only: true
volumes:
- ./etat/oauth2-proxy:/config:ro
- ./etat/equipe:/equipe:ro
- ./etat/secrets/google:/secrets:ro
# interne : pour que Nginx le joigne. sortie : pour joindre Google.
networks: [interne, sortie]
networks:
public:
name: cybersas_public
ipam:
config: [{subnet: 172.30.1.0/24}]
# Entre Nginx et oauth2-proxy. Sans passerelle : rien n'en sort par lui.
interne:
name: cybersas_interne
internal: true
ipam:
config: [{subnet: 172.30.2.0/24}]
sortie:
name: cybersas_sortie
ipam:
config: [{subnet: 172.30.3.0/24}]
volumes:
sasd:
journaux-nginx: