Add hardering

This commit is contained in:
Johnny
2026-04-06 08:37:54 +02:00
parent 4980d8cf3c
commit c0412d1150
27 changed files with 1527 additions and 82 deletions

75
CLAUDE.md Normal file
View File

@@ -0,0 +1,75 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commandes de développement
```bash
# Lancer l'application (mode interactif, demande sudo automatiquement)
python3 -m securecheck
# Lancer sans sudo (pour tester sans élévation)
SECURECHECK_SKIP_SUDO=1 python3 -m securecheck
# Mode non interactif
python3 -m securecheck --dry-run --tasks system_update,lynis_audit
python3 -m securecheck --scenario baseline_workstation --run
python3 -m securecheck --list-scenarios
# Avec sudo (recommandé pour les tâches système réelles)
sudo -E python3 -m securecheck
# Installation locale
pip install .
# Build du binaire autonome (nécessite .venv avec pyinstaller)
python3 -m venv .venv
.venv/bin/pip install pyinstaller
./build_executable.sh
# Résultat dans dist/securecheck
```
## Architecture
### Flux d'exécution principal
`__main__.py:main()` orchestre tout :
1. `ensure_root()` — ré-exécute automatiquement avec `sudo -E` si non root (sauf `SECURECHECK_SKIP_SUDO=1`)
2. `detect_system()` — détecte distro, gestionnaire de paquets, user invocant
3. `task_catalog()` + `builtin_scenarios()` — charge tâches et scénarios
4. En mode interactif : `SecureCheckTUI` → sélection → `execute_tasks()``RunSummaryTUI`
5. En mode non interactif : résolution des tâches via `--tasks`/`--scenario``execute_tasks()` → affichage terminal
### Modules clés
- **`models.py`** — dataclasses centrales : `TaskDefinition`, `TaskResult`, `Scenario`
- **`catalog.py`** — registre de toutes les tâches (`task_catalog()`) et scénarios builtin (`builtin_scenarios()`). Utilise `bind()` pour attacher les handlers.
- **`tasks.py`** — implémentation de chaque tâche (une fonction par tâche). Chaque fonction reçoit `(context: ExecutionContext, task: TaskDefinition)` et retourne `TaskResult`.
- **`executor.py`** — `ExecutionContext` (contexte partagé par toutes les tâches) et `CommandRunner` (abstraction pour toutes les opérations système : paquets, fichiers, services, shell). `execute_tasks()` itère sur les tâches et capture les exceptions.
- **`app.py`** — TUI curses : `SecureCheckTUI` (menu principal) et `RunSummaryTUI` (résumé post-exécution)
- **`config.py`** — `AppPaths` et `build_paths()` : résolution XDG des chemins (config, state, logs). Logs dans `/var/log/securecheck` si root, sinon `~/.local/state/securecheck/logs`.
- **`storage.py`** — `ScenarioStore` : lecture/écriture des scénarios utilisateur dans `~/.config/securecheck/scenarios.json`
- **`system_info.py`** — `SystemInfo` + `detect_system()` : détecte OS, package manager, user réel (via `SUDO_USER`), home, uid/gid
- **`status.py`** — `collect_status()` : sonde les services/composants pour le tableau d'état LED
- **`assets.py`** — accès aux fichiers embarqués dans `securecheck/assets/` (banner.txt, p10k.zsh, icônes)
### Ajouter une nouvelle tâche
1. Écrire la fonction dans `tasks.py` avec la signature `(context: ExecutionContext, task: TaskDefinition) -> TaskResult`
2. Enregistrer dans `catalog.py` : ajouter un `TaskDefinition` dans `task_catalog()` et mapper son handler dans `handlers`
3. L'ajouter optionnellement à un `builtin_scenarios()` existant
### Conventions importantes
- `CommandRunner` est le seul point d'entrée pour les opérations système (jamais `subprocess` directement dans `tasks.py`)
- `context.runner.write_text_file()` est idempotent : ne réécrit pas si le contenu est identique
- `context.runner.update_package_index()` est appelé une seule fois par exécution (flag `_package_index_updated`)
- Le `target_user` dans `SystemInfo` est l'utilisateur réel ayant lancé `sudo`, pas root
- `--dry-run` : toutes les opérations système sont simulées, les commandes sont loggées sans être exécutées
### Emplacements runtime
- Scénarios : `~/.config/securecheck/scenarios.json`
- Logs : `/var/log/securecheck/` (root) ou `~/.local/state/securecheck/logs/`
- Rapports par exécution : `<log_dir>/run-YYYYMMDD-HHMMSS.log`
- Rapports tâches (lynis, rootkits) : `<log_dir>/reports/`

Binary file not shown.

View File

@@ -31,6 +31,9 @@
'DATA'),
('securecheck/assets/securecheck-icon.svg',
'/home/tuxgyver/scripts/securecheck/securecheck/assets/securecheck-icon.svg',
'DATA'),
('securecheck/assets/system_hardening.sh',
'/home/tuxgyver/scripts/securecheck/securecheck/assets/system_hardening.sh',
'DATA')],
'3.13.7 (main, Mar 3 2026, 12:19:54) [GCC 15.2.0]',
[('pyi_rth_inspect',
@@ -189,12 +192,12 @@
('opcode', '/usr/lib/python3.13/opcode.py', 'PYMODULE'),
('_opcode_metadata', '/usr/lib/python3.13/_opcode_metadata.py', 'PYMODULE'),
('ast', '/usr/lib/python3.13/ast.py', 'PYMODULE'),
('_py_abc', '/usr/lib/python3.13/_py_abc.py', 'PYMODULE'),
('stringprep', '/usr/lib/python3.13/stringprep.py', 'PYMODULE'),
('_colorize', '/usr/lib/python3.13/_colorize.py', 'PYMODULE'),
('tracemalloc', '/usr/lib/python3.13/tracemalloc.py', 'PYMODULE'),
('subprocess', '/usr/lib/python3.13/subprocess.py', 'PYMODULE'),
('signal', '/usr/lib/python3.13/signal.py', 'PYMODULE'),
('stringprep', '/usr/lib/python3.13/stringprep.py', 'PYMODULE'),
('tracemalloc', '/usr/lib/python3.13/tracemalloc.py', 'PYMODULE'),
('_colorize', '/usr/lib/python3.13/_colorize.py', 'PYMODULE'),
('_py_abc', '/usr/lib/python3.13/_py_abc.py', 'PYMODULE'),
('securecheck.__main__',
'/home/tuxgyver/scripts/securecheck/securecheck/__main__.py',
'PYMODULE'),
@@ -250,6 +253,9 @@
('securecheck.app',
'/home/tuxgyver/scripts/securecheck/securecheck/app.py',
'PYMODULE'),
('securecheck.summary_utils',
'/home/tuxgyver/scripts/securecheck/securecheck/summary_utils.py',
'PYMODULE'),
('curses', '/usr/lib/python3.13/curses/__init__.py', 'PYMODULE'),
('curses.has_key', '/usr/lib/python3.13/curses/has_key.py', 'PYMODULE'),
('__future__', '/usr/lib/python3.13/__future__.py', 'PYMODULE')],
@@ -307,8 +313,8 @@
('python3.13/lib-dynload/_curses.cpython-313-x86_64-linux-gnu.so',
'/usr/lib/python3.13/lib-dynload/_curses.cpython-313-x86_64-linux-gnu.so',
'EXTENSION'),
('libz.so.1', '/lib/x86_64-linux-gnu/libz.so.1', 'BINARY'),
('libexpat.so.1', '/lib/x86_64-linux-gnu/libexpat.so.1', 'BINARY'),
('libz.so.1', '/lib/x86_64-linux-gnu/libz.so.1', 'BINARY'),
('libcrypto.so.3', '/lib/x86_64-linux-gnu/libcrypto.so.3', 'BINARY'),
('libzstd.so.1', '/lib/x86_64-linux-gnu/libzstd.so.1', 'BINARY'),
('liblzma.so.5', '/lib/x86_64-linux-gnu/liblzma.so.5', 'BINARY'),
@@ -333,33 +339,49 @@
('securecheck/assets/securecheck-icon.svg',
'/home/tuxgyver/scripts/securecheck/securecheck/assets/securecheck-icon.svg',
'DATA'),
('wheel-0.46.1.dist-info/METADATA',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/METADATA',
'DATA'),
('wheel-0.46.1.dist-info/WHEEL',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/WHEEL',
'DATA'),
('wheel-0.46.1.dist-info/INSTALLER',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/INSTALLER',
('securecheck/assets/system_hardening.sh',
'/home/tuxgyver/scripts/securecheck/securecheck/assets/system_hardening.sh',
'DATA'),
('wheel-0.46.1.dist-info/entry_points.txt',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/entry_points.txt',
'DATA'),
('wheel-0.46.1.dist-info/INSTALLER',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/INSTALLER',
'DATA'),
('wheel-0.46.1.dist-info/WHEEL',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/WHEEL',
'DATA'),
('wheel-0.46.1.dist-info/METADATA',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/METADATA',
'DATA'),
('base_library.zip',
'/home/tuxgyver/scripts/securecheck/build/securecheck/base_library.zip',
'DATA')],
[('functools', '/usr/lib/python3.13/functools.py', 'PYMODULE'),
('sre_compile', '/usr/lib/python3.13/sre_compile.py', 'PYMODULE'),
('copyreg', '/usr/lib/python3.13/copyreg.py', 'PYMODULE'),
('operator', '/usr/lib/python3.13/operator.py', 'PYMODULE'),
('genericpath', '/usr/lib/python3.13/genericpath.py', 'PYMODULE'),
('keyword', '/usr/lib/python3.13/keyword.py', 'PYMODULE'),
[('locale', '/usr/lib/python3.13/locale.py', 'PYMODULE'),
('abc', '/usr/lib/python3.13/abc.py', 'PYMODULE'),
('ntpath', '/usr/lib/python3.13/ntpath.py', 'PYMODULE'),
('types', '/usr/lib/python3.13/types.py', 'PYMODULE'),
('sre_parse', '/usr/lib/python3.13/sre_parse.py', 'PYMODULE'),
('os', '/usr/lib/python3.13/os.py', 'PYMODULE'),
('_collections_abc', '/usr/lib/python3.13/_collections_abc.py', 'PYMODULE'),
('linecache', '/usr/lib/python3.13/linecache.py', 'PYMODULE'),
('posixpath', '/usr/lib/python3.13/posixpath.py', 'PYMODULE'),
('collections', '/usr/lib/python3.13/collections/__init__.py', 'PYMODULE'),
('io', '/usr/lib/python3.13/io.py', 'PYMODULE'),
('locale', '/usr/lib/python3.13/locale.py', 'PYMODULE'),
('re._parser', '/usr/lib/python3.13/re/_parser.py', 'PYMODULE'),
('re._constants', '/usr/lib/python3.13/re/_constants.py', 'PYMODULE'),
('re._compiler', '/usr/lib/python3.13/re/_compiler.py', 'PYMODULE'),
('re._casefix', '/usr/lib/python3.13/re/_casefix.py', 'PYMODULE'),
('re', '/usr/lib/python3.13/re/__init__.py', 'PYMODULE'),
('operator', '/usr/lib/python3.13/operator.py', 'PYMODULE'),
('_collections_abc', '/usr/lib/python3.13/_collections_abc.py', 'PYMODULE'),
('sre_constants', '/usr/lib/python3.13/sre_constants.py', 'PYMODULE'),
('reprlib', '/usr/lib/python3.13/reprlib.py', 'PYMODULE'),
('keyword', '/usr/lib/python3.13/keyword.py', 'PYMODULE'),
('codecs', '/usr/lib/python3.13/codecs.py', 'PYMODULE'),
('genericpath', '/usr/lib/python3.13/genericpath.py', 'PYMODULE'),
('enum', '/usr/lib/python3.13/enum.py', 'PYMODULE'),
('sre_parse', '/usr/lib/python3.13/sre_parse.py', 'PYMODULE'),
('_weakrefset', '/usr/lib/python3.13/_weakrefset.py', 'PYMODULE'),
('copyreg', '/usr/lib/python3.13/copyreg.py', 'PYMODULE'),
('weakref', '/usr/lib/python3.13/weakref.py', 'PYMODULE'),
('encodings.zlib_codec',
'/usr/lib/python3.13/encodings/zlib_codec.py',
'PYMODULE'),
@@ -590,23 +612,10 @@
('encodings.ascii', '/usr/lib/python3.13/encodings/ascii.py', 'PYMODULE'),
('encodings.aliases', '/usr/lib/python3.13/encodings/aliases.py', 'PYMODULE'),
('encodings', '/usr/lib/python3.13/encodings/__init__.py', 'PYMODULE'),
('enum', '/usr/lib/python3.13/enum.py', 'PYMODULE'),
('weakref', '/usr/lib/python3.13/weakref.py', 'PYMODULE'),
('_weakrefset', '/usr/lib/python3.13/_weakrefset.py', 'PYMODULE'),
('warnings', '/usr/lib/python3.13/warnings.py', 'PYMODULE'),
('sre_constants', '/usr/lib/python3.13/sre_constants.py', 'PYMODULE'),
('heapq', '/usr/lib/python3.13/heapq.py', 'PYMODULE'),
('codecs', '/usr/lib/python3.13/codecs.py', 'PYMODULE'),
('traceback', '/usr/lib/python3.13/traceback.py', 'PYMODULE'),
('linecache', '/usr/lib/python3.13/linecache.py', 'PYMODULE'),
('re._parser', '/usr/lib/python3.13/re/_parser.py', 'PYMODULE'),
('re._constants', '/usr/lib/python3.13/re/_constants.py', 'PYMODULE'),
('re._compiler', '/usr/lib/python3.13/re/_compiler.py', 'PYMODULE'),
('re._casefix', '/usr/lib/python3.13/re/_casefix.py', 'PYMODULE'),
('re', '/usr/lib/python3.13/re/__init__.py', 'PYMODULE'),
('posixpath', '/usr/lib/python3.13/posixpath.py', 'PYMODULE'),
('reprlib', '/usr/lib/python3.13/reprlib.py', 'PYMODULE'),
('abc', '/usr/lib/python3.13/abc.py', 'PYMODULE'),
('collections', '/usr/lib/python3.13/collections/__init__.py', 'PYMODULE'),
('ntpath', '/usr/lib/python3.13/ntpath.py', 'PYMODULE'),
('stat', '/usr/lib/python3.13/stat.py', 'PYMODULE')])
('stat', '/usr/lib/python3.13/stat.py', 'PYMODULE'),
('warnings', '/usr/lib/python3.13/warnings.py', 'PYMODULE'),
('os', '/usr/lib/python3.13/os.py', 'PYMODULE'),
('sre_compile', '/usr/lib/python3.13/sre_compile.py', 'PYMODULE'),
('heapq', '/usr/lib/python3.13/heapq.py', 'PYMODULE'),
('functools', '/usr/lib/python3.13/functools.py', 'PYMODULE')])

View File

@@ -90,8 +90,8 @@
('python3.13/lib-dynload/_curses.cpython-313-x86_64-linux-gnu.so',
'/usr/lib/python3.13/lib-dynload/_curses.cpython-313-x86_64-linux-gnu.so',
'EXTENSION'),
('libz.so.1', '/lib/x86_64-linux-gnu/libz.so.1', 'BINARY'),
('libexpat.so.1', '/lib/x86_64-linux-gnu/libexpat.so.1', 'BINARY'),
('libz.so.1', '/lib/x86_64-linux-gnu/libz.so.1', 'BINARY'),
('libcrypto.so.3', '/lib/x86_64-linux-gnu/libcrypto.so.3', 'BINARY'),
('libzstd.so.1', '/lib/x86_64-linux-gnu/libzstd.so.1', 'BINARY'),
('liblzma.so.5', '/lib/x86_64-linux-gnu/liblzma.so.5', 'BINARY'),
@@ -114,17 +114,20 @@
('securecheck/assets/securecheck-icon.svg',
'/home/tuxgyver/scripts/securecheck/securecheck/assets/securecheck-icon.svg',
'DATA'),
('wheel-0.46.1.dist-info/METADATA',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/METADATA',
('securecheck/assets/system_hardening.sh',
'/home/tuxgyver/scripts/securecheck/securecheck/assets/system_hardening.sh',
'DATA'),
('wheel-0.46.1.dist-info/WHEEL',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/WHEEL',
('wheel-0.46.1.dist-info/entry_points.txt',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/entry_points.txt',
'DATA'),
('wheel-0.46.1.dist-info/INSTALLER',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/INSTALLER',
'DATA'),
('wheel-0.46.1.dist-info/entry_points.txt',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/entry_points.txt',
('wheel-0.46.1.dist-info/WHEEL',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/WHEEL',
'DATA'),
('wheel-0.46.1.dist-info/METADATA',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/METADATA',
'DATA'),
('base_library.zip',
'/home/tuxgyver/scripts/securecheck/build/securecheck/base_library.zip',
@@ -132,7 +135,7 @@
[],
False,
False,
1775421404,
1775457449,
[('run',
'/home/tuxgyver/.local/lib/python3.13/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run',
'EXECUTABLE')],

View File

@@ -85,8 +85,8 @@
('python3.13/lib-dynload/_curses.cpython-313-x86_64-linux-gnu.so',
'/usr/lib/python3.13/lib-dynload/_curses.cpython-313-x86_64-linux-gnu.so',
'EXTENSION'),
('libz.so.1', '/lib/x86_64-linux-gnu/libz.so.1', 'BINARY'),
('libexpat.so.1', '/lib/x86_64-linux-gnu/libexpat.so.1', 'BINARY'),
('libz.so.1', '/lib/x86_64-linux-gnu/libz.so.1', 'BINARY'),
('libcrypto.so.3', '/lib/x86_64-linux-gnu/libcrypto.so.3', 'BINARY'),
('libzstd.so.1', '/lib/x86_64-linux-gnu/libzstd.so.1', 'BINARY'),
('liblzma.so.5', '/lib/x86_64-linux-gnu/liblzma.so.5', 'BINARY'),
@@ -109,17 +109,20 @@
('securecheck/assets/securecheck-icon.svg',
'/home/tuxgyver/scripts/securecheck/securecheck/assets/securecheck-icon.svg',
'DATA'),
('wheel-0.46.1.dist-info/METADATA',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/METADATA',
('securecheck/assets/system_hardening.sh',
'/home/tuxgyver/scripts/securecheck/securecheck/assets/system_hardening.sh',
'DATA'),
('wheel-0.46.1.dist-info/WHEEL',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/WHEEL',
('wheel-0.46.1.dist-info/entry_points.txt',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/entry_points.txt',
'DATA'),
('wheel-0.46.1.dist-info/INSTALLER',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/INSTALLER',
'DATA'),
('wheel-0.46.1.dist-info/entry_points.txt',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/entry_points.txt',
('wheel-0.46.1.dist-info/WHEEL',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/WHEEL',
'DATA'),
('wheel-0.46.1.dist-info/METADATA',
'/usr/lib/python3/dist-packages/wheel-0.46.1.dist-info/METADATA',
'DATA'),
('base_library.zip',
'/home/tuxgyver/scripts/securecheck/build/securecheck/base_library.zip',

Binary file not shown.

View File

@@ -178,6 +178,9 @@
('securecheck.storage',
'/home/tuxgyver/scripts/securecheck/securecheck/storage.py',
'PYMODULE'),
('securecheck.summary_utils',
'/home/tuxgyver/scripts/securecheck/securecheck/summary_utils.py',
'PYMODULE'),
('securecheck.system_info',
'/home/tuxgyver/scripts/securecheck/securecheck/system_info.py',
'PYMODULE'),

Binary file not shown.

Binary file not shown.

View File

@@ -18,8 +18,8 @@ missing module named _frozen_importlib_external - imported by importlib._bootstr
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
missing module named 'collections.abc' - imported by traceback (top-level), typing (top-level), inspect (top-level), logging (top-level), importlib.resources.readers (top-level), selectors (top-level), tracemalloc (top-level), http.client (top-level), pkg_resources (top-level), setuptools (top-level), setuptools._distutils.filelist (top-level), setuptools._distutils.util (top-level), jaraco.functools (top-level), more_itertools.more (top-level), more_itertools.recipes (top-level), setuptools._distutils._modified (top-level), setuptools._distutils.compat (top-level), setuptools._distutils.spawn (top-level), typing_extensions (top-level), asyncio.base_events (top-level), asyncio.coroutines (top-level), setuptools._distutils.compilers.C.base (top-level), setuptools._distutils.fancy_getopt (top-level), setuptools._reqs (top-level), setuptools.discovery (top-level), setuptools.dist (top-level), setuptools._distutils.command.bdist (top-level), setuptools._distutils.core (top-level), setuptools._distutils.cmd (top-level), setuptools._distutils.dist (top-level), configparser (top-level), setuptools._distutils.extension (top-level), setuptools.config.setupcfg (top-level), setuptools.config.expand (top-level), setuptools.config.pyprojecttoml (top-level), setuptools.config._apply_pyprojecttoml (top-level), tomllib._parser (top-level), setuptools._vendor.tomli._parser (top-level), setuptools.command.egg_info (top-level), setuptools._distutils.command.build (top-level), setuptools._distutils.command.sdist (top-level), setuptools.glob (top-level), setuptools.command._requirestxt (top-level), setuptools.command.bdist_wheel (top-level), platformdirs.api (conditional), platformdirs.windows (conditional), platformdirs.unix (conditional), setuptools._distutils.command.build_ext (top-level), _pyrepl.types (top-level), _pyrepl.readline (top-level), setuptools._distutils.compilers.C.msvc (top-level)
missing module named winreg - imported by importlib._bootstrap_external (conditional), mimetypes (optional), urllib.request (delayed, conditional, optional), platform (delayed, optional), platformdirs.windows (delayed, optional), setuptools._distutils.compilers.C.msvc (top-level), setuptools.msvc (conditional)
missing module named nt - imported by shutil (conditional), importlib._bootstrap_external (conditional), _colorize (delayed, conditional, optional), os (delayed, conditional, optional), ntpath (optional), ctypes (delayed, conditional), _pyrepl.windows_console (delayed, optional)
missing module named _winapi - imported by encodings (delayed, conditional, optional), shutil (conditional), subprocess (conditional), ntpath (optional), mimetypes (optional), sysconfig (delayed), multiprocessing.connection (optional), multiprocessing.spawn (delayed, conditional), multiprocessing.reduction (conditional), multiprocessing.shared_memory (conditional), multiprocessing.heap (conditional), multiprocessing.popen_spawn_win32 (top-level), asyncio.windows_events (top-level), asyncio.windows_utils (top-level)
missing module named nt - imported by shutil (conditional), importlib._bootstrap_external (conditional), ntpath (optional), _colorize (delayed, conditional, optional), os (delayed, conditional, optional), ctypes (delayed, conditional), _pyrepl.windows_console (delayed, optional)
missing module named _winapi - imported by encodings (delayed, conditional, optional), shutil (conditional), ntpath (optional), subprocess (conditional), mimetypes (optional), sysconfig (delayed), multiprocessing.connection (optional), multiprocessing.spawn (delayed, conditional), multiprocessing.reduction (conditional), multiprocessing.shared_memory (conditional), multiprocessing.heap (conditional), multiprocessing.popen_spawn_win32 (top-level), asyncio.windows_events (top-level), asyncio.windows_utils (top-level)
missing module named msvcrt - imported by subprocess (optional), getpass (optional), multiprocessing.spawn (delayed, conditional), multiprocessing.popen_spawn_win32 (top-level), asyncio.windows_events (top-level), asyncio.windows_utils (top-level), _pyrepl.windows_console (top-level)
missing module named win32evtlog - imported by logging.handlers (delayed, optional)
missing module named win32evtlogutil - imported by logging.handlers (delayed, optional)

View File

@@ -352,6 +352,7 @@ imported by:
&#8226; <a href="#securecheck.models">securecheck.models</a>
&#8226; <a href="#securecheck.status">securecheck.status</a>
&#8226; <a href="#securecheck.storage">securecheck.storage</a>
&#8226; <a href="#securecheck.summary_utils">securecheck.summary_utils</a>
&#8226; <a href="#securecheck.system_info">securecheck.system_info</a>
&#8226; <a href="#securecheck.tasks">securecheck.tasks</a>
&#8226; <a href="#setuptools">setuptools</a>
@@ -10444,9 +10445,12 @@ imported by:
&#8226; <a href="#random">random</a>
&#8226; <a href="#run.py">run.py</a>
&#8226; <a href="#runpy">runpy</a>
&#8226; <a href="#securecheck.__main__">securecheck.__main__</a>
&#8226; <a href="#securecheck.config">securecheck.config</a>
&#8226; <a href="#securecheck.executor">securecheck.executor</a>
&#8226; <a href="#securecheck.logging_utils">securecheck.logging_utils</a>
&#8226; <a href="#securecheck.system_info">securecheck.system_info</a>
&#8226; <a href="#securecheck.tasks">securecheck.tasks</a>
&#8226; <a href="#setuptools">setuptools</a>
&#8226; <a href="#setuptools._core_metadata">setuptools._core_metadata</a>
&#8226; <a href="#setuptools._distutils.archive_util">setuptools._distutils.archive_util</a>
@@ -11840,7 +11844,7 @@ imported by:
&#8226; <a href="#re._parser">re._parser</a>
&#8226; <a href="#rlcompleter">rlcompleter</a>
&#8226; <a href="#run.py">run.py</a>
&#8226; <a href="#securecheck.app">securecheck.app</a>
&#8226; <a href="#securecheck.summary_utils">securecheck.summary_utils</a>
&#8226; <a href="#securecheck.tasks">securecheck.tasks</a>
&#8226; <a href="#setuptools">setuptools</a>
&#8226; <a href="#setuptools._distutils.cmd">setuptools._distutils.cmd</a>
@@ -12115,6 +12119,7 @@ imported by:
&#8226; <a href="#securecheck.models">securecheck.models</a>
&#8226; <a href="#securecheck.status">securecheck.status</a>
&#8226; <a href="#securecheck.storage">securecheck.storage</a>
&#8226; <a href="#securecheck.summary_utils">securecheck.summary_utils</a>
&#8226; <a href="#securecheck.system_info">securecheck.system_info</a>
&#8226; <a href="#securecheck.tasks">securecheck.tasks</a>
@@ -12130,6 +12135,7 @@ imports:
<a href="#__future__">__future__</a>
&#8226; <a href="#argparse">argparse</a>
&#8226; <a href="#datetime">datetime</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#securecheck">securecheck</a>
&#8226; <a href="#securecheck.app">securecheck.app</a>
&#8226; <a href="#securecheck.catalog">securecheck.catalog</a>
@@ -12159,12 +12165,12 @@ imports:
&#8226; <a href="#collections">collections</a>
&#8226; <a href="#curses">curses</a>
&#8226; <a href="#dataclasses">dataclasses</a>
&#8226; <a href="#re">re</a>
&#8226; <a href="#securecheck">securecheck</a>
&#8226; <a href="#securecheck.assets">securecheck.assets</a>
&#8226; <a href="#securecheck.models">securecheck.models</a>
&#8226; <a href="#securecheck.status">securecheck.status</a>
&#8226; <a href="#securecheck.storage">securecheck.storage</a>
&#8226; <a href="#securecheck.summary_utils">securecheck.summary_utils</a>
&#8226; <a href="#securecheck.system_info">securecheck.system_info</a>
&#8226; <a href="#textwrap">textwrap</a>
&#8226; <a href="#typing">typing</a>
@@ -12280,6 +12286,7 @@ imports:
<a href="#__future__">__future__</a>
&#8226; <a href="#logging">logging</a>
&#8226; <a href="#logging.handlers">logging.handlers</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#pathlib">pathlib</a>
&#8226; <a href="#securecheck">securecheck</a>
@@ -12311,6 +12318,7 @@ imported by:
&#8226; <a href="#securecheck.catalog">securecheck.catalog</a>
&#8226; <a href="#securecheck.executor">securecheck.executor</a>
&#8226; <a href="#securecheck.storage">securecheck.storage</a>
&#8226; <a href="#securecheck.summary_utils">securecheck.summary_utils</a>
&#8226; <a href="#securecheck.tasks">securecheck.tasks</a>
</div>
@@ -12361,6 +12369,26 @@ imported by:
</div>
<div class="node">
<a name="securecheck.summary_utils"></a>
<a target="code" href="/home/tuxgyver/scripts/securecheck/securecheck/summary_utils.py" type="text/plain"><tt>securecheck.summary_utils</tt></a>
<span class="moduletype">SourceModule</span> <div class="import">
imports:
<a href="#__future__">__future__</a>
&#8226; <a href="#re">re</a>
&#8226; <a href="#securecheck">securecheck</a>
&#8226; <a href="#securecheck.models">securecheck.models</a>
&#8226; <a href="#typing">typing</a>
</div>
<div class="import">
imported by:
<a href="#securecheck.app">securecheck.app</a>
</div>
</div>
<div class="node">
<a name="securecheck.system_info"></a>
<a target="code" href="/home/tuxgyver/scripts/securecheck/securecheck/system_info.py" type="text/plain"><tt>securecheck.system_info</tt></a>
@@ -12394,12 +12422,15 @@ imports:
<a href="#__future__">__future__</a>
&#8226; <a href="#datetime">datetime</a>
&#8226; <a href="#json">json</a>
&#8226; <a href="#os">os</a>
&#8226; <a href="#pathlib">pathlib</a>
&#8226; <a href="#re">re</a>
&#8226; <a href="#securecheck">securecheck</a>
&#8226; <a href="#securecheck.assets">securecheck.assets</a>
&#8226; <a href="#securecheck.executor">securecheck.executor</a>
&#8226; <a href="#securecheck.models">securecheck.models</a>
&#8226; <a href="#stat">stat</a>
&#8226; <a href="#tempfile">tempfile</a>
</div>
<div class="import">
@@ -16098,6 +16129,7 @@ imported by:
&#8226; <a href="#posixpath">posixpath</a>
&#8226; <a href="#run.py">run.py</a>
&#8226; <a href="#securecheck.executor">securecheck.executor</a>
&#8226; <a href="#securecheck.tasks">securecheck.tasks</a>
&#8226; <a href="#setuptools._core_metadata">setuptools._core_metadata</a>
&#8226; <a href="#setuptools._distutils.file_util">setuptools._distutils.file_util</a>
&#8226; <a href="#setuptools._shutil">setuptools._shutil</a>
@@ -16612,6 +16644,7 @@ imported by:
&#8226; <a href="#pkg_resources">pkg_resources</a>
&#8226; <a href="#securecheck.config">securecheck.config</a>
&#8226; <a href="#securecheck.executor">securecheck.executor</a>
&#8226; <a href="#securecheck.tasks">securecheck.tasks</a>
&#8226; <a href="#setuptools._core_metadata">setuptools._core_metadata</a>
&#8226; <a href="#setuptools._distutils.compilers.C.base">setuptools._distutils.compilers.C.base</a>
&#8226; <a href="#setuptools._distutils.util">setuptools._distutils.util</a>
@@ -17166,6 +17199,7 @@ imported by:
&#8226; <a href="#platformdirs.windows">platformdirs.windows</a>
&#8226; <a href="#securecheck.app">securecheck.app</a>
&#8226; <a href="#securecheck.models">securecheck.models</a>
&#8226; <a href="#securecheck.summary_utils">securecheck.summary_utils</a>
&#8226; <a href="#setuptools">setuptools</a>
&#8226; <a href="#setuptools._distutils._modified">setuptools._distutils._modified</a>
&#8226; <a href="#setuptools._distutils.archive_util">setuptools._distutils.archive_util</a>

BIN
dist/securecheck vendored

Binary file not shown.

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import argparse
import os
import sys
from datetime import datetime
@@ -14,6 +15,20 @@ from .storage import ScenarioStore
from .system_info import detect_system
def ensure_root() -> None:
if os.geteuid() == 0:
return
if os.environ.get("SECURECHECK_SKIP_SUDO") == "1":
return
args = sys.argv[1:]
if getattr(sys, "frozen", False):
target = sys.argv[0]
cmd = ["sudo", "-E", target, *args]
else:
cmd = ["sudo", "-E", sys.executable, "-m", "securecheck", *args]
os.execvp("sudo", cmd)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="SecureCheck - console semi-graphique pour contrôles sécurité Linux")
parser.add_argument("--dry-run", action="store_true", help="Simule les commandes sans modifier le système")
@@ -66,6 +81,7 @@ def print_summary(results, run_log_path, system) -> None:
def main() -> int:
ensure_root()
args = parse_args()
paths = build_paths()
ensure_app_dirs(paths)
@@ -77,6 +93,7 @@ def main() -> int:
store = ScenarioStore(paths.scenario_file, builtin_scenarios())
if args.list_scenarios:
print(f"Scénarios stockés dans {paths.scenario_file}")
for scenario in store.list_all():
print(f"{scenario.name}: {scenario.description}")
return 0

Binary file not shown.

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
import curses
import re
import textwrap
from collections import defaultdict
from dataclasses import dataclass
@@ -11,6 +10,7 @@ from .assets import banner_text
from .models import Scenario, TaskDefinition, TaskResult
from .status import StatusItem
from .storage import ScenarioStore
from .summary_utils import SCORE_PREFIXES, clean_text, collect_details
from .system_info import SystemInfo
@@ -334,18 +334,12 @@ class SecureCheckTUI:
class RunSummaryTUI:
ANSI_RE = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")
def __init__(self, results: list[TaskResult], status_items: list[StatusItem], run_log_path: str) -> None:
self.results = results
self.status_items = status_items
self.run_log_path = run_log_path
self.scroll_offset = 0
@classmethod
def _clean(cls, text: str) -> str:
return cls.ANSI_RE.sub("", text)
def run(self) -> None:
curses.wrapper(self._main)
@@ -368,8 +362,7 @@ class RunSummaryTUI:
height, width = stdscr.getmaxyx()
ok_count = sum(1 for result in self.results if result.success)
ko_count = len(self.results) - ok_count
score_lines: list[str] = []
notif_lines: list[str] = []
score_lines, notif_lines = collect_details(self.results)
entries: list[tuple[str, int]] = []
entries.append((f"OK: {ok_count} | ECHEC: {ko_count} | Appuie sur une touche pour revenir", curses.color_pair(Palette.HEADER) | curses.A_BOLD))
for result in self.results:
@@ -377,12 +370,10 @@ class RunSummaryTUI:
color = curses.color_pair(Palette.SUCCESS if result.success else Palette.ERROR)
entries.append((f"{status:<4} {result.label} ({result.duration_seconds:.1f}s)", color | curses.A_BOLD))
for detail in result.details:
clean = self._clean(detail)
if clean.startswith("Score Lynis") or clean.startswith("Hardening index"):
score_lines.append(clean)
clean = clean_text(detail).strip()
if any(clean.startswith(prefix) for prefix in SCORE_PREFIXES):
continue
if clean.startswith("Modifications") or clean.strip().startswith(""):
notif_lines.append(clean)
if clean.startswith("Modifications") or clean.startswith(""):
continue
wrapped = textwrap.wrap(clean, width - 9) or [""]
for line in wrapped:

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ from .tasks import (
log_rotation,
lynis_audit,
rootkit_check,
system_hardening,
system_update,
utilities_setup,
zram_setup,
@@ -98,6 +99,14 @@ def task_catalog() -> list[TaskDefinition]:
handler=lambda context: None,
default_selected=False,
),
TaskDefinition(
key="system_hardening",
label="Durcissement système (hardening)",
description="Applique un durcissement complet : sysctl, SSH, PAM, modules noyau, permissions, AIDE, bannières, limites de sécurité.",
category="Sécurité",
handler=lambda context: None,
default_selected=False,
),
]
handlers = {
@@ -111,6 +120,7 @@ def task_catalog() -> list[TaskDefinition]:
"zram_setup": zram_setup,
"firewall_setup": firewall_setup,
"docker_setup": docker_setup,
"system_hardening": system_hardening,
}
return [bind(task, handlers[task.key]) for task in base]

View File

@@ -1,15 +1,28 @@
from __future__ import annotations
import logging
import os
from logging.handlers import RotatingFileHandler
from pathlib import Path
_LOG_FILE_MODE = 0o644
def _prepare_log_file(path: Path) -> None:
"""Crée le fichier de log s'il n'existe pas et fixe ses permissions à 644."""
path.parent.mkdir(parents=True, exist_ok=True)
if not path.exists():
path.touch()
os.chmod(path, _LOG_FILE_MODE)
def setup_logging(log_file: Path) -> logging.Logger:
logger = logging.getLogger("securecheck")
if logger.handlers:
return logger
_prepare_log_file(log_file)
logger.setLevel(logging.INFO)
logger.propagate = False
formatter = logging.Formatter(
@@ -24,6 +37,8 @@ def setup_logging(log_file: Path) -> logging.Logger:
def attach_run_handler(logger: logging.Logger, run_log_file: Path) -> RotatingFileHandler:
_prepare_log_file(run_log_file)
formatter = logging.Formatter(
fmt="%(asctime)s | %(levelname)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",

View File

@@ -91,12 +91,10 @@ def collect_status(system: SystemInfo) -> list[StatusItem]:
docker_active = _command_exists("docker") and _service_active("docker.service")
fail2ban_active = _command_exists("fail2ban-client") and _service_active("fail2ban.service")
services.append(StatusItem("Services", "Service Docker", docker_active, "actif" if docker_active else "inactif"))
services.append(StatusItem("Services", "Service fail2ban", fail2ban_active, "actif" if fail2ban_active else "inactif"))
services.append(StatusItem("Services", "Docker", docker_active, "actif" if docker_active else "inactif"))
services.append(StatusItem("Services", "fail2ban", fail2ban_active, "actif" if fail2ban_active else "inactif"))
avahi_running = _command_exists("avahi-daemon") and _service_active("avahi-daemon")
services.append(StatusItem("Services", "Avahi", not avahi_running, "désactivé" if not avahi_running else "actif"))
services.append(_binary_status("Services", "Docker", "docker"))
services.append(_binary_status("Services", "fail2ban", "fail2ban-client"))
poste.extend([
_binary_status("Poste", "zsh", "zsh"),

View File

@@ -0,0 +1,41 @@
from __future__ import annotations
import re
from typing import Sequence
from .models import TaskResult
ANSI_RE = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")
SCORE_PREFIXES = ("Score Lynis", "Hardening index")
RECOMMENDATION_PREFIXES = (
"Modifications recommandées",
"",
)
def clean_text(text: str) -> str:
return ANSI_RE.sub("", text)
def collect_details(results: Sequence[TaskResult]) -> tuple[list[str], list[str]]:
score_lines: list[str] = []
recommendation_lines: list[str] = []
seen_scores: set[str] = set()
seen_recommendations: set[str] = set()
for result in results:
for detail in result.details:
line = clean_text(detail).strip()
if not line:
continue
if any(line.startswith(prefix) for prefix in SCORE_PREFIXES) and line not in seen_scores:
seen_scores.add(line)
score_lines.append(line)
continue
if any(line.startswith(prefix) for prefix in RECOMMENDATION_PREFIXES) and line not in seen_recommendations:
seen_recommendations.add(line)
recommendation_lines.append(line)
return score_lines, recommendation_lines

View File

@@ -1,7 +1,10 @@
from __future__ import annotations
import json
import os
import re
import stat
import tempfile
from datetime import datetime
from pathlib import Path
@@ -195,8 +198,21 @@ def rootkit_check(context: ExecutionContext, task: TaskDefinition) -> TaskResult
report_path = context.paths.report_dir / f"{datetime.now().strftime('%Y%m%d-%H%M%S')}-rootkit-report.json"
context.runner.write_text_file(report_path, json.dumps(report_payload, indent=2) + "\n", mode=0o640, requires_root=False)
details.append(f"Rapport rootkits: {report_path}")
success = rkhunter_result.returncode == 0 and chkrootkit_result.returncode == 0
return context.make_result(task, success=success, changed=changed, started_at=started_at, details=details, error=None if success else "Vérification rootkit incomplète ou avec alertes")
# rkhunter: rc=0 clean, rc=1 warnings trouvés, rc=2+ erreur critique (outil n'a pas pu tourner)
rkhunter_ran = rkhunter_result.returncode <= 1
rkhunter_warnings = [line for line in rkhunter_result.stdout.splitlines() if line.startswith("Warning:")]
if rkhunter_warnings:
details.append(f"rkhunter: {len(rkhunter_warnings)} warning(s) à vérifier dans le rapport")
# chkrootkit: rc=0 signifie que l'outil a tourné (pas qu'il n'y a pas d'infection — les INFECTED sont dans stdout)
chkrootkit_ran = chkrootkit_result.returncode == 0
infected_lines = [line for line in chkrootkit_result.stdout.splitlines() if "INFECTED" in line]
if infected_lines:
details.append(f"chkrootkit: {len(infected_lines)} détection(s) à vérifier dans le rapport")
success = rkhunter_ran and chkrootkit_ran
error = None if success else "Les outils de vérification n'ont pas pu s'exécuter correctement"
return context.make_result(task, success=success, changed=changed, started_at=started_at, details=details, error=error)
def log_rotation(context: ExecutionContext, task: TaskDefinition) -> TaskResult:
@@ -434,7 +450,6 @@ def utilities_setup(context: ExecutionContext, task: TaskDefinition) -> TaskResu
context.runner.run(["cp", "-f", str(aide_db_new), "/var/lib/aide/aide.db"], requires_root=True, check=False)
details.append("Base AIDE mise à jour")
if context.runner.command_exists("systemctl"):
context.runner.run(["systemctl", "enable", "--now", "aidecheck.timer"], requires_root=True, check=False)
context.runner.run(["systemctl", "enable", "--now", "dailyaidecheck.timer"], requires_root=True, check=False)
details.append("Timers AIDE activés")
@@ -560,6 +575,83 @@ def docker_setup(context: ExecutionContext, task: TaskDefinition) -> TaskResult:
return _result(context, task, started_at, changed=changed, details=details)
def system_hardening(context: ExecutionContext, task: TaskDefinition) -> TaskResult:
started_at = datetime.now()
details: list[str] = []
script_content = asset_text("system_hardening.sh")
tmp_dir = context.paths.state_dir
tmp_dir.mkdir(parents=True, exist_ok=True)
script_path: Path | None = None
try:
with tempfile.NamedTemporaryFile(
mode="w", suffix=".sh", delete=False, dir=tmp_dir, encoding="utf-8"
) as fh:
fh.write(script_content)
script_path = Path(fh.name)
current_mode = stat.S_IMODE(script_path.stat().st_mode)
script_path.chmod(current_mode | 0o111)
env = os.environ.copy()
env.update({
"AUTO_YES": "yes",
"AUTO_SSH_PORT": "22",
"AUTO_CHANGE_ROOT_PWD": "no",
"AUTO_DISABLE_ROOT_LOGIN": "no",
"AUTO_SKIP_LYNIS": "no",
"AUTO_ENABLE_FAIL2BAN": "yes",
"AUTO_ENABLE_UFW": "yes",
"AUTO_ENABLE_AIDE": "yes",
"AUTO_ENABLE_CLAMAV": "yes",
"AUTO_SKIP_PORTS_DETECTION": "no",
"DEBIAN_FRONTEND": "noninteractive",
})
result = context.runner.run(
["bash", str(script_path), "--unattended"],
requires_root=True,
check=False,
capture_output=True,
env=env,
)
ok_steps = [line for line in result.stdout.splitlines() if "[OK]" in line]
warn_steps = [line for line in result.stdout.splitlines() if "[WARN]" in line]
err_steps = [line for line in result.stdout.splitlines() if "[ERR]" in line]
details.append(f"{len(ok_steps)} étape(s) réussie(s)")
if warn_steps:
details.append(f"{len(warn_steps)} avertissement(s)")
if err_steps:
details.append(f"{len(err_steps)} erreur(s)")
for line in err_steps[:5]:
details.append(f" {line.strip()}")
score_line = next(
(line for line in result.stdout.splitlines() if "Score:" in line and "Lynis" in line),
None,
)
if score_line:
details.append(score_line.strip())
log_path = Path("/var/log/system_hardening.log")
if log_path.exists():
details.append(f"Log complet: {log_path}")
backup_path = next(Path("/root").glob("backup_hardening_*"), None) if Path("/root").exists() else None
if backup_path:
details.append(f"Sauvegardes: {backup_path}")
success = result.returncode == 0 and not err_steps
error = None if success else f"Le script s'est terminé avec le code {result.returncode}"
return context.make_result(task, success=success, changed=True, started_at=started_at, details=details, error=error)
finally:
if script_path and script_path.exists():
script_path.unlink(missing_ok=True)
def bind(task: TaskDefinition, func) -> TaskDefinition:
return TaskDefinition(
key=task.key,