Files
hcornet f92c9f0a1a
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
remove history
2026-09-30 16:08:36 +02:00

16 KiB

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

# 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 :

# 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.

# 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]
# 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

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 :

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 :

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.