From 6afdf69af5107601134776657c606897fdf4bd1d Mon Sep 17 00:00:00 2001 From: Crank-Git Date: Sat, 11 Apr 2026 19:03:18 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20packaging=20=E2=80=94=20.deb/.rpm=20via?= =?UTF-8?q?=20nfpm=20and=20Docker=20image?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operators can now install ja4monitor via apt/rpm or run it in Docker. ## .deb / .rpm (via nfpm) packaging/ nfpm.yaml — nfpm package config (arch/version templated from env) ja4monitor.service — systemd unit with capability bounding set + hardening scripts/postinst — creates ja4monitor system user, data dir, setcap scripts/prerm — stops/disables systemd unit before removal scripts/postrm — purge removes user, data, and config dirs postinst grants CAP_NET_RAW and CAP_NET_ADMIN via setcap so the daemon runs as the ja4monitor system user without root. Falls back gracefully when setcap is unavailable (prints a warning). ## Docker Dockerfile — two-stage build (golang:1.25-bookworm builder + debian:bookworm-slim runtime). Runtime image: libpcap + ca-certificates, non-root ja4monitor user, /data volume for SQLite. Capabilities granted at runtime via --cap-add; binary is NOT setuid in the container. .dockerignore added to exclude testdata, captures, and build artifacts. ## Release workflow .github/workflows/release.yml updated to: 1. Install nfpm after building 2. Package amd64 and arm64 .deb and .rpm 3. Upload all packages alongside binaries in the GitHub Release Co-Authored-By: Claude Opus 4.6 --- .dockerignore | 10 ++++++ .github/workflows/release.yml | 49 +++++++++++++++++++++++++- .goreleaser.yml | 61 ++++++++++++++++++++++++++++++++ Dockerfile | 64 ++++++++++++++++++++++++++++++++++ packaging/ja4monitor.service | 40 +++++++++++++++++++++ packaging/nfpm.yaml | 65 +++++++++++++++++++++++++++++++++++ packaging/scripts/postinst | 48 ++++++++++++++++++++++++++ packaging/scripts/postrm | 22 ++++++++++++ packaging/scripts/prerm | 16 +++++++++ 9 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 packaging/ja4monitor.service create mode 100644 packaging/nfpm.yaml create mode 100755 packaging/scripts/postinst create mode 100755 packaging/scripts/postrm create mode 100755 packaging/scripts/prerm diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a9224d5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.github +dist/ +testdata/ +*.pcapng +*.pcap +*.zip +captures*/ +__MACOSX/ +ja4monitor diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4113d3c..105672a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,10 +86,53 @@ jobs: go build -tags pcaponly -ldflags "-X main.version=$VERSION" \ -o dist/ja4monitor-linux-arm64-pcaponly ./cmd/ja4monitor/ + - name: Install nfpm + run: | + NFPM_VERSION=2.40.0 + curl -fsSL "https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_Linux_x86_64.tar.gz" \ + | sudo tar -xz -C /usr/local/bin nfpm + nfpm --version + + - name: Package amd64 (.deb) + run: | + cp dist/ja4monitor-linux-amd64 dist/ja4monitor + ARCH=amd64 VERSION=${VERSION#v} nfpm package \ + --config packaging/nfpm.yaml \ + --packager deb \ + --target dist/ja4monitor_${VERSION#v}_linux_amd64.deb + rm dist/ja4monitor + + - name: Package amd64 (.rpm) + run: | + cp dist/ja4monitor-linux-amd64 dist/ja4monitor + ARCH=amd64 VERSION=${VERSION#v} nfpm package \ + --config packaging/nfpm.yaml \ + --packager rpm \ + --target dist/ja4monitor_${VERSION#v}_linux_amd64.rpm + rm dist/ja4monitor + + - name: Package arm64 (.deb) + run: | + cp dist/ja4monitor-linux-arm64 dist/ja4monitor + ARCH=arm64 VERSION=${VERSION#v} nfpm package \ + --config packaging/nfpm.yaml \ + --packager deb \ + --target dist/ja4monitor_${VERSION#v}_linux_arm64.deb + rm dist/ja4monitor + + - name: Package arm64 (.rpm) + run: | + cp dist/ja4monitor-linux-arm64 dist/ja4monitor + ARCH=arm64 VERSION=${VERSION#v} nfpm package \ + --config packaging/nfpm.yaml \ + --packager rpm \ + --target dist/ja4monitor_${VERSION#v}_linux_arm64.rpm + rm dist/ja4monitor + - name: Checksum run: | cd dist - sha256sum ja4monitor-* > sha256sums.txt + sha256sum ja4monitor-* *.deb *.rpm > sha256sums.txt cat sha256sums.txt - name: Create GitHub Release @@ -100,6 +143,10 @@ jobs: dist/ja4monitor-linux-amd64-pcaponly dist/ja4monitor-linux-arm64 dist/ja4monitor-linux-arm64-pcaponly + dist/ja4monitor_*_linux_amd64.deb + dist/ja4monitor_*_linux_amd64.rpm + dist/ja4monitor_*_linux_arm64.deb + dist/ja4monitor_*_linux_arm64.rpm dist/sha256sums.txt generate_release_notes: true draft: false diff --git a/.goreleaser.yml b/.goreleaser.yml index 14accdc..0ce3641 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -18,6 +18,67 @@ archives: - format: tar.gz name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" +# .deb and .rpm packages (Linux only) +nfpms: + - package_name: ja4monitor + vendor: Crank-Git + homepage: https://github.com/Crank-Git/ja4monitor + maintainer: Crank-Git + description: | + Passive JA4 network fingerprint sensor. + Captures TLS, HTTP, SSH, and TCP fingerprints from live traffic or PCAP + files and alerts on anomalies: new fingerprints, known malware signatures, + protocol mismatches, and behavioral novelty. + license: MIT + section: net + priority: optional + + # Only build packages for Linux; Darwin .pkg is out of scope. + builds: + - ja4monitor + goos: + - linux + goarch: + - amd64 + - arm64 + + formats: + - deb + - rpm + + dependencies: + - libpcap + + rpm: + group: Applications/System + + contents: + # Example config — installed to /etc; not overwritten on upgrade + - src: configs/ja4monitor.example.toml + dst: /etc/ja4monitor/ja4monitor.toml + type: config|noreplace + file_info: + mode: 0640 + owner: root + group: ja4monitor + + # Example custom rules — reference only, not loaded by default + - src: configs/custom_rules.example.toml + dst: /etc/ja4monitor/custom_rules.example.toml + file_info: + mode: 0644 + + # Systemd unit + - src: packaging/ja4monitor.service + dst: /lib/systemd/system/ja4monitor.service + file_info: + mode: 0644 + + scripts: + postinstall: packaging/scripts/postinst + preremove: packaging/scripts/prerm + postremove: packaging/scripts/postrm + checksum: name_template: checksums.txt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..83ec0dc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,64 @@ +# ja4monitor Dockerfile +# +# Build: +# docker build -t ja4monitor:latest . +# +# Run (live capture — requires host network and raw-socket capabilities): +# docker run --rm -it \ +# --cap-add NET_RAW \ +# --cap-add NET_ADMIN \ +# --network host \ +# -v /var/lib/ja4monitor:/data \ +# ja4monitor:latest \ +# --interface eth0 --db /data/ja4monitor.db +# +# Run (PCAP replay — no special capabilities needed): +# docker run --rm -it \ +# -v /path/to/captures:/pcap:ro \ +# ja4monitor:latest \ +# /pcap/traffic.pcapng +# +# Kubernetes note: +# Live capture requires a privileged DaemonSet or a custom seccomp profile +# that allows CAP_NET_RAW and CAP_NET_ADMIN. See README for a reference +# DaemonSet manifest. +# +# ─── Build stage ──────────────────────────────────────────────────────────── +FROM golang:1.25-bookworm AS builder + +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=1 go build \ + -ldflags="-s -w" \ + -o /out/ja4monitor \ + ./cmd/ja4monitor/ + +# ─── Runtime stage ─────────────────────────────────────────────────────────── +FROM debian:bookworm-slim + +# libpcap is required for live capture. ca-certificates enables HTTPS webhooks. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + libpcap0.8 \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Non-root user. Capabilities are granted at runtime via --cap-add, NOT +# by making the binary setuid — the container runtime handles privilege. +RUN groupadd --system ja4monitor \ + && useradd --system --gid ja4monitor --no-create-home \ + --shell /usr/sbin/nologin ja4monitor + +COPY --from=builder /out/ja4monitor /usr/bin/ja4monitor + +# Data directory for the SQLite database. +RUN install -d -o ja4monitor -g ja4monitor -m 750 /data + +USER ja4monitor +WORKDIR /data + +ENTRYPOINT ["/usr/bin/ja4monitor"] +CMD ["--help"] diff --git a/packaging/ja4monitor.service b/packaging/ja4monitor.service new file mode 100644 index 0000000..2d4459b --- /dev/null +++ b/packaging/ja4monitor.service @@ -0,0 +1,40 @@ +[Unit] +Description=ja4monitor — passive JA4 fingerprint sensor +Documentation=https://github.com/Crank-Git/ja4monitor +After=network.target +Wants=network.target + +[Service] +Type=simple +User=ja4monitor +Group=ja4monitor + +# The binary has CAP_NET_RAW and CAP_NET_ADMIN set via setcap in postinst. +# AmbientCapabilities ensures those capabilities survive the setuid drop. +AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN +CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN + +ExecStart=/usr/bin/ja4monitor daemon --config /etc/ja4monitor/ja4monitor.toml +Restart=on-failure +RestartSec=5s + +# Harden the service: deny most kernel interfaces that a passive sensor +# doesn't need. +NoNewPrivileges=yes +ProtectSystem=strict +ProtectHome=yes +ReadWritePaths=/var/lib/ja4monitor /var/log/ja4monitor +PrivateTmp=yes +PrivateDevices=no # needs /dev/net for packet capture +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectControlGroups=yes +RestrictSUIDSGID=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +RestrictRealtime=yes +SystemCallFilter=@system-service +SystemCallArchitectures=native + +[Install] +WantedBy=multi-user.target diff --git a/packaging/nfpm.yaml b/packaging/nfpm.yaml new file mode 100644 index 0000000..8b225a2 --- /dev/null +++ b/packaging/nfpm.yaml @@ -0,0 +1,65 @@ +# nfpm package configuration for ja4monitor +# Run from repo root: ARCH=amd64 VERSION=0.8.0 nfpm package --config packaging/nfpm.yaml --packager deb +# https://nfpm.goreleaser.com/configuration/ + +name: ja4monitor +arch: ${ARCH} +platform: linux +version: ${VERSION} +section: net +priority: optional +maintainer: Crank-Git +description: | + Passive JA4 network fingerprint sensor. + Captures TLS, HTTP, SSH, and TCP fingerprints from live traffic or PCAP + files and alerts on anomalies: new fingerprints, known malware signatures, + protocol mismatches, and behavioral novelty. +vendor: Crank-Git +homepage: https://github.com/Crank-Git/ja4monitor +license: MIT + +# Per-format dependency names differ; use overrides to avoid deb getting +# the RPM package name and vice versa. +overrides: + deb: + depends: + - libpcap0.8 + rpm: + depends: + - libpcap + rpm: + group: Applications/System + +contents: + # Binary — the release workflow copies the arch-specific binary to dist/ja4monitor + # before invoking nfpm, so the source path is always ../dist/ja4monitor. + - src: ../dist/ja4monitor + dst: /usr/bin/ja4monitor + file_info: + mode: 0755 + + # Example config — installed to /etc; not overwritten on upgrade. + - src: ../configs/ja4monitor.example.toml + dst: /etc/ja4monitor/ja4monitor.toml + type: config|noreplace + file_info: + mode: 0640 + owner: root + group: ja4monitor + + # Example custom rules — reference only, not loaded by default. + - src: ../configs/custom_rules.example.toml + dst: /etc/ja4monitor/custom_rules.example.toml + file_info: + mode: 0644 + + # Systemd unit. + - src: ja4monitor.service + dst: /lib/systemd/system/ja4monitor.service + file_info: + mode: 0644 + +scripts: + postinstall: scripts/postinst + preremove: scripts/prerm + postremove: scripts/postrm diff --git a/packaging/scripts/postinst b/packaging/scripts/postinst new file mode 100755 index 0000000..05c2657 --- /dev/null +++ b/packaging/scripts/postinst @@ -0,0 +1,48 @@ +#!/bin/sh +# ja4monitor postinst — runs after package install/upgrade +set -e + +# Create system user and group (no login shell, no home directory). +# getent check makes this idempotent across upgrades. +if ! getent group ja4monitor >/dev/null 2>&1; then + groupadd --system ja4monitor +fi +if ! getent passwd ja4monitor >/dev/null 2>&1; then + useradd --system \ + --gid ja4monitor \ + --no-create-home \ + --shell /usr/sbin/nologin \ + --comment "ja4monitor network monitor" \ + ja4monitor +fi + +# Create data directory owned by the service user. +install -d -o ja4monitor -g ja4monitor -m 750 /var/lib/ja4monitor + +# Create log directory. +install -d -o ja4monitor -g ja4monitor -m 750 /var/log/ja4monitor + +# Grant raw-socket capture capability so the daemon runs without root. +# setcap is preferred over adding the user to a privileged group because +# the capability is scoped to this binary only. +# +# Required capabilities: +# CAP_NET_RAW — raw packet capture (libpcap / AF_PACKET) +# CAP_NET_ADMIN — required by some kernel versions for promiscuous mode +if command -v setcap >/dev/null 2>&1; then + setcap cap_net_raw,cap_net_admin=eip /usr/bin/ja4monitor +else + echo "WARNING: setcap not found. ja4monitor must run as root or with" >&2 + echo " CAP_NET_RAW and CAP_NET_ADMIN capabilities set manually." >&2 +fi + +# Install and enable the systemd unit if systemd is available. +if command -v systemctl >/dev/null 2>&1 && systemctl is-system-running --quiet 2>/dev/null; then + systemctl daemon-reload + # Enable but do not start — operator must configure /etc/ja4monitor/ja4monitor.toml first. + systemctl enable ja4monitor.service 2>/dev/null || true + echo "ja4monitor installed. Configure /etc/ja4monitor/ja4monitor.toml then:" + echo " systemctl start ja4monitor" +fi + +exit 0 diff --git a/packaging/scripts/postrm b/packaging/scripts/postrm new file mode 100755 index 0000000..c3a7051 --- /dev/null +++ b/packaging/scripts/postrm @@ -0,0 +1,22 @@ +#!/bin/sh +# ja4monitor postrm — runs after package removal +set -e + +case "$1" in + purge) + # Full purge: remove data, logs, config, and system user. + rm -rf /var/lib/ja4monitor /var/log/ja4monitor /etc/ja4monitor + + if getent passwd ja4monitor >/dev/null 2>&1; then + userdel ja4monitor 2>/dev/null || true + fi + if getent group ja4monitor >/dev/null 2>&1; then + groupdel ja4monitor 2>/dev/null || true + fi + ;; + remove) + # Plain remove: keep data and config (operator may reinstall). + ;; +esac + +exit 0 diff --git a/packaging/scripts/prerm b/packaging/scripts/prerm new file mode 100755 index 0000000..408a3a6 --- /dev/null +++ b/packaging/scripts/prerm @@ -0,0 +1,16 @@ +#!/bin/sh +# ja4monitor prerm — runs before package removal +set -e + +# Stop and disable the systemd service before removing the binary. +# 'remove' and 'deconfigure' are dpkg states; 'delete' is rpm. +case "$1" in + remove|deconfigure|delete) + if command -v systemctl >/dev/null 2>&1; then + systemctl stop ja4monitor.service 2>/dev/null || true + systemctl disable ja4monitor.service 2>/dev/null || true + fi + ;; +esac + +exit 0