# 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-.conf`, `20-.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 src ` 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.