first
This commit is contained in:
1 parent
c551c87ebf
commit
0e24df336c
28 files changed
+899
-415
No files matched your search
@@ -0,0 +1,6 @@
|
||||
---
|
||||
profile: production
|
||||
offline: true
|
||||
exclude_paths:
|
||||
- .gitea/
|
||||
- collections/ansible_collections/
|
||||
@@ -0,0 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
*.j2 text eol=lf
|
||||
*.sh text eol=lf
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
# Gitea valide, Semaphore exécute : aucun playbook n'est joué ici.
|
||||
# Semaphore suit la branche « valide », que seul ce workflow fait avancer,
|
||||
# depuis « main » et après un contrôle réussi.
|
||||
name: Ansible - controle du code
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Récupérer le dépôt
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Installer les outils
|
||||
run: |
|
||||
pip3 install --quiet --break-system-packages ansible-core ansible-lint yamllint
|
||||
ansible-galaxy collection install -r collections/requirements.yml
|
||||
|
||||
- name: yamllint
|
||||
run: yamllint --strict .
|
||||
|
||||
- name: ansible-lint
|
||||
run: ansible-lint
|
||||
|
||||
- name: Vérification de syntaxe du playbook
|
||||
run: |
|
||||
ansible-playbook site.yml --syntax-check
|
||||
ansible-playbook site.yml --list-tasks > /dev/null
|
||||
|
||||
- name: Rendu de daemon.json (valeurs par défaut et toutes options)
|
||||
run: |
|
||||
set -e
|
||||
d=roles/docker_hardening
|
||||
rendre() {
|
||||
ansible localhost -m ansible.builtin.template \
|
||||
-a "src=$d/templates/daemon.json.j2 dest=$1" \
|
||||
-e @$d/defaults/main.yml -e @group_vars/all/main.yml ${2:+-e "$2"} > /dev/null
|
||||
python3 -m json.tool "$1" > /dev/null && echo "$1 : JSON valide"
|
||||
}
|
||||
rendre /tmp/daemon-defaut.json
|
||||
rendre /tmp/daemon-complet.json '{"docker_hardening_default_address_pools":[{"base":"172.20.0.0/14","size":24}],
|
||||
"docker_hardening_insecure_registries":["registry.example"],"docker_hardening_registry_mirrors":["https://mirror.example"],
|
||||
"docker_hardening_userns_remap":"default","docker_hardening_data_root":"/srv/docker"}'
|
||||
|
||||
- name: Rendu et syntaxe du script DOCKER-USER
|
||||
run: |
|
||||
set -e
|
||||
d=roles/docker_hardening
|
||||
ansible localhost -m ansible.builtin.template \
|
||||
-a "src=$d/templates/docker-user-firewall.sh.j2 dest=/tmp/docker-user-firewall" \
|
||||
-e @$d/defaults/main.yml -e @group_vars/all/main.yml \
|
||||
-e '{"docker_hardening_firewall_interfaces":["eth0"],"docker_hardening_firewall_log_drops":true,
|
||||
"docker_hardening_firewall_public_ports":[{"port":443,"proto":"tcp"},{"port":443,"proto":"udp"}],
|
||||
"docker_hardening_firewall_allowed_sources_v6":["fd00::/8"]}' > /dev/null
|
||||
sh -n /tmp/docker-user-firewall && echo "Script DOCKER-USER : syntaxe valide"
|
||||
|
||||
promouvoir:
|
||||
# Avance la branche « valide » (suivie par Semaphore) sur le commit validé de « main ».
|
||||
needs: lint
|
||||
if: gitea.ref == 'refs/heads/main' && gitea.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Récupérer le dépôt
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITEA_TOKEN_PROMOTION }}
|
||||
|
||||
- name: Pousser main vers valide
|
||||
run: |
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "[email protected]"
|
||||
git push origin HEAD:refs/heads/valide
|
||||
echo "Branche valide -> $(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Déclencher Semaphore (facultatif)
|
||||
if: ${{ vars.SEMAPHORE_URL != '' }}
|
||||
env:
|
||||
SEMAPHORE_URL: ${{ vars.SEMAPHORE_URL }}
|
||||
SEMAPHORE_PROJECT_ID: ${{ vars.SEMAPHORE_PROJECT_ID }}
|
||||
SEMAPHORE_TEMPLATE_ID: ${{ vars.SEMAPHORE_TEMPLATE_ID }}
|
||||
SEMAPHORE_TOKEN: ${{ secrets.SEMAPHORE_TOKEN }}
|
||||
run: |
|
||||
code=$(curl -sS -o /tmp/reponse.json -w '%{http_code}' \
|
||||
-H "Authorization: Bearer ${SEMAPHORE_TOKEN}" -H "Content-Type: application/json" \
|
||||
-X POST "${SEMAPHORE_URL}/api/project/${SEMAPHORE_PROJECT_ID}/tasks" \
|
||||
-d "{\"template_id\": ${SEMAPHORE_TEMPLATE_ID}, \"dry_run\": true, \"message\": \"Gitea ${GITHUB_SHA::7}\"}")
|
||||
echo "Semaphore : HTTP ${code}, tâche $(sed -n 's/.*"id":\([0-9]*\).*/\1/p' /tmp/reponse.json | head -1)"
|
||||
[ "$code" = "201" ] || [ "$code" = "200" ]
|
||||
+2
-415
@@ -1,416 +1,3 @@
|
||||
# ---> VisualStudio
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
*.retry
|
||||
.cache/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
extends: default
|
||||
|
||||
ignore: |
|
||||
.gitea/
|
||||
collections/ansible_collections/
|
||||
|
||||
rules:
|
||||
line-length:
|
||||
max: 160
|
||||
truthy:
|
||||
allowed-values: ["true", "false"]
|
||||
comments:
|
||||
min-spaces-from-content: 1
|
||||
comments-indentation: false
|
||||
braces:
|
||||
max-spaces-inside: 1
|
||||
octal-values:
|
||||
forbid-implicit-octal: true
|
||||
forbid-explicit-octal: true
|
||||
@@ -0,0 +1,33 @@
|
||||
# Chaîne Gitea → Semaphore
|
||||
|
||||
Gitea valide, Semaphore exécute. Semaphore ne suit pas `main` mais la branche
|
||||
**`valide`**, que seul le workflow Gitea fait avancer :
|
||||
|
||||
```
|
||||
push sur main ──► lint : yamllint, ansible-lint (profil production), syntax-check,
|
||||
list-tasks, rendu de daemon.json (JSON valide, 2 scénarios),
|
||||
rendu du script DOCKER-USER (sh -n)
|
||||
│ succès
|
||||
▼
|
||||
promouvoir : main ──► valide (+ déclenchement Semaphore, facultatif)
|
||||
│
|
||||
▼
|
||||
Semaphore (dépôt sur la branche « valide ») exécute site.yml
|
||||
```
|
||||
|
||||
Un commit qui casse le contrôle n'atteint jamais `valide`, donc jamais Semaphore.
|
||||
|
||||
## Côté Gitea
|
||||
|
||||
- secret `GITEA_TOKEN_PROMOTION` : jeton d'un compte autorisé à pousser sur `valide`
|
||||
(protéger la branche `valide` pour que seul ce compte puisse y écrire) ;
|
||||
- facultatif, pour lancer Semaphore après promotion : variables `SEMAPHORE_URL`,
|
||||
`SEMAPHORE_PROJECT_ID`, `SEMAPHORE_TEMPLATE_ID` et secret `SEMAPHORE_TOKEN`.
|
||||
La tâche est créée en **dry run** (`--check`).
|
||||
|
||||
## Côté Semaphore
|
||||
|
||||
- dépôt : ce projet, branche `valide` (clé « Login with password » du compte `svc-semaphore`) ;
|
||||
- inventaire : `inventory/hosts.yml`, clé SSH root du compte `ansible` ;
|
||||
- modèle de tâche : `site.yml`, premier passage en `--check --diff` ;
|
||||
- collections : `ansible.posix` (voir `collections/requirements.yml`).
|
||||
@@ -1,2 +1,90 @@
|
||||
# ansible-docker
|
||||
|
||||
Installation de **Docker Engine** et du **plugin Docker Compose** depuis le dépôt officiel
|
||||
Docker, puis durcissement de l'hôte, sur Debian 12/13 et Ubuntu 22.04/24.04.
|
||||
|
||||
Gitea valide le code, **Semaphore** l'exécute (branche `valide`) : voir [CICD.md](CICD.md).
|
||||
|
||||
## Arborescence
|
||||
|
||||
```
|
||||
ansible-docker/
|
||||
├── ansible.cfg
|
||||
├── site.yml # playbook unique (groupe d'inventaire « docker »)
|
||||
├── collections/requirements.yml # ansible.posix
|
||||
├── inventory/hosts.yml
|
||||
├── group_vars/all/main.yml # valeurs du lab (DNS, réseaux autorisés)
|
||||
├── roles/
|
||||
│ ├── docker_install/ # dépôt officiel, clé vérifiée, paquets, services
|
||||
│ └── docker_hardening/ # daemon.json, groupe docker, DOCKER-USER, auditd, contrôles
|
||||
└── .gitea/workflows/ansible-lint.yml
|
||||
```
|
||||
|
||||
Aucun secret n'est nécessaire : pas de `vault.yml` dans ce projet.
|
||||
|
||||
## Ce que fait chaque rôle
|
||||
|
||||
**docker_install**
|
||||
- refuse toute distribution non prise en charge ;
|
||||
- retire les paquets en conflit (`docker.io`, `containerd`, `runc`, `podman-docker`…,
|
||||
ainsi que les homonymes `docker`/`wmdocker`, un dock pour gestionnaire de fenêtres) ;
|
||||
- télécharge la clé du dépôt Docker et **vérifie son empreinte** avant de déclarer le dépôt
|
||||
(format deb822, `/etc/apt/sources.list.d/docker.sources`, l'ancien `docker.list` est retiré) ;
|
||||
- installe `docker-ce`, `docker-ce-cli`, `containerd.io`, `docker-buildx-plugin`,
|
||||
`docker-compose-plugin`, sans paquets recommandés (pas de `docker-ce-rootless-extras`) ;
|
||||
- fige la version si `docker_install_version` est renseignée.
|
||||
|
||||
**docker_hardening**
|
||||
|
||||
| Mesure | Détail |
|
||||
|---|---|
|
||||
| `daemon.json` | `icc: false`, `no-new-privileges`, `live-restore`, `userland-proxy: false`, rotation des journaux, `experimental: false`, validé par `dockerd --validate` avant écriture |
|
||||
| Liaison par défaut | `ip: 127.0.0.1` : un `-p 8080:80` n'expose plus rien au réseau |
|
||||
| Groupe `docker` | réduit à `docker_hardening_group_members` (vide par défaut) : tout autre membre est retiré |
|
||||
| Ports publiés | chaîne `DOCKER-USER` (IPv4 et IPv6) : seules les sources autorisées et les ports déclarés publics passent |
|
||||
| Audit | règles auditd sur les binaires, la configuration et les unités Docker/containerd |
|
||||
| Routage | `net.ipv4.ip_forward=1` posé en dernier (`99-zz-docker.conf`) |
|
||||
| Contrôle final | seccomp, AppArmor, no-new-privileges, live-restore, socket `0660 root:docker`, aucune API sur 2375/2376 |
|
||||
|
||||
## Variables à connaître
|
||||
|
||||
| Variable | Défaut | Rôle |
|
||||
|---|---|---|
|
||||
| `docker_install_version` | `""` | version d'Engine à installer et figer (ex. `29.1.5`) |
|
||||
| `docker_hardening_default_bind_ip` | `127.0.0.1` | IP de publication quand le compose n'en précise pas |
|
||||
| `docker_hardening_firewall_allowed_sources` | `10.0.4.0/24`, `10.0.5.0/24` | sources autorisées sur tous les ports publiés |
|
||||
| `docker_hardening_firewall_public_ports` | `[]` | ports ouverts à toute source, ex. `{ port: 443, proto: tcp }` |
|
||||
| `docker_hardening_firewall_interfaces` | interface de la route par défaut | interfaces d'entrée filtrées |
|
||||
| `docker_hardening_group_members` | `[]` | comptes autorisés dans le groupe `docker` |
|
||||
| `docker_hardening_dns` | `10.0.4.2` | résolveurs transmis aux conteneurs |
|
||||
| `docker_hardening_default_address_pools` | `[]` | plages des réseaux Docker |
|
||||
| `docker_hardening_userns_remap` | `""` | remappage des UID, **à décider avant tout déploiement de conteneurs** |
|
||||
| `docker_hardening_audit_watch_data_root` | `false` | audit de `/var/lib/docker` (très verbeux) |
|
||||
|
||||
La liste complète et commentée est dans `roles/*/defaults/main.yml`.
|
||||
|
||||
## Points d'attention
|
||||
|
||||
- **Publication des ports** : avec `ip: 127.0.0.1`, un service à ouvrir au réseau doit être
|
||||
publié explicitement dans son compose, par exemple `"0.0.0.0:443:443"` pour Traefik, puis
|
||||
déclaré dans `docker_hardening_firewall_public_ports` s'il doit être joignable hors des
|
||||
réseaux autorisés.
|
||||
- **`no-new-privileges`** : les images qui s'appuient sur `sudo`/`su` ou un binaire setuid
|
||||
à l'intérieur du conteneur échouent. Le lever pour un seul service :
|
||||
`security_opt: ["no-new-privileges:false"]`.
|
||||
- **Redémarrage du démon** : un changement de `daemon.json` redémarre Docker. Grâce à
|
||||
`live-restore`, les conteneurs continuent de tourner, mais les options réseau (`icc`,
|
||||
plages d'adresses…) ne s'appliquent qu'aux réseaux et conteneurs recréés.
|
||||
- **Pare-feu de l'hôte** : si un autre rôle (par exemple le durcissement Linux) gère nftables,
|
||||
sa configuration ne doit pas contenir `flush ruleset` ni une politique `drop` sur la chaîne
|
||||
`forward` : elle effacerait ou bloquerait les règles de Docker.
|
||||
- **auditd immuable** : si une autre configuration pose `-e 2`, `augenrules --load` échoue
|
||||
jusqu'au prochain redémarrage.
|
||||
|
||||
## Exécution (Semaphore)
|
||||
|
||||
Modèle de tâche sur `site.yml`, inventaire `inventory/hosts.yml`. Premier passage conseillé en
|
||||
`--check --diff` : sur un hôte vierge, le rôle de durcissement s'arrête proprement après
|
||||
l'installation simulée, puisque Docker n'existe pas encore.
|
||||
|
||||
Tags : `docker_install`, `docker_hardening`.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
[defaults]
|
||||
inventory = inventory/hosts.yml
|
||||
roles_path = roles
|
||||
collections_path = ~/.ansible/collections:./collections
|
||||
remote_user = root
|
||||
interpreter_python = /usr/bin/python3
|
||||
host_key_checking = True
|
||||
stdout_callback = ansible.builtin.default
|
||||
callback_result_format = yaml
|
||||
callbacks_enabled = ansible.posix.profile_tasks
|
||||
forks = 5
|
||||
retry_files_enabled = False
|
||||
|
||||
# Pas de section [privilege_escalation] : les cibles n'ont pas sudo,
|
||||
# Ansible s'y connecte directement en root par clé.
|
||||
|
||||
[ssh_connection]
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=300s -o StrictHostKeyChecking=accept-new
|
||||
pipelining = True
|
||||
@@ -0,0 +1 @@
|
||||
ansible_collections/
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
collections:
|
||||
- name: ansible.posix
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
ansible_managed: "Fichier géré par Ansible (ansible-docker) - toute modification manuelle sera écrasée"
|
||||
|
||||
# Résolveur du lab, transmis aux conteneurs
|
||||
docker_hardening_dns:
|
||||
- 10.0.4.2
|
||||
|
||||
# Réseaux autorisés à joindre les ports publiés par les conteneurs (chaîne DOCKER-USER)
|
||||
docker_hardening_firewall_allowed_sources:
|
||||
- 10.0.4.0/24
|
||||
- 10.0.5.0/24
|
||||
|
||||
# Ports publiés ouverts à toute source (à définir par hôte, ex. Traefik) :
|
||||
# docker_hardening_firewall_public_ports:
|
||||
# - { port: 443, proto: tcp }
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
all:
|
||||
vars:
|
||||
ansible_user: root
|
||||
children:
|
||||
docker:
|
||||
hosts:
|
||||
srv-deb-01:
|
||||
ansible_host: 10.0.5.21
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
# ---------------------------------------------------------------------------
|
||||
# Démon Docker (/etc/docker/daemon.json)
|
||||
# ---------------------------------------------------------------------------
|
||||
# Adresse d'écoute par défaut des ports publiés sans IP explicite (-p 8080:80).
|
||||
# "127.0.0.1" : rien n'est exposé au réseau par accident ; un port à ouvrir au LAN
|
||||
# doit être publié explicitement (ex. "0.0.0.0:443:443" pour Traefik).
|
||||
docker_hardening_default_bind_ip: "127.0.0.1"
|
||||
|
||||
# Pas de communication libre entre conteneurs du bridge par défaut
|
||||
docker_hardening_icc: false
|
||||
# Les conteneurs survivent au redémarrage du démon
|
||||
docker_hardening_live_restore: true
|
||||
# Publication des ports par iptables plutôt que par le processus docker-proxy
|
||||
docker_hardening_userland_proxy: false
|
||||
# Interdit l'acquisition de privilèges (setuid, setcap) dans les conteneurs
|
||||
docker_hardening_no_new_privileges: true
|
||||
|
||||
# Rotation des journaux des conteneurs
|
||||
docker_hardening_log_max_size: "10m"
|
||||
docker_hardening_log_max_file: 5
|
||||
|
||||
# Résolveurs transmis aux conteneurs ([] = ceux de l'hôte)
|
||||
docker_hardening_dns: []
|
||||
# Plages d'adresses des réseaux Docker ([] = plages par défaut)
|
||||
# Exemple : [{ base: "172.20.0.0/14", size: 24 }]
|
||||
docker_hardening_default_address_pools: []
|
||||
# Registres sans TLS vérifié (à éviter) et miroirs
|
||||
docker_hardening_insecure_registries: []
|
||||
docker_hardening_registry_mirrors: []
|
||||
# Remappage des UID ("" = désactivé, "default" = compte dockremap).
|
||||
# À décider AVANT d'héberger des conteneurs : l'activer masque les images,
|
||||
# conteneurs et volumes existants et impose de revoir les droits des bind mounts.
|
||||
docker_hardening_userns_remap: ""
|
||||
# Emplacement des données ("" = /var/lib/docker)
|
||||
docker_hardening_data_root: ""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Accès au démon
|
||||
# ---------------------------------------------------------------------------
|
||||
# Membres autorisés du groupe docker (équivalent root) : tout autre membre est retiré
|
||||
docker_hardening_group_members: []
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Filtrage des ports publiés (chaîne DOCKER-USER)
|
||||
# ---------------------------------------------------------------------------
|
||||
# Docker insère ses propres règles iptables : sans ce filtrage, un port publié
|
||||
# sur 0.0.0.0 est joignable depuis tout le réseau, quel que soit le pare-feu de l'hôte.
|
||||
docker_hardening_firewall_enabled: true
|
||||
# Interfaces d'entrée filtrées (par défaut : celle de la route par défaut)
|
||||
docker_hardening_firewall_interfaces: >-
|
||||
{{ [ansible_facts['default_ipv4']['interface']]
|
||||
if ansible_facts['default_ipv4']['interface'] is defined else [] }}
|
||||
# Sources autorisées à joindre tous les ports publiés
|
||||
docker_hardening_firewall_allowed_sources: []
|
||||
docker_hardening_firewall_allowed_sources_v6: []
|
||||
# Ports publiés ouverts à toute source : [{ port: 443, proto: tcp }]
|
||||
docker_hardening_firewall_public_ports: []
|
||||
# Journaliser les paquets rejetés (limité à 5/min)
|
||||
docker_hardening_firewall_log_drops: false
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Audit (auditd)
|
||||
# ---------------------------------------------------------------------------
|
||||
docker_hardening_audit_enabled: true
|
||||
# Surveiller aussi le répertoire de données : très verbeux sur un hôte chargé
|
||||
docker_hardening_audit_watch_data_root: false
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
# Ordre d'exécution = ordre de déclaration : le démon d'abord, le filtrage ensuite.
|
||||
- name: Recharger systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Redémarrer Docker
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: restarted
|
||||
|
||||
- name: Recharger les règles d'audit
|
||||
ansible.builtin.command:
|
||||
cmd: augenrules --load
|
||||
changed_when: true
|
||||
|
||||
- name: Appliquer le filtrage DOCKER-USER
|
||||
ansible.builtin.systemd:
|
||||
name: docker-user-firewall
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
ignore_errors: "{{ ansible_check_mode }}"
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: Lire les membres du groupe docker
|
||||
ansible.builtin.getent:
|
||||
database: group
|
||||
key: docker
|
||||
|
||||
- name: Retirer du groupe docker les comptes non autorisés
|
||||
ansible.builtin.command:
|
||||
cmd: gpasswd -d {{ item }} docker
|
||||
loop: "{{ docker_hardening_current_members | difference(docker_hardening_group_members) }}"
|
||||
changed_when: true
|
||||
vars:
|
||||
docker_hardening_current_members: "{{ ansible_facts['getent_group']['docker'][2].split(',') | select | list }}"
|
||||
|
||||
- name: Ajouter au groupe docker les comptes autorisés
|
||||
ansible.builtin.user:
|
||||
name: "{{ item }}"
|
||||
groups: docker
|
||||
append: true
|
||||
loop: "{{ docker_hardening_group_members }}"
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Installer auditd
|
||||
ansible.builtin.apt:
|
||||
name: auditd
|
||||
state: present
|
||||
|
||||
- name: Déposer les règles d'audit Docker
|
||||
ansible.builtin.template:
|
||||
src: docker.rules.j2
|
||||
dest: /etc/audit/rules.d/docker.rules
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
notify: Recharger les règles d'audit
|
||||
|
||||
- name: Activer auditd
|
||||
ansible.builtin.systemd:
|
||||
name: auditd
|
||||
enabled: true
|
||||
state: started
|
||||
ignore_errors: "{{ ansible_check_mode }}"
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: Sécuriser le répertoire de configuration Docker
|
||||
ansible.builtin.file:
|
||||
path: /etc/docker
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Déposer la configuration du démon
|
||||
ansible.builtin.template:
|
||||
src: daemon.json.j2
|
||||
dest: /etc/docker/daemon.json
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
validate: dockerd --validate --config-file %s
|
||||
notify: Redémarrer Docker
|
||||
|
||||
- name: Maintenir le routage IPv4 nécessaire aux réseaux Docker
|
||||
ansible.posix.sysctl:
|
||||
name: net.ipv4.ip_forward
|
||||
value: "1"
|
||||
sysctl_file: /etc/sysctl.d/99-zz-docker.conf
|
||||
state: present
|
||||
reload: true
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
- name: Déployer le filtrage DOCKER-USER
|
||||
when: docker_hardening_firewall_enabled
|
||||
block:
|
||||
- name: Déposer le script de filtrage
|
||||
ansible.builtin.template:
|
||||
src: docker-user-firewall.sh.j2
|
||||
dest: /usr/local/sbin/docker-user-firewall
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0750"
|
||||
validate: sh -n %s
|
||||
notify: Appliquer le filtrage DOCKER-USER
|
||||
|
||||
- name: Déposer l'unité systemd du filtrage
|
||||
ansible.builtin.template:
|
||||
src: docker-user-firewall.service.j2
|
||||
dest: /etc/systemd/system/docker-user-firewall.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Appliquer le filtrage DOCKER-USER
|
||||
|
||||
- name: Activer le filtrage au démarrage de Docker
|
||||
ansible.builtin.systemd:
|
||||
name: docker-user-firewall
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
# En mode check, l'unité n'a pas encore été déposée au premier passage
|
||||
ignore_errors: "{{ ansible_check_mode }}"
|
||||
|
||||
- name: Retirer le filtrage DOCKER-USER
|
||||
when: not docker_hardening_firewall_enabled
|
||||
block:
|
||||
- name: Rechercher l'unité de filtrage
|
||||
ansible.builtin.stat:
|
||||
path: /etc/systemd/system/docker-user-firewall.service
|
||||
register: docker_hardening_fw_unit
|
||||
|
||||
- name: Arrêter et désactiver le filtrage
|
||||
ansible.builtin.systemd:
|
||||
name: docker-user-firewall
|
||||
enabled: false
|
||||
state: stopped
|
||||
when: docker_hardening_fw_unit.stat.exists
|
||||
|
||||
- name: Supprimer le script et l'unité de filtrage
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /etc/systemd/system/docker-user-firewall.service
|
||||
- /usr/local/sbin/docker-user-firewall
|
||||
notify: Recharger systemd
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
- name: Rechercher le démon Docker
|
||||
ansible.builtin.stat:
|
||||
path: /usr/bin/dockerd
|
||||
register: docker_hardening_dockerd
|
||||
|
||||
- name: Signaler l'arrêt anticipé en mode check
|
||||
ansible.builtin.debug:
|
||||
msg: "Docker n'est pas encore installé : le durcissement sera évalué au premier passage réel."
|
||||
when:
|
||||
- ansible_check_mode
|
||||
- not docker_hardening_dockerd.stat.exists
|
||||
|
||||
- name: Arrêter ici en mode check si Docker n'est pas installé
|
||||
ansible.builtin.meta: end_host
|
||||
when:
|
||||
- ansible_check_mode
|
||||
- not docker_hardening_dockerd.stat.exists
|
||||
|
||||
- name: Vérifier les paramètres du durcissement
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not docker_hardening_firewall_enabled or docker_hardening_firewall_interfaces | length > 0
|
||||
- docker_hardening_firewall_public_ports | rejectattr('port', 'defined') | list | length == 0
|
||||
- docker_hardening_firewall_public_ports | map(attribute='proto', default='tcp') | difference(['tcp', 'udp']) | length == 0
|
||||
fail_msg: >-
|
||||
Paramètres invalides : interface de filtrage introuvable, ou entrée de
|
||||
docker_hardening_firewall_public_ports sans « port » ou avec un protocole autre que tcp/udp.
|
||||
quiet: true
|
||||
|
||||
- name: Appliquer la configuration du démon
|
||||
ansible.builtin.import_tasks: daemon.yml
|
||||
|
||||
- name: Restreindre l'accès au démon
|
||||
ansible.builtin.import_tasks: access.yml
|
||||
|
||||
- name: Filtrer les ports publiés
|
||||
ansible.builtin.import_tasks: firewall.yml
|
||||
|
||||
- name: Auditer les composants Docker
|
||||
ansible.builtin.import_tasks: audit.yml
|
||||
when: docker_hardening_audit_enabled
|
||||
|
||||
- name: Appliquer les changements en attente avant contrôle
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: Contrôler l'état final
|
||||
ansible.builtin.import_tasks: verify.yml
|
||||
when: not ansible_check_mode
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
- name: Relever la configuration effective de Docker
|
||||
ansible.builtin.command:
|
||||
cmd: docker info --format json
|
||||
register: docker_hardening_info_raw
|
||||
changed_when: false
|
||||
|
||||
- name: Relever la version du plugin Compose
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose version --short
|
||||
register: docker_hardening_compose_version
|
||||
changed_when: false
|
||||
|
||||
- name: Rechercher une API Docker exposée en TCP
|
||||
ansible.builtin.command:
|
||||
cmd: ss -ltnH '( sport = :2375 or sport = :2376 )'
|
||||
register: docker_hardening_tcp_api
|
||||
changed_when: false
|
||||
|
||||
- name: Relever les droits du socket Docker
|
||||
ansible.builtin.stat:
|
||||
path: /run/docker.sock
|
||||
register: docker_hardening_socket
|
||||
|
||||
- name: Vérifier le durcissement effectif
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- docker_hardening_info['SecurityOptions'] | select('match', '^name=seccomp') | list | length > 0
|
||||
- docker_hardening_info['SecurityOptions'] | select('match', '^name=apparmor') | list | length > 0
|
||||
- >-
|
||||
not docker_hardening_no_new_privileges
|
||||
or docker_hardening_info['SecurityOptions'] | select('match', '^name=no-new-privileges') | list | length > 0
|
||||
- docker_hardening_info['LiveRestoreEnabled'] == (docker_hardening_live_restore | bool)
|
||||
- docker_hardening_tcp_api.stdout | length == 0
|
||||
- docker_hardening_socket.stat.exists
|
||||
- docker_hardening_socket.stat.mode == '0660'
|
||||
- docker_hardening_socket.stat.pw_name == 'root'
|
||||
- docker_hardening_socket.stat.gr_name == 'docker'
|
||||
fail_msg: "Le durcissement effectif ne correspond pas à la configuration attendue."
|
||||
success_msg: >-
|
||||
Docker {{ docker_hardening_info['ServerVersion'] }}, Compose {{ docker_hardening_compose_version.stdout }} :
|
||||
seccomp, AppArmor, no-new-privileges, live-restore, socket 0660 root:docker, aucune API TCP.
|
||||
vars:
|
||||
docker_hardening_info: "{{ docker_hardening_info_raw.stdout | from_json }}"
|
||||
|
||||
- name: Afficher les avertissements du démon
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ (docker_hardening_info_raw.stdout | from_json)['Warnings'] | default([], true) }}"
|
||||
when: (docker_hardening_info_raw.stdout | from_json)['Warnings'] | default([], true) | length > 0
|
||||
@@ -0,0 +1,34 @@
|
||||
{#- Pas d'en-tête ansible_managed : le format JSON n'admet aucun commentaire. -#}
|
||||
{
|
||||
"icc": {{ docker_hardening_icc | bool | to_json }},
|
||||
"ip": {{ docker_hardening_default_bind_ip | to_json }},
|
||||
"live-restore": {{ docker_hardening_live_restore | bool | to_json }},
|
||||
"userland-proxy": {{ docker_hardening_userland_proxy | bool | to_json }},
|
||||
"no-new-privileges": {{ docker_hardening_no_new_privileges | bool | to_json }},
|
||||
"experimental": false,
|
||||
{% if docker_hardening_dns | length > 0 %}
|
||||
"dns": {{ docker_hardening_dns | to_json }},
|
||||
{% endif %}
|
||||
{% if docker_hardening_default_address_pools | length > 0 %}
|
||||
"default-address-pools": {{ docker_hardening_default_address_pools | to_json }},
|
||||
{% endif %}
|
||||
{% if docker_hardening_insecure_registries | length > 0 %}
|
||||
"insecure-registries": {{ docker_hardening_insecure_registries | to_json }},
|
||||
{% endif %}
|
||||
{% if docker_hardening_registry_mirrors | length > 0 %}
|
||||
"registry-mirrors": {{ docker_hardening_registry_mirrors | to_json }},
|
||||
{% endif %}
|
||||
{% if docker_hardening_userns_remap | length > 0 %}
|
||||
"userns-remap": {{ docker_hardening_userns_remap | to_json }},
|
||||
{% endif %}
|
||||
{% if docker_hardening_data_root | length > 0 %}
|
||||
"data-root": {{ docker_hardening_data_root | to_json }},
|
||||
{% endif %}
|
||||
"log-level": "info",
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": {{ docker_hardening_log_max_size | to_json }},
|
||||
"max-file": {{ docker_hardening_log_max_file | string | to_json }},
|
||||
"compress": "true"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# {{ ansible_managed }}
|
||||
[Unit]
|
||||
Description=Filtrage des ports publiés par Docker (chaîne DOCKER-USER)
|
||||
After=docker.service
|
||||
PartOf=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/local/sbin/docker-user-firewall start
|
||||
ExecStop=/usr/local/sbin/docker-user-firewall stop
|
||||
|
||||
[Install]
|
||||
WantedBy=docker.service
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/bin/sh
|
||||
# {{ ansible_managed }}
|
||||
#
|
||||
# Filtre les connexions entrantes vers les ports publiés par les conteneurs
|
||||
# (chaîne DOCKER-USER, consultée par Docker avant ses propres règles).
|
||||
# Lancé par docker-user-firewall.service après docker.service.
|
||||
# start : applique les règles stop : vide la chaîne
|
||||
set -eu
|
||||
|
||||
{% macro rules(cmd, sources) %}
|
||||
{% for iface in docker_hardening_firewall_interfaces %}
|
||||
{{ cmd }} -w -A DOCKER-USER -i {{ iface }} -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
|
||||
{% for p in docker_hardening_firewall_public_ports %}
|
||||
{{ cmd }} -w -A DOCKER-USER -i {{ iface }} -p {{ p.proto | default('tcp') }} -m conntrack --ctorigdstport {{ p.port }} --ctdir ORIGINAL -j RETURN
|
||||
{% endfor %}
|
||||
{% for src in sources %}
|
||||
{{ cmd }} -w -A DOCKER-USER -i {{ iface }} -s {{ src }} -j RETURN
|
||||
{% endfor %}
|
||||
{% if docker_hardening_firewall_log_drops %}
|
||||
{{ cmd }} -w -A DOCKER-USER -i {{ iface }} -m limit --limit 5/min -j LOG --log-prefix "DOCKER-USER DROP: "
|
||||
{% endif %}
|
||||
{{ cmd }} -w -A DOCKER-USER -i {{ iface }} -j DROP
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
reset_chain() {
|
||||
"$1" -w -N DOCKER-USER 2>/dev/null || true
|
||||
"$1" -w -F DOCKER-USER
|
||||
}
|
||||
|
||||
rules_v4() {
|
||||
{{ rules('iptables', docker_hardening_firewall_allowed_sources) -}}
|
||||
}
|
||||
|
||||
rules_v6() {
|
||||
{{ rules('ip6tables', docker_hardening_firewall_allowed_sources_v6) -}}
|
||||
}
|
||||
|
||||
case "${1:-start}" in
|
||||
start)
|
||||
reset_chain iptables
|
||||
rules_v4
|
||||
iptables -w -A DOCKER-USER -j RETURN
|
||||
if command -v ip6tables >/dev/null 2>&1; then
|
||||
reset_chain ip6tables
|
||||
rules_v6
|
||||
ip6tables -w -A DOCKER-USER -j RETURN
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
reset_chain iptables
|
||||
iptables -w -A DOCKER-USER -j RETURN
|
||||
if command -v ip6tables >/dev/null 2>&1; then
|
||||
reset_chain ip6tables
|
||||
ip6tables -w -A DOCKER-USER -j RETURN
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage : $0 {start|stop}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,15 @@
|
||||
## {{ ansible_managed }}
|
||||
## Surveillance des composants Docker (recommandations CIS Docker Benchmark, section 1)
|
||||
-w /usr/bin/dockerd -p rwxa -k docker
|
||||
-w /usr/bin/containerd -p rwxa -k docker
|
||||
-w /usr/bin/containerd-shim-runc-v2 -p rwxa -k docker
|
||||
-w /usr/bin/runc -p rwxa -k docker
|
||||
-w /etc/docker -p rwxa -k docker
|
||||
-w /etc/containerd -p rwxa -k docker
|
||||
-w /run/containerd -p rwxa -k docker
|
||||
-w /usr/lib/systemd/system/docker.service -p rwxa -k docker
|
||||
-w /usr/lib/systemd/system/docker.socket -p rwxa -k docker
|
||||
-w /usr/lib/systemd/system/containerd.service -p rwxa -k docker
|
||||
{% if docker_hardening_audit_watch_data_root %}
|
||||
-w {{ docker_hardening_data_root | default('/var/lib/docker', true) }} -p rwxa -k docker
|
||||
{% endif %}
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
# Distributions prises en charge : Debian par version majeure, Ubuntu par version complète
|
||||
docker_install_supported:
|
||||
Debian: ["12", "13"]
|
||||
Ubuntu: ["22.04", "24.04"]
|
||||
|
||||
# Version de Docker Engine à installer ("" = dernière version stable du dépôt).
|
||||
# Exemple : "29.1.5" — docker-ce et docker-ce-cli sont alors figés (apt hold).
|
||||
docker_install_version: ""
|
||||
|
||||
# Paquets en conflit à retirer (paquets de la distribution et homonymes sans rapport)
|
||||
docker_install_conflicting_packages:
|
||||
- docker.io
|
||||
- docker-doc
|
||||
- docker-compose
|
||||
- docker-compose-v2
|
||||
- podman-docker
|
||||
- containerd
|
||||
- runc
|
||||
- docker
|
||||
- wmdocker
|
||||
|
||||
# Empreinte de la clé de signature officielle du dépôt Docker
|
||||
docker_install_gpg_fingerprint: "9DC858229FC7DD38854AE2D88D81803C0EBFCD88"
|
||||
docker_install_gpg_key: /etc/apt/keyrings/docker.asc
|
||||
|
||||
docker_install_arch_map:
|
||||
x86_64: amd64
|
||||
aarch64: arm64
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
- name: Vérifier que la distribution est prise en charge
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts['distribution'] in docker_install_supported
|
||||
- docker_install_os_version in docker_install_supported[ansible_facts['distribution']]
|
||||
- ansible_facts['architecture'] in docker_install_arch_map
|
||||
fail_msg: >-
|
||||
{{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}
|
||||
({{ ansible_facts['architecture'] }}) n'est pas pris en charge par ce rôle.
|
||||
quiet: true
|
||||
|
||||
- name: Retirer les paquets en conflit avec Docker CE
|
||||
ansible.builtin.apt:
|
||||
name: "{{ docker_install_conflicting_packages }}"
|
||||
state: absent
|
||||
purge: true
|
||||
|
||||
- name: Retirer l'ancienne déclaration du dépôt (format .list)
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/sources.list.d/docker.list
|
||||
state: absent
|
||||
|
||||
- name: Installer les prérequis
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- gnupg
|
||||
- apparmor
|
||||
- iptables
|
||||
state: present
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Créer le répertoire des clés APT
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Télécharger la clé de signature du dépôt Docker
|
||||
ansible.builtin.get_url:
|
||||
url: "https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}/gpg"
|
||||
dest: "{{ docker_install_gpg_key }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Contrôler la présence de la clé
|
||||
ansible.builtin.stat:
|
||||
path: "{{ docker_install_gpg_key }}"
|
||||
register: docker_install_key_stat
|
||||
|
||||
- name: Lire l'empreinte de la clé téléchargée
|
||||
ansible.builtin.command:
|
||||
cmd: gpg --show-keys --with-colons --with-fingerprint {{ docker_install_gpg_key }}
|
||||
register: docker_install_key_fpr
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
when: docker_install_key_stat.stat.exists
|
||||
|
||||
- name: Vérifier l'empreinte de la clé Docker
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- docker_install_gpg_fingerprint in docker_install_key_fpr.stdout
|
||||
fail_msg: "Empreinte de la clé Docker inattendue : le dépôt n'est pas déclaré."
|
||||
quiet: true
|
||||
when: docker_install_key_stat.stat.exists
|
||||
|
||||
- name: Déclarer le dépôt APT officiel Docker
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/sources.list.d/docker.sources
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
# {{ ansible_managed }}
|
||||
Types: deb
|
||||
URIs: https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}
|
||||
Suites: {{ ansible_facts['distribution_release'] }}
|
||||
Components: stable
|
||||
Architectures: {{ docker_install_arch_map[ansible_facts['architecture']] }}
|
||||
Signed-By: {{ docker_install_gpg_key }}
|
||||
register: docker_install_repo
|
||||
|
||||
- name: Installer Docker Engine et le plugin Compose
|
||||
ansible.builtin.apt:
|
||||
name: "{{ docker_install_packages_versioned }}"
|
||||
state: present
|
||||
install_recommends: false
|
||||
allow_change_held_packages: true
|
||||
update_cache: "{{ docker_install_repo is changed }}"
|
||||
ignore_errors: "{{ ansible_check_mode }}"
|
||||
|
||||
- name: Figer ou libérer la version d'Engine
|
||||
ansible.builtin.dpkg_selections:
|
||||
name: "{{ item }}"
|
||||
selection: "{{ 'hold' if docker_install_version | length > 0 else 'install' }}"
|
||||
loop: "{{ docker_install_engine_packages }}"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Activer et démarrer containerd et Docker
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: true
|
||||
state: started
|
||||
loop:
|
||||
- containerd
|
||||
- docker
|
||||
ignore_errors: "{{ ansible_check_mode }}"
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
docker_install_packages:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
|
||||
# Paquets portant la version d'Engine (figés si docker_install_version est renseignée)
|
||||
docker_install_engine_packages:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
|
||||
docker_install_packages_versioned: >-
|
||||
{{ docker_install_packages if docker_install_version | length == 0
|
||||
else ['docker-ce=5:' ~ docker_install_version ~ '*', 'docker-ce-cli=5:' ~ docker_install_version ~ '*']
|
||||
+ docker_install_packages[2:] }}
|
||||
|
||||
docker_install_os_version: >-
|
||||
{{ ansible_facts['distribution_major_version'] if ansible_facts['distribution'] == 'Debian'
|
||||
else ansible_facts['distribution_version'] }}
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
# Installation et durcissement de Docker Engine + plugin Docker Compose
|
||||
# Cibles : Debian 12/13, Ubuntu 22.04/24.04 — exécuté depuis Semaphore (branche « valide »)
|
||||
- name: Docker - installation et durcissement
|
||||
hosts: docker
|
||||
gather_facts: true
|
||||
roles:
|
||||
- role: docker_install
|
||||
tags: [docker_install]
|
||||
- role: docker_hardening
|
||||
tags: [docker_hardening]
|
||||
Reference in new issue
Block a user