Skip to content

Commit 3840388

Browse files
committed
feat: add ns-audit
1 parent 3d45a07 commit 3840388

35 files changed

Lines changed: 8373 additions & 0 deletions

config/ns-audit.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CONFIG_PACKAGE_ns-audit=y

packages/ns-audit/Makefile

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#
2+
# Copyright (C) 2026 Nethesis S.r.l.
3+
# SPDX-License-Identifier: GPL-2.0-only
4+
#
5+
6+
include $(TOPDIR)/rules.mk
7+
8+
PKG_NAME:=ns-audit
9+
PKG_VERSION:=0.1.0
10+
PKG_RELEASE:=1
11+
12+
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
13+
14+
PKG_MAINTAINER:=Giacomo Sanchietti <giacomo.sanchietti@nethesis.it>
15+
PKG_LICENSE:=GPL-2.0-only
16+
17+
include $(INCLUDE_DIR)/package.mk
18+
19+
define Package/ns-audit
20+
SECTION:=base
21+
CATEGORY:=NethSecurity
22+
TITLE:=NethSecurity on-device audit collector
23+
URL:=https://github.com/NethServer/nethsecurity
24+
DEPENDS:=+python3 +python3-nethsec +python3-jinja2
25+
PKGARCH:=all
26+
endef
27+
28+
define Package/ns-audit/description
29+
Read-only local audit evidence collector for NethSecurity.
30+
endef
31+
32+
define Package/ns-audit/postinst
33+
#!/bin/sh
34+
if [ -z "$${IPKG_INSTROOT}" ] && [ -x /etc/init.d/rpcd ]; then
35+
/etc/init.d/rpcd restart
36+
fi
37+
exit 0
38+
endef
39+
40+
define Package/ns-audit/postrm
41+
#!/bin/sh
42+
if [ -z "$${IPKG_INSTROOT}" ] && [ -x /etc/init.d/rpcd ]; then
43+
/etc/init.d/rpcd restart
44+
fi
45+
exit 0
46+
endef
47+
48+
# this is required, otherwise compile will fail
49+
define Build/Compile
50+
endef
51+
52+
define Package/ns-audit/install
53+
$(INSTALL_DIR) $(1)/usr/sbin
54+
$(INSTALL_BIN) ./files/ns-audit.py $(1)/usr/sbin/ns-audit
55+
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
56+
$(INSTALL_BIN) ./files/ns.audit $(1)/usr/libexec/rpcd/ns.audit
57+
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
58+
$(INSTALL_DATA) ./files/ns.audit.json $(1)/usr/share/rpcd/acl.d/ns.audit.json
59+
$(INSTALL_DIR) $(1)/usr/share/ns-audit
60+
$(CP) ./files/ns_audit $(1)/usr/share/ns-audit/
61+
endef
62+
63+
$(eval $(call BuildPackage,ns-audit))

packages/ns-audit/README.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# ns-audit
2+
3+
On-device NethSecurity audit reporting package.
4+
5+
This package provides a read-only local audit pipeline for NethSecurity. It
6+
collects sanitized evidence, runs the available analyzers, renders
7+
`audit-report.html` with Jinja2, packages it into `audit-report.tar.gz`, and
8+
exposes an optional read-only `ns.audit` RPCD endpoint. It does not generate
9+
PDF files or change appliance configuration.
10+
11+
## Execution model
12+
13+
- Runs locally on the NethSecurity appliance.
14+
- Does not use SSH, Paramiko, remote collectors, or external report services.
15+
- Does not write UCI configuration, call `uci commit`, restart services, or
16+
modify `/etc/config/*`.
17+
- Reads local configuration and runtime state to build sanitized JSON artifacts.
18+
- Produces HTML, JSON, and tar.gz bundle artifacts; PDF output is intentionally unsupported.
19+
- The RPCD endpoint only writes report artifacts below
20+
`/var/run/ns-audit/<report_id>/`.
21+
22+
Expected input directory:
23+
24+
```text
25+
/var/run/ns-audit/<run-id>/
26+
raw_snapshot.json
27+
inventory.json
28+
findings.json
29+
compliance_mapping.json
30+
summary.json
31+
```
32+
33+
Report rendering:
34+
35+
```bash
36+
PYTHONPATH=/usr/share/ns-audit python3 -m ns_audit.reporting.html render \
37+
--input /var/run/ns-audit/latest \
38+
--output /var/run/ns-audit/latest/audit-report.html
39+
```
40+
41+
From a repository checkout, use `PYTHONPATH=packages/ns-audit/files` instead.
42+
The top-level CLI is expected to wrap the same renderer as:
43+
44+
```bash
45+
ns-audit report --input /var/run/ns-audit/latest \
46+
--output /var/run/ns-audit/latest/audit-report.html
47+
ns-audit report --input /var/run/ns-audit/latest \
48+
--output /var/run/ns-audit/latest/audit-report.tar.gz
49+
```
50+
51+
The tarball form renders the HTML report into the same output directory and then
52+
packages the HTML, JSON artifacts, and deduplicated log files.
53+
54+
## Outputs
55+
56+
- `audit-report.html`: HTML report with embedded CSS and links to collected logs.
57+
- `audit-report.tar.gz`: bundle containing the HTML report, JSON artifacts, and deduplicated log copies under `logs/`.
58+
- Existing JSON artifacts remain the source of truth:
59+
- `raw_snapshot.json`
60+
- `inventory.json`
61+
- `findings.json`
62+
- `compliance_mapping.json`
63+
- `summary.json`
64+
65+
The HTML report includes:
66+
67+
1. Executive Summary
68+
2. Configuration Inventory
69+
3. Gap Analysis & Compliance Mapping
70+
4. Actionable Remediation Plan
71+
5. Collected Logs
72+
6. Evidence Appendix
73+
74+
## Validation
75+
76+
Render-time validation refuses to write HTML if known private-key, password-hash,
77+
preshared-key, or token patterns are detected.
78+
79+
Validate all generated artifacts:
80+
81+
```bash
82+
PYTHONPATH=/usr/share/ns-audit python3 -m ns_audit.reporting.html validate /var/run/ns-audit/latest
83+
```
84+
85+
The command returns JSON:
86+
87+
```json
88+
{"matches": [], "status": "ok"}
89+
```
90+
91+
## Package installation notes
92+
93+
The package integration installs the CLI, Python modules, reporting assets, and
94+
the optional RPCD wrapper:
95+
96+
- depending on `+python3-jinja2`;
97+
- installing `files/ns-audit.py` as `/usr/sbin/ns-audit`;
98+
- installing `files/ns_audit/reporting/*.py` as Python module files;
99+
- installing `files/ns_audit/reporting/templates/audit-report.html.j2`;
100+
- installing `files/ns_audit/reporting/assets/audit-report.css`;
101+
- installing `/usr/libexec/rpcd/ns.audit` and its ACL file;
102+
- restarting `rpcd` on live package install/removal so the endpoint is
103+
registered.
104+
105+
If a downstream branch does not have an `ns-audit` Makefile yet, add equivalent
106+
install rules when the package skeleton is introduced.
107+
108+
## RPCD wrapper
109+
110+
The package installs `/usr/libexec/rpcd/ns.audit` and
111+
`/usr/share/rpcd/acl.d/ns.audit.json`. The endpoint is read-only with respect to
112+
appliance configuration: it does not write UCI configuration, call
113+
`uci commit`, or restart services.
114+
115+
Methods:
116+
117+
- `generate`: optionally accepts `report_id`, runs the installed
118+
`ns_audit.cli.run(output_dir)` pipeline, and returns `report_id`,
119+
`output_dir`, generated artifacts (including `audit-report.html` and
120+
`audit-report.tar.gz`), and `summary` when readable.
121+
- `list-reports`: lists safe report directories under `/var/run/ns-audit`.
122+
- `get-summary`: requires `report_id` and returns the matching `summary.json`.
123+
124+
`report_id` must match `[A-Za-z0-9_.-]+`; if omitted by `generate`, a
125+
timestamped ID is generated. Arbitrary output paths are not accepted.
126+
127+
Examples:
128+
129+
```bash
130+
ubus call ns.audit generate '{}'
131+
ubus call ns.audit generate '{"report_id":"manual-2026-01-01"}'
132+
ubus call ns.audit list-reports '{}'
133+
ubus call ns.audit get-summary '{"report_id":"manual-2026-01-01"}'
134+
```
135+
136+
The same methods are available through `api-cli`:
137+
138+
```bash
139+
api-cli ns.audit generate --data '{}'
140+
api-cli ns.audit list-reports
141+
api-cli ns.audit get-summary --data '{"report_id":"manual-2026-01-01"}'
142+
```
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/python3
2+
#
3+
# Copyright (C) 2026 Nethesis S.r.l.
4+
# SPDX-License-Identifier: GPL-2.0-only
5+
#
6+
7+
import os
8+
import sys
9+
10+
11+
SHARE_DIR = "/usr/share/ns-audit"
12+
LOCAL_DIR = os.path.dirname(os.path.abspath(__file__))
13+
14+
15+
def main() -> int:
16+
if os.path.isdir(os.path.join(SHARE_DIR, "ns_audit")):
17+
sys.path.insert(0, SHARE_DIR)
18+
else:
19+
sys.path.insert(0, LOCAL_DIR)
20+
21+
from ns_audit.cli import main as cli_main
22+
23+
return cli_main()
24+
25+
26+
if __name__ == "__main__":
27+
raise SystemExit(main())

0 commit comments

Comments
 (0)