remove history
Déploiement Squid / Déclencher le modèle Semaphore (push) Failing after 12s
Qualité du code Ansible / yamllint, ansible-lint et syntaxe du playbook (push) Failing after 11s
Qualité du code Ansible / Validation de squid.conf par Squid lui-même (push) Successful in 1m19s

This commit is contained in:
hcornet committed 2026-09-30 16:08:36 +02:00
commit f92c9f0a1a
78 files changed
+3262

No files matched your search

+397
View File
@@ -0,0 +1,397 @@
# ansible-squid
Déploiement d'un proxy web **Squid** sur Debian, piloté depuis **Semaphore**
et déclenché par **Gitea Actions**.
Le rôle couvre les ACL réseau, le filtrage de domaines, les quatre méthodes
d'authentification (aucune, basic, LDAP/AD, Kerberos), le mode explicite et
l'interception transparente, et le déchiffrement TLS (SSL Bump).
---
## 1. Contraintes assumées
| Contrainte | Conséquence dans le dépôt |
|---|---|
| Le conteneur `semaphore-app` n'embarque aucune collection Ansible | Le rôle n'utilise que `ansible.builtin`. Pas de `requirements.yml`, rien à installer. |
| Le paquet Debian `squid` n'est pas compilé avec OpenSSL | Quand `squid_ssl_bump` vaut `true`, le rôle installe `squid-openssl` à la place. APT remplace l'un par l'autre. |
| Une configuration Squid invalide casse le service | `squid.conf` est déployé avec `validate: squid -k parse`, et la CI rejoue ce contrôle sur six scénarios avant tout déploiement. |
| Un port intercepté ne peut pas porter d'authentification | Les ports interceptés sont nommés et exemptés automatiquement (ACL `myportname`). Le contrôle y reste basé sur l'adresse source. |
---
## 2. Arborescence
```
ansible.cfg configuration Ansible (callback default + result_format yaml)
site.yml playbook unique, groupe « squid »
inventory/hosts.yml inventaire d'exemple
group_vars/all/main.yml variables communes
group_vars/squid/main.yml paramétrage du proxy
group_vars/squid/vault.yml.example secrets à chiffrer (ou à mettre dans Semaphore)
examples/auth-*.yml un exemple complet par méthode d'authentification
roles/squid/
defaults/main.yml toutes les variables surchargeables
vars/main.yml chemins des helpers Debian, raccourcis calculés
tasks/assert.yml validation des variables avant toute action
tasks/install.yml paquets, répertoires, sauvegarde de la conf d'origine
tasks/ssl_bump.yml autorité de certification et base de certificats générés
tasks/auth.yml fichiers de la méthode retenue, nettoyage des autres
tasks/configure.yml listes de filtrage puis squid.conf (avec validation)
tasks/service.yml cache éventuel, activation, flush des handlers
tasks/verify.yml ports, syntaxe, requête de bout en bout
templates/squid.conf.j2 gabarit principal
templates/auth/*.j2 un fragment par méthode
templates/lists/domains.txt.j2
tests/ rendu hors ligne et scénarios utilisés par la CI
scripts/openbao-env.sh lecture des secrets Semaphore dans OpenBao
scripts/trigger-semaphore.sh déclenchement et suivi de la tâche Semaphore
.gitea/workflows/lint.yml qualité + validation de la configuration générée
.gitea/workflows/deploy.yml déploiement via Semaphore
```
---
## 3. Prérequis
**Sur la cible** : Debian 12 ou 13, accès SSH avec un compte disposant de
`sudo`, sortie Internet.
**Côté Semaphore** (projet `Production`, celui déjà utilisé pour Centreon) :
1. Clé d'accès Gitea existante (`svc-semaphore`, type « Login with password »).
2. Dépôt : `https://gitea.tips-of-mine.com/Tips-Of-Mine/ansible-squid.git`,
branche `main`.
3. Inventaire de type `file` pointant sur `inventory/hosts.yml`, avec la clé
SSH du compte `ansible`.
4. Groupe de variables (menu **Variable Groups** depuis la 2.19) nommé
`Squid`, pour les secrets : `squid_ldap_bind_password`,
`squid_kerberos_keytab_b64`, `squid_ssl_ca_key_content`.
5. Modèle `Déploiement Squid` sur `site.yml`.
**Côté Gitea** : trois secrets de dépôt seulement — `OPENBAO_ADDR`,
`OPENBAO_ROLE_ID`, `OPENBAO_SECRET_ID`. Tout le reste vit dans OpenBao sous
`secret/squid` : `semaphore_url`, `semaphore_token`, `semaphore_project_id`,
`semaphore_template_id`.
---
## 4. Démarrage rapide
```bash
# 1. Renseigner l'inventaire
$EDITOR inventory/hosts.yml
# 2. Ajuster le paramétrage
$EDITOR group_vars/squid/main.yml
# 3. Déployer
ansible-playbook site.yml
```
Depuis Semaphore, lancer le modèle `Déploiement Squid`. Depuis Gitea,
onglet **Actions** → *Déploiement Squid* → **Run workflow**, avec au besoin
un `--limit`, des étiquettes (`install`, `ssl`, `auth`, `config`, `service`,
`verify`) ou le mode simulation.
---
## 5. Variables principales
| Variable | Défaut | Rôle |
|---|---|---|
| `squid_mode` | `both` | `explicit`, `intercept` ou les deux |
| `squid_http_port` | `3128` | port du proxy explicite |
| `squid_intercept_http_port` | `3129` | port d'interception HTTP |
| `squid_intercept_https_port` | `3130` | port d'interception HTTPS (SSL Bump) |
| `squid_allowed_networks` | `10.0.4.0/24` | réseaux autorisés |
| `squid_denied_sources` | `[]` | adresses refusées avant toute autre règle |
| `squid_filter_mode` | `blocklist` | `blocklist` ou `allowlist` |
| `squid_denied_domains` | `[]` | domaines interdits |
| `squid_allowed_domains` | `[]` | domaines autorisés (obligatoire en `allowlist`) |
| `squid_auth_method` | `none` | `none`, `basic`, `ldap`, `kerberos` |
| `squid_ssl_bump` | `false` | déchiffrement TLS |
| `squid_no_bump_domains` | 3 entrées | domaines relayés sans déchiffrement |
| `squid_cache_enabled` | `false` | cache disque |
| `squid_log_to_syslog` | `false` | journalisation vers syslog plutôt qu'en fichier |
| `squid_verify` | `true` | contrôles de fin de déploiement |
La liste exhaustive et commentée est dans `roles/squid/defaults/main.yml`.
Un point de vigilance : en mode `allowlist`, tout ce qui n'est pas dans
`squid_allowed_domains` est refusé, y compris les domaines de mise à jour du
système. Le rôle refuse d'ailleurs de partir si la liste est vide.
---
## 6. Les quatre authentifications
Chaque méthode dispose d'un exemple prêt à copier dans `group_vars/squid/` :
- `examples/auth-none.yml` — contrôle par adresse source uniquement. Seule
méthode compatible avec l'interception.
- `examples/auth-basic.yml` — comptes locaux. Les condensats se génèrent
avec `openssl passwd -apr1` (saisie interactive, rien dans l'historique).
- `examples/auth-ldap.yml` — annuaire AD, avec restriction optionnelle par
groupe. Le mot de passe du compte de service est lu dans un fichier (`-W`)
et n'apparaît jamais dans `ps`.
- `examples/auth-kerberos.yml` — SSO domaine. Contient les commandes
`setspn` et `ktpass` à passer côté contrôleur de domaine, et l'encodage du
keytab en base64.
Changer de méthode suffit : `tasks/auth.yml` supprime les fichiers de celles
qui ne sont plus retenues.
---
## 7. SSL Bump
Par défaut le rôle génère une autorité auto-signée
(`squid_ssl_ca_self_signed: true`). En production, fournir plutôt une
autorité issue de ta PKI via `squid_ssl_ca_cert_content` et
`squid_ssl_ca_key_content`, alimentées par Semaphore ou OpenBao.
Le certificat public est déposé sur la cible dans
`/usr/local/share/ca-certificates/squid-proxy-ca.crt`. **Il doit être
distribué à tous les postes clients**, sinon chaque site HTTPS produira une
erreur de certificat. Sur un parc AD, une GPO « Autorités de certification
racines de confiance » fait l'affaire.
Les domaines de `squid_no_bump_domains` sont relayés sans déchiffrement
(`ssl_bump splice`) : à alimenter avec la banque, la santé, les impôts, et
tout ce qui pratique l'épinglage de certificat. Les domaines interdits sont
coupés dès le SNI (`ssl_bump terminate`), avant même le déchiffrement.
---
## 8. Mode interception
Le rôle configure Squid ; la redirection du trafic reste à faire sur le
firewall. Sur OPNsense, deux règles de NAT de type *port forward* depuis le
réseau client :
| Protocole | Port source | Destination | Redirection |
|---|---|---|---|
| TCP | 80 | toute | `IP_du_proxy:3129` |
| TCP | 443 | toute | `IP_du_proxy:3130` |
Le proxy lui-même doit être exclu de ces règles, faute de quoi le trafic
sortant de Squid revient sur Squid.
Pour le mode explicite, le plus simple reste un fichier PAC distribué par
GPO, ou la variable d'environnement `http_proxy` sur les serveurs Linux.
---
## 9. Vérifications
Le rôle termine par une série de contrôles (`squid_verify`) : ouverture des
ports, `squid -k parse` sur la configuration en place, et une requête réelle
à travers le proxy quand l'authentification le permet.
À la main, sur la cible :
```bash
# Version et syntaxe
squid -k parse >/dev/null && echo "configuration valide"
# Test explicite
curl -sS -o /dev/null -w '%{http_code}\n' -x http://127.0.0.1:3128 http://www.debian.org/
# Test d'un domaine interdit (403 attendu)
curl -sS -o /dev/null -w '%{http_code}\n' -x http://127.0.0.1:3128 http://doubleclick.net/
# Dix dernières lignes du journal, sans le détail des requêtes internes
tail -n 10 /var/log/squid/access.log
```
Deux comportements normaux à ne pas chercher à corriger :
- `WARNING: HTTP requires the use of Via` au démarrage : conséquence directe
de `via off`, choisi pour ne pas annoncer le proxy aux serveurs distants.
- `logfile_rotate 0` : la rotation est confiée à logrotate, qui appelle
`squid -k rotate`. Le fichier `/etc/squid/conf.d/` n'est volontairement
pas inclus, la configuration est entièrement pilotée par le rôle.
---
## 10. Intégration continue
`lint.yml` (à chaque push et chaque PR) :
1. `yamllint` sur tout le dépôt ;
2. `ansible-lint` en profil **production** ;
3. `ansible-playbook --syntax-check` ;
4. rendu des six scénarios de `tests/scenarios/` puis validation de chaque
`squid.conf` par `squid -k parse`, avec un vrai Squid installé dans le
job. C'est ce contrôle qui a détecté, avant livraison, une inclusion de
gabarit qui collait deux directives sur la même ligne.
`deploy.yml` (push sur `main` touchant le code Ansible, ou lancement
manuel) : lecture des secrets dans OpenBao par AppRole, déclenchement du
modèle Semaphore, puis attente du résultat. Le job échoue si la tâche
Semaphore échoue.
Les jetons ne sont jamais affichés : `openbao-env.sh` les masque
(`::add-mask::`) et n'écrit que l'URL Semaphore visée.
---
## 11. Pièges connus
- **Bit d'exécution des scripts** : si les fichiers de `scripts/` arrivent
sans droit d'exécution depuis Windows, corriger une fois pour toutes avec
`git update-index --chmod=+x scripts/openbao-env.sh scripts/trigger-semaphore.sh`.
Le workflow applique de toute façon un `chmod +x` avant appel.
- **Fins de ligne** : `.gitattributes` force le LF sur les `.yml`, `.j2`,
`.sh` et `.cfg`.
- **Première exécution avec SSL Bump** : l'installation de `squid-openssl`
remplace le paquet `squid` et redémarre le service. Prévoir une courte
coupure si le proxy est déjà en production.
---
## 12. Modèle par serveur (fragments conf.d)
Chaque machine autorisée possède une fiche dans `servers/`, chaque
regroupement une fiche dans `groups/`. Le rôle en dérive un fichier par
entité dans `/etc/squid/conf.d/`, chargé par le socle **avant** le
`http_access deny all` final.
```yaml
# servers/srv-warda-01.yml
name: srv-warda-01
env: prod
sources:
- srv-warda-01.tips-of-mine.local
groups: [windows] # documentaire : l'appartenance fait foi côté groupe
rules:
- domains: [javadl.oracle.com, www.java.com]
ports: [https]
```
```yaml
# groups/windows.yml
name: windows
members: # seules ces machines bénéficient du groupe
- srv-warda-01.tips-of-mine.local
rules:
- domains: [download.windowsupdate.com]
ports: [http, https]
```
Fichiers produits : `00-ports.conf` (catalogue de ports partagé),
`10-groupe-<nom>.conf`, `20-<serveur>.conf`.
### Garantie de non-accès
Une machine n'obtient un flux que si son FQDN figure explicitement dans une
ACL `src`. Trois mécanismes la protègent :
1. `squid_allow_localnet` bascule **automatiquement à false** dès qu'une
fiche existe : plus aucune autorisation globale par réseau.
2. Le rôle refuse de déployer si un membre de groupe n'a pas de fiche
serveur correspondante.
3. `http_access deny all` clôt le socle, après l'`include`.
### Nommage des ACL de destination
Le nom est dérivé du domaine lui-même : `go.microsoft.com` devient
`go_microsoft_com`, `.datadoghq.com` devient `_datadoghq_com`. Même domaine
= toujours le même nom et la même valeur, donc deux fiches ne peuvent pas
définir la même ACL avec des contenus différents. C'est ce qui se produit
aujourd'hui entre plusieurs fragments écrits à la main.
### Ports
Le catalogue vit dans `squid_port_acls` et n'est rendu qu'une fois, dans
`00-ports.conf`. Les fiches ne référencent que des clés (`https`, `sftp`,
`8443`…) ; une clé absente du catalogue fait échouer le déploiement avec la
liste des ports fautifs.
### Reprise de l'existant
```bash
python3 scripts/import-conf-d.py /etc/squid/conf.d --sortie servers/
```
Le script produit une fiche par fragment et un rapport d'anomalies. Passé
sur les cinq fichiers de production fournis, il a relevé : une ACL
référencée mais jamais définie (Squid refuse de démarrer dans ce cas), un
domaine terminé par `/`, une adresse IP en `dstdomain`, une ligne tronquée
et plusieurs ACL déclarées deux fois. `--rapport-seul` fait l'audit sans
rien écrire.
### Points vérifiés sur Squid 6.14
- Les noms d'ACL sont **insensibles à la casse** : `srv-warda-02_prod` et
`srv-warda-02_prod` désignent la même ACL.
- Une ACL référencée sans être définie est **fatale**, pas un avertissement.
- `dns_v4_first` est **obsolète** depuis Squid 6 et génère une erreur au
démarrage : `squid_dns_v4_first` est donc à `false` par défaut.
- `acl <nom> src <fqdn>` impose une résolution DNS au démarrage. Si un nom
ne résout plus, Squid **refuse de démarrer**. Des sources en adresses IP
suppriment cette dépendance.
---
## 13. Rapports d'utilisation (SARG)
Désactivé par défaut. Activation :
```yaml
squid_reports_enabled: true
```
Le rôle installe `sarg` depuis les dépôts Debian, génère `/etc/sarg/sarg.conf`,
un script `/usr/local/sbin/sarg-rapport.sh` et les entrées `/etc/cron.d/`
correspondantes, puis publie les rapports par un nginx local.
### Périodes et consultation
Trois périodes, réglables par `squid_sarg_periods` : `daily` (la veille),
`weekly` (7 jours glissants), `monthly` (mois précédent). Les rapports
atterrissent dans `/var/www/sarg/{quotidien,hebdomadaire,mensuel}` et sont
consultables sur le port 8081, **restreint aux réseaux de
`squid_sarg_web_allowed`** — par défaut les mêmes que `squid_allowed_networks`.
Aucune exposition publique, pas d'indexation, pas de cache navigateur.
Le script passe à SARG le journal courant **et** `access.log.1` s'il existe,
pour couvrir une rotation intervenue entre-temps.
### Contenu des rapports
`topusers`, `topsites`, `sites_users`, `users_sites`, `denied` et
`auth_failures`. Sans authentification, `records_without_userid: ip` rattache
chaque ligne à l'adresse source ; avec LDAP ou Kerberos, les rapports sont
nominatifs.
Le rapport `denied` est le plus utile au quotidien : il liste les tentatives
refusées, donc les machines qui cherchent à sortir sans y être autorisées.
### Points vérifiés sur SARG 2.4.0
- La directive `language` n'existe plus et provoque `Unknown option language`.
Elle a été retirée du gabarit ; `charset` suffit.
- Le type de rapport `date_time` échoue à créer le répertoire de son graphique
(`Cannot open .../graph.html`). Il est volontairement absent des valeurs par
défaut. L'ajouter à `squid_sarg_report_type` fait réapparaître l'erreur.
- SARG ne crée pas les sous-répertoires de période : le rôle s'en charge.
### Vie privée
Avec le SSL Bump actif, les rapports contiennent les URL complètes, pas
seulement les noms de domaine. Deux leviers :
```yaml
squid_sarg_privacy: true # masque les URL, conserve les agrégats
squid_sarg_exclude_hosts: # domaines absents des rapports
- .ameli.fr
squid_sarg_exclude_users: []
squid_sarg_keep: 90 # nombre de rapports conservés par période
```
En entreprise, un rapport nominatif d'historique de navigation relève du suivi
individuel : information des personnes, inscription au registre des traitements
et durée de conservation définie sont à cadrer avant la mise en service.