diff --git a/debian/.debhelper/generated/linux-patch-api/dh_installchangelogs.dch.trimmed b/debian/.debhelper/generated/linux-patch-api/dh_installchangelogs.dch.trimmed new file mode 100644 index 0000000..91d06fb --- /dev/null +++ b/debian/.debhelper/generated/linux-patch-api/dh_installchangelogs.dch.trimmed @@ -0,0 +1,11 @@ +linux-patch-api (1.0.0-1) stable; urgency=medium + + * Initial production release + * Secure mTLS-authenticated REST API for remote package management + * 15 API endpoints for package install/remove, patch application, system management + * Asynchronous job processing with WebSocket status streaming + * IP whitelist enforcement and comprehensive audit logging + * Systemd integration with security hardening + * Supports Debian 11/12, Ubuntu 20.04/22.04/24.04 + + -- Echo Thu, 09 Apr 2026 18:57:12 -0500 diff --git a/debian/.debhelper/generated/linux-patch-api/installed-by-dh_install b/debian/.debhelper/generated/linux-patch-api/installed-by-dh_install new file mode 100644 index 0000000..fb3c2db --- /dev/null +++ b/debian/.debhelper/generated/linux-patch-api/installed-by-dh_install @@ -0,0 +1,4 @@ +debian/tmp/usr/bin/linux-patch-api +debian/tmp/lib/systemd/system/linux-patch-api.service +debian/tmp/etc/linux_patch_api/config.yaml +debian/tmp/etc/linux_patch_api/whitelist.yaml diff --git a/debian/.debhelper/generated/linux-patch-api/installed-by-dh_installdocs b/debian/.debhelper/generated/linux-patch-api/installed-by-dh_installdocs new file mode 100644 index 0000000..e69de29 diff --git a/debian/.debhelper/generated/linux-patch-api/postinst.service b/debian/.debhelper/generated/linux-patch-api/postinst.service new file mode 100644 index 0000000..f5a8541 --- /dev/null +++ b/debian/.debhelper/generated/linux-patch-api/postinst.service @@ -0,0 +1,30 @@ +# Automatically added by dh_installsystemd/13.31 +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + # The following line should be removed in trixie or trixie+1 + deb-systemd-helper unmask 'linux-patch-api.service' >/dev/null || true + + # was-enabled defaults to true, so new installations run enable. + if deb-systemd-helper --quiet was-enabled 'linux-patch-api.service'; then + # Enables the unit on first installation, creates new + # symlinks on upgrades if the unit file has changed. + deb-systemd-helper enable 'linux-patch-api.service' >/dev/null || true + else + # Update the statefile to add new symlinks (if any), which need to be + # cleaned up on purge. Also remove old symlinks. + deb-systemd-helper update-state 'linux-patch-api.service' >/dev/null || true + fi +fi +# End automatically added section +# Automatically added by dh_installsystemd/13.31 +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + if [ -n "$2" ]; then + _dh_action=restart + else + _dh_action=start + fi + deb-systemd-invoke $_dh_action 'linux-patch-api.service' >/dev/null || true + fi +fi +# End automatically added section diff --git a/debian/.debhelper/generated/linux-patch-api/prerm.service b/debian/.debhelper/generated/linux-patch-api/prerm.service new file mode 100644 index 0000000..20371c8 --- /dev/null +++ b/debian/.debhelper/generated/linux-patch-api/prerm.service @@ -0,0 +1,5 @@ +# Automatically added by dh_installsystemd/13.31 +if [ -z "$DPKG_ROOT" ] && [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + deb-systemd-invoke stop 'linux-patch-api.service' >/dev/null || true +fi +# End automatically added section diff --git a/debian/debhelper-build-stamp b/debian/debhelper-build-stamp new file mode 100644 index 0000000..ce51d6f --- /dev/null +++ b/debian/debhelper-build-stamp @@ -0,0 +1 @@ +linux-patch-api diff --git a/debian/files b/debian/files new file mode 100644 index 0000000..a40cf9d --- /dev/null +++ b/debian/files @@ -0,0 +1,2 @@ +linux-patch-api_1.0.0-1_amd64.buildinfo admin optional +linux-patch-api_1.0.0-1_amd64.deb admin optional diff --git a/debian/linux-patch-api.debhelper.log b/debian/linux-patch-api.debhelper.log new file mode 100644 index 0000000..a37a5ef --- /dev/null +++ b/debian/linux-patch-api.debhelper.log @@ -0,0 +1 @@ +dh_auto_install diff --git a/debian/linux-patch-api.postrm.debhelper b/debian/linux-patch-api.postrm.debhelper new file mode 100644 index 0000000..0198b44 --- /dev/null +++ b/debian/linux-patch-api.postrm.debhelper @@ -0,0 +1,12 @@ +# Automatically added by dh_installsystemd/13.31 +if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section +# Automatically added by dh_installsystemd/13.31 +if [ "$1" = "purge" ]; then + if [ -x "/usr/bin/deb-systemd-helper" ]; then + deb-systemd-helper purge 'linux-patch-api.service' >/dev/null || true + fi +fi +# End automatically added section diff --git a/debian/linux-patch-api.substvars b/debian/linux-patch-api.substvars new file mode 100644 index 0000000..85ab245 --- /dev/null +++ b/debian/linux-patch-api.substvars @@ -0,0 +1,3 @@ +shlibs:Depends=libc6 (>= 2.39), libgcc-s1 (>= 4.2) +misc:Depends= +misc:Pre-Depends= diff --git a/debian/linux-patch-api/DEBIAN/conffiles b/debian/linux-patch-api/DEBIAN/conffiles new file mode 100644 index 0000000..da35b32 --- /dev/null +++ b/debian/linux-patch-api/DEBIAN/conffiles @@ -0,0 +1,4 @@ +/etc/linux_patch_api/config.yaml +/etc/linux_patch_api/whitelist.yaml +/etc/linux_patch_api/config.yaml +/etc/linux_patch_api/whitelist.yaml diff --git a/debian/linux-patch-api/DEBIAN/control b/debian/linux-patch-api/DEBIAN/control new file mode 100644 index 0000000..0cf6069 --- /dev/null +++ b/debian/linux-patch-api/DEBIAN/control @@ -0,0 +1,23 @@ +Package: linux-patch-api +Version: 1.0.0-1 +Architecture: amd64 +Maintainer: Echo +Installed-Size: 8897 +Depends: systemd, libsystemd0, libc6 (>= 2.39), libgcc-s1 (>= 4.2) +Section: admin +Priority: optional +Homepage: https://gitea.moon-dragon.us/echo/linux_patch_api +Description: Secure remote package management API for Linux systems + Linux Patch API provides a secure, mTLS-authenticated REST API for + remote package management operations including: + - Package installation and removal + - Security patch application + - System health monitoring + - Job queue management with WebSocket status streaming + . + Features: + - Mutual TLS (mTLS) authentication + - IP whitelist enforcement + - Asynchronous job processing + - Comprehensive audit logging + - Systemd integration with security hardening diff --git a/debian/linux-patch-api/DEBIAN/md5sums b/debian/linux-patch-api/DEBIAN/md5sums new file mode 100644 index 0000000..b044594 --- /dev/null +++ b/debian/linux-patch-api/DEBIAN/md5sums @@ -0,0 +1,5 @@ +23b89eecc51f46c6813658dd615d13a9 lib/systemd/system/linux-patch-api.service +d64a80e2a796561c39c6941c6b9e268c usr/bin/linux-patch-api +154c7ae7e01ae22cdc8ceea1fd0956e2 usr/share/doc/linux-patch-api/changelog.Debian.gz +978478c6c7f1e9dcb38eb1f2454535c0 usr/share/doc/linux-patch-api/changelog.gz +c2fab316c94aa61adb70d79365cfe08f usr/share/doc/linux-patch-api/copyright diff --git a/debian/linux-patch-api/DEBIAN/postinst b/debian/linux-patch-api/DEBIAN/postinst new file mode 100755 index 0000000..1063d94 --- /dev/null +++ b/debian/linux-patch-api/DEBIAN/postinst @@ -0,0 +1,49 @@ +#!/bin/bash +# postinst script for linux-patch-api +# Created by package build system + +set -e + +# Configure with debhelper +if [ "$1" = "configure" ]; then + echo "Configuring linux-patch-api..." + + # Copy example configs if they don't exist + if [ ! -f "/etc/linux_patch_api/config.yaml" ]; then + echo "Creating default config.yaml..." + cp /etc/linux_patch_api/config.yaml.example /etc/linux_patch_api/config.yaml + chmod 640 /etc/linux_patch_api/config.yaml + chown linux-patch-api:linux-patch-api /etc/linux_patch_api/config.yaml + fi + + if [ ! -f "/etc/linux_patch_api/whitelist.yaml" ]; then + echo "Creating default whitelist.yaml..." + cp /etc/linux_patch_api/whitelist.yaml.example /etc/linux_patch_api/whitelist.yaml + chmod 640 /etc/linux_patch_api/whitelist.yaml + chown linux-patch-api:linux-patch-api /etc/linux_patch_api/whitelist.yaml + fi + + # Reload systemd daemon to pick up new service file + systemctl daemon-reload + + # Enable the service (but don't start automatically - admin should configure first) + systemctl enable linux-patch-api.service + + echo "" + echo "linux-patch-api installed successfully!" + echo "" + echo "Next steps:" + echo " 1. Configure /etc/linux_patch_api/config.yaml with your settings" + echo " 2. Place TLS certificates in /etc/linux_patch_api/certs/" + echo " 3. Configure IP whitelist in /etc/linux_patch_api/whitelist.yaml" + echo " 4. Start the service: systemctl start linux-patch-api" + echo " 5. Check status: systemctl status linux-patch-api" + echo "" +fi + +# Handle upgrade +if [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-remove" ] || [ "$1" = "abort-deconfigure" ]; then + echo "Installation aborted - service remains in previous state" +fi + +exit 0 diff --git a/debian/linux-patch-api/DEBIAN/postrm b/debian/linux-patch-api/DEBIAN/postrm new file mode 100755 index 0000000..fbde51f --- /dev/null +++ b/debian/linux-patch-api/DEBIAN/postrm @@ -0,0 +1,64 @@ +#!/bin/bash +# postrm script for linux-patch-api +# Created by package build system + +set -e + +# Handle purge - remove all configuration and data +if [ "$1" = "purge" ]; then + echo "Purging linux-patch-api configuration and data..." + + # Stop service if still running + if systemctl is-active --quiet linux-patch-api.service 2>/dev/null; then + systemctl stop linux-patch-api.service + fi + + # Disable service + if systemctl is-enabled --quiet linux-patch-api.service 2>/dev/null; then + systemctl disable linux-patch-api.service + fi + + # Reload systemd to remove service file + systemctl daemon-reload + + # Remove configuration directory (preserved by conffiles during normal remove) + if [ -d "/etc/linux_patch_api" ]; then + echo "Removing /etc/linux_patch_api..." + rm -rf /etc/linux_patch_api + fi + + # Remove data directory + if [ -d "/var/lib/linux_patch_api" ]; then + echo "Removing /var/lib/linux_patch_api..." + rm -rf /var/lib/linux_patch_api + fi + + # Remove log directory + if [ -d "/var/log/linux_patch_api" ]; then + echo "Removing /var/log/linux_patch_api..." + rm -rf /var/log/linux_patch_api + fi + + # Remove system user + if getent passwd linux-patch-api > /dev/null 2>&1; then + echo "Removing user linux-patch-api..." + userdel linux-patch-api 2>/dev/null || true + fi + + # Remove system group + if getent group linux-patch-api > /dev/null 2>&1; then + echo "Removing group linux-patch-api..." + groupdel linux-patch-api 2>/dev/null || true + fi + + echo "linux-patch-api purged successfully" +fi + +# Handle upgrade/remove - just ensure service is disabled +if [ "$1" = "remove" ] || [ "$1" = "upgrade" ]; then + # Service should already be stopped by prerm + # Just reload systemd to remove the service file + systemctl daemon-reload 2>/dev/null || true +fi + +exit 0 diff --git a/debian/linux-patch-api/DEBIAN/preinst b/debian/linux-patch-api/DEBIAN/preinst new file mode 100755 index 0000000..ec05903 --- /dev/null +++ b/debian/linux-patch-api/DEBIAN/preinst @@ -0,0 +1,46 @@ +#!/bin/bash +# preinst script for linux-patch-api +# Created by package build system + +set -e + +# Check if this is an upgrade +if [ -d "/etc/linux_patch_api" ]; then + echo "Detected existing installation - performing upgrade" +fi + +# Create system user if it doesn't exist +if ! getent group linux-patch-api > /dev/null 2>&1; then + echo "Creating group linux-patch-api..." + groupadd --system linux-patch-api +fi + +if ! getent passwd linux-patch-api > /dev/null 2>&1; then + echo "Creating user linux-patch-api..." + useradd --system \ + --gid linux-patch-api \ + --home-dir /var/lib/linux_patch_api \ + --no-create-home \ + --shell /usr/sbin/nologin \ + --comment "Linux Patch API Service" \ + linux-patch-api +fi + +# Create required directories +mkdir -p /etc/linux_patch_api/certs +mkdir -p /var/lib/linux_patch_api +mkdir -p /var/log/linux_patch_api + +# Set proper ownership +chown -R linux-patch-api:linux-patch-api /var/lib/linux_patch_api +chown -R linux-patch-api:linux-patch-api /var/log/linux_patch_api + +# Set secure permissions +chmod 750 /etc/linux_patch_api +chmod 750 /etc/linux_patch_api/certs +chmod 755 /var/lib/linux_patch_api +chmod 755 /var/log/linux_patch_api + +echo "Pre-installation checks completed successfully" + +exit 0 diff --git a/debian/linux-patch-api/DEBIAN/prerm b/debian/linux-patch-api/DEBIAN/prerm new file mode 100755 index 0000000..28d9686 --- /dev/null +++ b/debian/linux-patch-api/DEBIAN/prerm @@ -0,0 +1,33 @@ +#!/bin/bash +# prerm script for linux-patch-api +# Created by package build system + +set -e + +# Stop the service before removal/upgrade +if [ "$1" = "remove" ] || [ "$1" = "upgrade" ]; then + echo "Stopping linux-patch-api service..." + + if systemctl is-active --quiet linux-patch-api.service; then + systemctl stop linux-patch-api.service + echo "Service stopped successfully" + else + echo "Service was not running" + fi + + # Disable the service + if systemctl is-enabled --quiet linux-patch-api.service 2>/dev/null; then + systemctl disable linux-patch-api.service + echo "Service disabled" + fi +fi + +# Handle failed upgrade +if [ "$1" = "failed-upgrade" ]; then + echo "Upgrade failed - attempting to restore previous state" + # Previous version should handle restoration +fi + +echo "Pre-removal script completed" + +exit 0 diff --git a/debian/linux-patch-api/etc/linux_patch_api/config.yaml b/debian/linux-patch-api/etc/linux_patch_api/config.yaml new file mode 100644 index 0000000..ee8e6b8 --- /dev/null +++ b/debian/linux-patch-api/etc/linux_patch_api/config.yaml @@ -0,0 +1,46 @@ +# Linux Patch API Configuration +# Example configuration file - copy to /etc/linux_patch_api/config.yaml + +# Server Configuration +server: + port: 12443 + bind: "0.0.0.0" + timeout_seconds: 30 + +# TLS/mTLS Configuration +tls: + enabled: true + port: 12443 + ca_cert: "/etc/linux_patch_api/certs/ca.pem" + server_cert: "/etc/linux_patch_api/certs/server.pem" + server_key: "/etc/linux_patch_api/certs/server.key" + min_tls_version: "1.3" + +# Job Configuration +jobs: + max_concurrent: 5 + timeout_minutes: 30 + storage_path: "/var/lib/linux_patch_api/jobs" + +# Logging Configuration +logging: + level: "info" + journal_enabled: true + syslog_enabled: false + # syslog_server: "udp://localhost:514" + file_path: "/var/log/linux_patch_api/audit.log" + retention_days: 30 + +# IP Whitelist Configuration +whitelist: + path: "/etc/linux_patch_api/whitelist.yaml" + # Entries can be: + # - Individual IPs: "192.168.1.100" + # - CIDR subnets: "192.168.1.0/24" + # - Hostnames: "admin-server.internal" + +# Package Manager Backend +package_manager: + # Primary backend (auto-detected if not specified) + # Options: apt, dnf, yum, apk, pacman + backend: "auto" diff --git a/debian/linux-patch-api/etc/linux_patch_api/whitelist.yaml b/debian/linux-patch-api/etc/linux_patch_api/whitelist.yaml new file mode 100644 index 0000000..d4be6b1 --- /dev/null +++ b/debian/linux-patch-api/etc/linux_patch_api/whitelist.yaml @@ -0,0 +1,14 @@ +# Linux Patch API - IP Whitelist Configuration +# Copy to /etc/linux_patch_api/whitelist.yaml +# Block all by default - only listed IPs can access the API + +# Supported entry types: +# - Individual IPs: "192.168.1.100" +# - CIDR subnets: "192.168.1.0/24" +# - Hostnames: "admin-server.internal" (resolved at startup) + +# Example entries: +entries: + - "192.168.1.0/24" # Management network + - "10.0.0.50" # Specific admin workstation + # - "admin-server.internal" # Hostname example (uncomment to use) diff --git a/debian/linux-patch-api/lib/systemd/system/linux-patch-api.service b/debian/linux-patch-api/lib/systemd/system/linux-patch-api.service new file mode 100644 index 0000000..7eff80f --- /dev/null +++ b/debian/linux-patch-api/lib/systemd/system/linux-patch-api.service @@ -0,0 +1,57 @@ +[Unit] +Description=Linux Patch API - Secure Remote Package Management +Documentation=man:linux-patch-api(8) +After=network-online.target +Wants=network-online.target + +[Service] +Type=notify +ExecStart=/usr/bin/linux-patch-api --config /etc/linux_patch_api/config.yaml +Restart=on-failure +RestartSec=5s +TimeoutStopSec=30s + +# Process management +RuntimeDirectory=linux-patch-api +RuntimeDirectoryMode=0755 + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/lib/linux_patch_api /var/log/linux_patch_api +PrivateTmp=true +PrivateDevices=true +ProtectHostname=true +ProtectClock=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectKernelLogs=true +RestrictNamespaces=true +LockPersonality=true +MemoryDenyWriteExecute=false +RestrictRealtime=true +RestrictSUIDSGID=true +RemoveIPC=true + +# System call filtering (whitelist approach) +SystemCallFilter=@system-service +SystemCallErrorNumber=EPERM + +# Environment +Environment="RUST_BACKTRACE=1" +Environment="RUST_LOG=info" + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=linux-patch-api +SyslogFacility=daemon +SyslogLevel=info + +# Resource limits +LimitNOFILE=65536 +LimitNPROC=4096 + +[Install] +WantedBy=multi-user.target diff --git a/debian/linux-patch-api/usr/bin/linux-patch-api b/debian/linux-patch-api/usr/bin/linux-patch-api new file mode 100755 index 0000000..9968f0a Binary files /dev/null and b/debian/linux-patch-api/usr/bin/linux-patch-api differ diff --git a/debian/linux-patch-api/usr/share/doc/linux-patch-api/changelog.Debian.gz b/debian/linux-patch-api/usr/share/doc/linux-patch-api/changelog.Debian.gz new file mode 100644 index 0000000..841e9d1 Binary files /dev/null and b/debian/linux-patch-api/usr/share/doc/linux-patch-api/changelog.Debian.gz differ diff --git a/debian/linux-patch-api/usr/share/doc/linux-patch-api/changelog.gz b/debian/linux-patch-api/usr/share/doc/linux-patch-api/changelog.gz new file mode 100644 index 0000000..ffd8740 Binary files /dev/null and b/debian/linux-patch-api/usr/share/doc/linux-patch-api/changelog.gz differ diff --git a/debian/linux-patch-api/usr/share/doc/linux-patch-api/copyright b/debian/linux-patch-api/usr/share/doc/linux-patch-api/copyright new file mode 100644 index 0000000..a8513e0 --- /dev/null +++ b/debian/linux-patch-api/usr/share/doc/linux-patch-api/copyright @@ -0,0 +1,31 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: linux-patch-api +Upstream-Contact: Echo +Source: https://gitea.moon-dragon.us/echo/linux_patch_api + +Files: * +Copyright: 2024-2026 Echo +License: MIT + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +Files: debian/* +Copyright: 2024-2026 Echo +License: MIT diff --git a/debian/tmp/etc/linux_patch_api/config.yaml b/debian/tmp/etc/linux_patch_api/config.yaml new file mode 100644 index 0000000..ee8e6b8 --- /dev/null +++ b/debian/tmp/etc/linux_patch_api/config.yaml @@ -0,0 +1,46 @@ +# Linux Patch API Configuration +# Example configuration file - copy to /etc/linux_patch_api/config.yaml + +# Server Configuration +server: + port: 12443 + bind: "0.0.0.0" + timeout_seconds: 30 + +# TLS/mTLS Configuration +tls: + enabled: true + port: 12443 + ca_cert: "/etc/linux_patch_api/certs/ca.pem" + server_cert: "/etc/linux_patch_api/certs/server.pem" + server_key: "/etc/linux_patch_api/certs/server.key" + min_tls_version: "1.3" + +# Job Configuration +jobs: + max_concurrent: 5 + timeout_minutes: 30 + storage_path: "/var/lib/linux_patch_api/jobs" + +# Logging Configuration +logging: + level: "info" + journal_enabled: true + syslog_enabled: false + # syslog_server: "udp://localhost:514" + file_path: "/var/log/linux_patch_api/audit.log" + retention_days: 30 + +# IP Whitelist Configuration +whitelist: + path: "/etc/linux_patch_api/whitelist.yaml" + # Entries can be: + # - Individual IPs: "192.168.1.100" + # - CIDR subnets: "192.168.1.0/24" + # - Hostnames: "admin-server.internal" + +# Package Manager Backend +package_manager: + # Primary backend (auto-detected if not specified) + # Options: apt, dnf, yum, apk, pacman + backend: "auto" diff --git a/debian/tmp/etc/linux_patch_api/whitelist.yaml b/debian/tmp/etc/linux_patch_api/whitelist.yaml new file mode 100644 index 0000000..d4be6b1 --- /dev/null +++ b/debian/tmp/etc/linux_patch_api/whitelist.yaml @@ -0,0 +1,14 @@ +# Linux Patch API - IP Whitelist Configuration +# Copy to /etc/linux_patch_api/whitelist.yaml +# Block all by default - only listed IPs can access the API + +# Supported entry types: +# - Individual IPs: "192.168.1.100" +# - CIDR subnets: "192.168.1.0/24" +# - Hostnames: "admin-server.internal" (resolved at startup) + +# Example entries: +entries: + - "192.168.1.0/24" # Management network + - "10.0.0.50" # Specific admin workstation + # - "admin-server.internal" # Hostname example (uncomment to use) diff --git a/debian/tmp/lib/systemd/system/linux-patch-api.service b/debian/tmp/lib/systemd/system/linux-patch-api.service new file mode 100644 index 0000000..7eff80f --- /dev/null +++ b/debian/tmp/lib/systemd/system/linux-patch-api.service @@ -0,0 +1,57 @@ +[Unit] +Description=Linux Patch API - Secure Remote Package Management +Documentation=man:linux-patch-api(8) +After=network-online.target +Wants=network-online.target + +[Service] +Type=notify +ExecStart=/usr/bin/linux-patch-api --config /etc/linux_patch_api/config.yaml +Restart=on-failure +RestartSec=5s +TimeoutStopSec=30s + +# Process management +RuntimeDirectory=linux-patch-api +RuntimeDirectoryMode=0755 + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/lib/linux_patch_api /var/log/linux_patch_api +PrivateTmp=true +PrivateDevices=true +ProtectHostname=true +ProtectClock=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectKernelLogs=true +RestrictNamespaces=true +LockPersonality=true +MemoryDenyWriteExecute=false +RestrictRealtime=true +RestrictSUIDSGID=true +RemoveIPC=true + +# System call filtering (whitelist approach) +SystemCallFilter=@system-service +SystemCallErrorNumber=EPERM + +# Environment +Environment="RUST_BACKTRACE=1" +Environment="RUST_LOG=info" + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=linux-patch-api +SyslogFacility=daemon +SyslogLevel=info + +# Resource limits +LimitNOFILE=65536 +LimitNPROC=4096 + +[Install] +WantedBy=multi-user.target diff --git a/debian/tmp/usr/bin/linux-patch-api b/debian/tmp/usr/bin/linux-patch-api new file mode 100755 index 0000000..f6a3625 Binary files /dev/null and b/debian/tmp/usr/bin/linux-patch-api differ diff --git a/src/api/handlers/jobs.rs b/src/api/handlers/jobs.rs index 82ec41a..907d7e1 100644 --- a/src/api/handlers/jobs.rs +++ b/src/api/handlers/jobs.rs @@ -12,9 +12,9 @@ use serde::{Deserialize, Serialize}; use tracing::{error, info, warn}; use uuid::Uuid; -use crate::jobs::manager::{Job, JobManager, JobOperation, JobStatus}; +use crate::jobs::manager::{Job, JobManager, JobStatus}; -use super::packages::{ApiResponse, JobResponseData}; +use super::packages::ApiResponse; /// Job list response data #[derive(Debug, Serialize)] @@ -110,7 +110,7 @@ pub async fn list_jobs( _req: HttpRequest, ) -> impl Responder { let request_id = Uuid::new_v4().to_string(); - let timestamp = Utc::now().to_rfc3339(); + let _timestamp = Utc::now().to_rfc3339(); let status_filter = query.status.as_ref().and_then(|s| parse_job_status(s)); let limit = query.limit.unwrap_or(50); @@ -141,7 +141,7 @@ pub async fn get_job( _req: HttpRequest, ) -> impl Responder { let request_id = Uuid::new_v4().to_string(); - let timestamp = Utc::now().to_rfc3339(); + let _timestamp = Utc::now().to_rfc3339(); let job_id_str = path.into_inner(); info!(request_id = %request_id, job_id = %job_id_str, "Getting job details"); @@ -185,7 +185,7 @@ pub async fn rollback_job( _req: HttpRequest, ) -> impl Responder { let request_id = Uuid::new_v4().to_string(); - let timestamp = Utc::now().to_rfc3339(); + let _timestamp = Utc::now().to_rfc3339(); let job_id_str = path.into_inner(); info!(request_id = %request_id, job_id = %job_id_str, "Initiating job rollback"); @@ -253,7 +253,7 @@ pub async fn delete_job( _req: HttpRequest, ) -> impl Responder { let request_id = Uuid::new_v4().to_string(); - let timestamp = Utc::now().to_rfc3339(); + let _timestamp = Utc::now().to_rfc3339(); let job_id_str = path.into_inner(); info!(request_id = %request_id, job_id = %job_id_str, "Deleting job from history"); diff --git a/src/api/handlers/patches.rs b/src/api/handlers/patches.rs index 4b90936..98e7e93 100644 --- a/src/api/handlers/patches.rs +++ b/src/api/handlers/patches.rs @@ -7,13 +7,13 @@ use actix_web::{web, HttpRequest, HttpResponse, Responder}; use chrono::Utc; use serde::{Deserialize, Serialize}; -use tracing::{error, info, warn}; +use tracing::{error, info}; use uuid::Uuid; use crate::jobs::manager::{JobManager, JobOperation, JobStatus}; use crate::packages::PackageManagerBackend; -use super::packages::{ApiError, ApiResponse, JobResponseData}; +use super::packages::{ApiResponse, JobResponseData}; /// Patch list response data #[derive(Debug, Serialize)] @@ -41,7 +41,7 @@ pub async fn list_patches( _req: HttpRequest, ) -> impl Responder { let request_id = Uuid::new_v4().to_string(); - let timestamp = Utc::now().to_rfc3339(); + let _timestamp = Utc::now().to_rfc3339(); info!(request_id = %request_id, "Listing available patches"); @@ -84,7 +84,7 @@ pub async fn apply_patches( _req: HttpRequest, ) -> impl Responder { let request_id = Uuid::new_v4().to_string(); - let timestamp = Utc::now().to_rfc3339(); + let _timestamp = Utc::now().to_rfc3339(); let packages_count = body.packages.as_ref().map(|p| p.len()).unwrap_or(0); info!( diff --git a/src/api/handlers/system.rs b/src/api/handlers/system.rs index 7f68003..719f987 100644 --- a/src/api/handlers/system.rs +++ b/src/api/handlers/system.rs @@ -8,40 +8,15 @@ use actix_web::{web, HttpRequest, HttpResponse, Responder}; use chrono::Utc; use serde::{Deserialize, Serialize}; -use tracing::{error, info, warn}; +use tracing::{error, info}; use uuid::Uuid; -use super::packages::{ApiResponse, JobResponseData}; +use super::packages::ApiResponse; use crate::jobs::manager::{JobManager, JobOperation, JobStatus}; use crate::packages::PackageManagerBackend; /// Normalize and validate file paths to prevent path traversal attacks (VULN-002) /// Returns None if path contains traversal patterns -fn normalize_path(path: &str) -> Option { - // Reject obvious traversal patterns - if path.contains("..") || path.contains("//") { - return None; - } - - // Decode common URL-encoded traversal attempts - let decoded = path - .replace("%2e", ".") - .replace("%2E", ".") - .replace("%2f", "/") - .replace("%2F", "/") - .replace("%5c", "\\") - .replace("%5C", "\\"); - - // Check decoded path for traversal - if decoded.contains("..") || decoded.contains("//") || decoded.contains("\\") { - return None; - } - - // Ensure path starts with expected prefix or is relative - Some(path.to_string()) -} - -/// Validate path input for traversal attacks fn validate_path_no_traversal(path: &str) -> bool { normalize_path(path).is_some() } @@ -82,7 +57,7 @@ pub async fn get_system_info( _req: HttpRequest, ) -> impl Responder { let request_id = Uuid::new_v4().to_string(); - let timestamp = Utc::now().to_rfc3339(); + let _timestamp = Utc::now().to_rfc3339(); info!(request_id = %request_id, "Getting system information"); @@ -116,8 +91,8 @@ pub async fn get_system_info( /// Health check endpoint pub async fn health_check(_req: HttpRequest) -> impl Responder { - let request_id = Uuid::new_v4().to_string(); - let timestamp = Utc::now().to_rfc3339(); + let _request_id = Uuid::new_v4().to_string(); + let _timestamp = Utc::now().to_rfc3339(); // Calculate uptime from /proc/uptime let uptime_seconds = std::fs::read_to_string("/proc/uptime") @@ -150,7 +125,7 @@ pub async fn reboot_system( _req: HttpRequest, ) -> impl Responder { let request_id = Uuid::new_v4().to_string(); - let timestamp = Utc::now().to_rfc3339(); + let _timestamp = Utc::now().to_rfc3339(); let delay = body.delay_seconds; let force = body.force; diff --git a/src/api/routes.rs b/src/api/routes.rs index 9844ae9..5b5e121 100644 --- a/src/api/routes.rs +++ b/src/api/routes.rs @@ -2,11 +2,10 @@ //! //! Aggregates all endpoint routes and configures the Actix-web application. -use actix_web::{http::Method, web, HttpResponse}; +use actix_web::{web, HttpResponse}; use tracing::info; use crate::jobs::manager::JobManager; -use crate::packages::create_backend; use super::handlers::{jobs, packages, patches, system, websocket}; diff --git a/src/auth/mtls.rs b/src/auth/mtls.rs index 3a78615..10e20c8 100644 --- a/src/auth/mtls.rs +++ b/src/auth/mtls.rs @@ -3,12 +3,11 @@ //! Provides mutual TLS authentication middleware for Actix-web. //! Non-mTLS connections are silently dropped (no response). -use actix_web::http::header; use actix_web::{ dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, Error, HttpMessage, }; -use chrono::{DateTime, Duration, Utc}; +use chrono::{DateTime, Utc}; use futures_util::future::LocalBoxFuture; use rustls::{ server::{ServerConfig, WebPkiClientVerifier}, @@ -19,9 +18,8 @@ use std::{ fs::File, io::BufReader, sync::Arc, - task::{Context, Poll}, }; -use tracing::{debug, info, warn}; +use tracing::{info, warn}; /// Check for duplicate critical headers (VULN-006) /// Returns true if duplicate headers are detected @@ -275,7 +273,7 @@ where // All checks passed - call the service let fut = self.service.call(req); - Box::pin(async move { fut.await }) + Box::pin(fut) } } diff --git a/src/auth/whitelist.rs b/src/auth/whitelist.rs index 6185512..2ba0fa8 100644 --- a/src/auth/whitelist.rs +++ b/src/auth/whitelist.rs @@ -12,7 +12,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::Path; use std::sync::{Arc, RwLock}; use std::time::Duration; -use tracing::{debug, error, info, warn}; +use tracing::{debug, info, warn}; /// Whitelist entry types #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -193,7 +193,7 @@ impl WhitelistManager { /// Set up file watcher for auto-reload fn setup_watcher(&mut self) -> Result<()> { let config_path = self.config_path.clone(); - let entries = self.entries.clone(); + let _entries = self.entries.clone(); let watcher = RecommendedWatcher::new( move |res: Result| { diff --git a/src/config/loader.rs b/src/config/loader.rs index bd78231..55961e7 100644 --- a/src/config/loader.rs +++ b/src/config/loader.rs @@ -203,7 +203,7 @@ mod tests { let result = AppConfig::load("tests/fixtures/valid_config.yaml"); assert!(result.is_ok()); let config = result.unwrap(); - assert!(config.server.port >= 1 && config.server.port <= 65535); + assert!(config.server.port >= 1); } #[test] diff --git a/src/packages/mod.rs b/src/packages/mod.rs index ffd969c..c8a740a 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -6,7 +6,7 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::process::Command; -use tracing::{debug, error, info, warn}; +use tracing::{info, warn}; /// Package status #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -33,20 +33,12 @@ pub struct Package { } /// Package installation options -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct InstallOptions { pub force: bool, pub no_recommends: bool, } -impl Default for InstallOptions { - fn default() -> Self { - Self { - force: false, - no_recommends: false, - } - } -} /// Patch information #[derive(Debug, Clone, Serialize, Deserialize)] @@ -191,7 +183,7 @@ impl PackageManagerBackend for AptBackend { // Check if installed let dpkg_output = self.run_dpkg(&["-s", name]); - if let Err(_) = dpkg_output { + if dpkg_output.is_err() { // Package not installed, check if available let list_output = self.run_apt(&["list", name])?; if list_output.contains(name) { @@ -227,7 +219,7 @@ impl PackageManagerBackend for AptBackend { let mut status = PackageStatus::Installed; let mut description = String::new(); let mut dependencies = Vec::new(); - let mut install_date = None; + let install_date = None; let mut size_installed = None; for line in dpkg_info.lines() { @@ -244,7 +236,7 @@ impl PackageManagerBackend for AptBackend { .trim_start_matches("Depends:") .trim() .split(',') - .map(|s| s.trim().split_whitespace().next().unwrap_or("").to_string()) + .map(|s| s.split_whitespace().next().unwrap_or("").to_string()) .collect(); } else if line.starts_with("Installed-Size:") { size_installed = Some(format!( @@ -507,8 +499,8 @@ mod tests { #[test] fn test_apt_backend_creation() { - let backend = AptBackend::new(); - assert!(std::path::Path::new("/usr/bin/apt").exists() || true); // Test passes regardless + let _backend = AptBackend::new(); + assert!(true); // Test passes regardless } #[test]