Initial commit: VaultMesh Skills collection

Collection of operational skills for VaultMesh infrastructure including:
- backup-sovereign: Backup and recovery operations
- btc-anchor: Bitcoin anchoring
- cloudflare-tunnel-manager: Cloudflare tunnel management
- container-registry: Container registry operations
- disaster-recovery: Disaster recovery procedures
- dns-sovereign: DNS management
- eth-anchor: Ethereum anchoring
- gitea-bootstrap: Gitea setup and configuration
- hetzner-bootstrap: Hetzner server provisioning
- merkle-forest: Merkle tree operations
- node-hardening: Node security hardening
- operator-bootstrap: Operator initialization
- proof-verifier: Cryptographic proof verification
- rfc3161-anchor: RFC3161 timestamping
- secrets-vault: Secrets management

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Vault Sovereign
2025-12-27 00:25:00 +00:00
commit eac77ef7b4
213 changed files with 11724 additions and 0 deletions

170
node-hardening/SKILL.md Normal file
View File

@@ -0,0 +1,170 @@
---
name: node-hardening
description: >
Harden a Linux node for sovereign EU infrastructure without losing remote access.
Implements UFW firewall, SSH hardening, fail2ban, and auditd with two-phase
plan/apply workflow and DRY_RUN safety gates. Use when securing Node A after
operator-bootstrap completes. Triggers: 'harden node', 'secure server',
'configure firewall', 'harden SSH', 'set up fail2ban', 'enable auditd',
'lock down node', 'security hardening'.
version: 1.0.0
---
# Node Hardening
High-risk Tier 1 skill for securing Linux nodes. All risky operations require explicit DRY_RUN=0 and confirmation phrase. Designed for full Linux servers (Ubuntu/Debian) with console/IPMI/VNC fallback access.
## Quick Start
```bash
# Set required parameters (none required, but review defaults)
export NODE_NAME="node-a"
export SSH_PORT=22
# Run preflight check
./scripts/00_preflight.sh
# Plan phases (safe to run, shows what WILL happen)
./scripts/10_ufw_plan.sh
./scripts/20_ssh_plan.sh
# Apply phases (REQUIRES DRY_RUN=0 and confirmation)
export DRY_RUN=0
./scripts/11_ufw_apply.sh # Type confirmation phrase
./scripts/21_ssh_apply.sh # Type confirmation phrase
# Optional: fail2ban and auditd
./scripts/30_fail2ban_setup.sh
./scripts/40_auditd_setup.sh
# Verify and report
./scripts/90_verify.sh
./scripts/99_report.sh
```
## Workflow
### Phase 0: Preflight (00)
Check dependencies: sudo, systemctl, ufw, sshd, fail2ban, auditd.
Detect SSH session and warn about keeping backup session open.
### Phase 1: UFW Firewall (10-11)
**Two-phase operation with DRY_RUN gate.**
Plan phase shows:
- Default deny incoming, allow outgoing
- SSH port allowance (rate-limited if possible)
- HTTP/HTTPS ports if enabled
- Current client IP auto-whitelisted
Apply phase executes:
- Backs up current iptables state
- Resets and configures UFW
- Enables firewall
Rollback: `./scripts/rollback/undo_ufw.sh`
### Phase 2: SSH Hardening (20-21)
**Two-phase operation with DRY_RUN gate and CONFIRM_PHRASE.**
Plan phase shows:
- Proposed sshd_config changes
- PermitRootLogin, PasswordAuthentication settings
- Cipher and MAC selections
Apply phase executes:
- Backs up /etc/ssh/sshd_config
- Renders hardened config from template
- Validates with `sshd -t` before applying
- Uses `reload` (not restart) to keep current session alive
- **Auto-restores on validation failure**
Rollback: `./scripts/rollback/undo_ssh.sh`
### Phase 3: fail2ban (30)
Optional intrusion detection.
- SSH jail configuration
- UFW integration
- Operator IP whitelisting
### Phase 4: auditd (40)
Optional audit logging.
- Monitor security-relevant files
- Kernel module loading
- User/group modifications
### Phase 5: Verification (90-99)
Generate JSON status matrix and markdown audit report.
## Inputs
| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| NODE_NAME | No | node-a | Hostname for this node |
| SSH_PORT | No | 22 | SSH port number |
| ALLOW_HTTP | No | true | Allow port 80 in UFW |
| ALLOW_HTTPS | No | true | Allow port 443 in UFW |
| ALLOW_ICMP | No | false | Allow ICMP (ping) |
| DRY_RUN | No | 1 | Set to 0 to enable apply scripts |
| REQUIRE_CONFIRM | No | 1 | Require confirmation phrase |
| CONFIRM_PHRASE | No | I UNDERSTAND THIS CAN LOCK ME OUT | Safety phrase |
| BACKUP_DIR | No | outputs/backups | Backup location |
| FAIL2BAN_ENABLE | No | true | Enable fail2ban setup |
| AUDITD_ENABLE | No | true | Enable auditd setup |
## Outputs
| File | Description |
|------|-------------|
| `outputs/backups/ufw_status_before.txt` | Pre-change UFW state |
| `outputs/backups/iptables_rules_before.txt` | Pre-change iptables |
| `outputs/backups/sshd_config.before` | Pre-change SSH config |
| `outputs/ufw_status_after.txt` | Post-change UFW state |
| `outputs/status_matrix.json` | Verification results |
| `outputs/audit_report.md` | Human-readable audit trail |
## Safety Guarantees
1. **DRY_RUN=1 by default** - Apply scripts refuse to run without explicit DRY_RUN=0
2. **CONFIRM_PHRASE required** - Must type exact phrase to proceed
3. **SSH reload (not restart)** - Keeps current session alive
4. **sshd -t validation** - Config validated before applying
5. **Auto-restore on failure** - Invalid config automatically reverted
6. **Backups before every change** - Full state preserved
7. **Emergency restore script** - Console-safe full recovery
8. **All scripts idempotent** - Safe to run multiple times
## Emergency Recovery
If you lose SSH access:
1. Access console via IPMI/VNC/physical
2. Run: `./scripts/rollback/emergency_restore.sh`
This will:
- Disable UFW
- Restore original sshd_config from backup
- Restart SSH service
## EU Compliance
| Aspect | Value |
|--------|-------|
| Data Residency | EU (Ireland - Dublin) |
| GDPR Applicable | Yes |
| Jurisdiction | Irish Law |
| Audit Logging | auditd (local only) |
## References
- [Recovery Procedures](references/recovery_procedures.md)
- [CIS Benchmarks](references/cis_benchmarks.md)
- [SSH Cipher Recommendations](references/ssh_cipher_recommendations.md)
## Next Steps
After completing node-hardening:
1. Verify SSH access from secondary session
2. Test rollback procedure (optional but recommended)
3. Proceed to **backup-sovereign** skill
4. Document hardening in LAWCHAIN (if applicable)

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
if ! command -v auditctl &>/dev/null; then
echo "auditd: missing"
exit 1
fi
if systemctl is-active --quiet auditd; then
echo "auditd: active"
exit 0
fi
echo "auditd: inactive"
exit 1

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
if ! command -v fail2ban-client &>/dev/null; then
echo "fail2ban: missing"
exit 1
fi
if systemctl is-active --quiet fail2ban; then
echo "fail2ban: active"
exit 0
fi
echo "fail2ban: inactive"
exit 1

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ ! -f /etc/ssh/sshd_config ]]; then
echo "sshd_config: missing"
exit 1
fi
if grep -Eq '^PasswordAuthentication\s+no' /etc/ssh/sshd_config; then
echo "ssh: password auth disabled"
else
echo "ssh: password auth not disabled"
exit 1
fi
if grep -Eq '^PermitRootLogin\s+no' /etc/ssh/sshd_config; then
echo "ssh: root login disabled"
else
echo "ssh: root login not disabled"
exit 1
fi
exit 0

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
if ! command -v ufw &>/dev/null; then
echo "ufw: missing"
exit 1
fi
if ufw status | grep -qi "Status: active"; then
echo "ufw: active"
exit 0
fi
echo "ufw: inactive"
exit 1

View File

@@ -0,0 +1,54 @@
{
"version": "1.0.0",
"skill": "node-hardening",
"description": "Safe-by-default hardening: UFW + SSH + fail2ban + auditd",
"parameters": {
"required": [],
"optional": {
"NODE_NAME": "node-a",
"SSH_PORT": 22,
"ALLOW_HTTP": true,
"ALLOW_HTTPS": true,
"ALLOW_ICMP": false,
"DRY_RUN": 1,
"REQUIRE_CONFIRM": 1,
"CONFIRM_PHRASE": "I UNDERSTAND THIS CAN LOCK ME OUT",
"BACKUP_DIR": "outputs/backups",
"FAIL2BAN_ENABLE": true,
"AUDITD_ENABLE": true
}
},
"phases": {
"preflight": ["00_preflight.sh"],
"ufw": {
"plan": ["10_ufw_plan.sh"],
"apply": ["11_ufw_apply.sh"],
"rollback": ["rollback/undo_ufw.sh"]
},
"ssh": {
"plan": ["20_ssh_plan.sh"],
"apply": ["21_ssh_apply.sh"],
"rollback": ["rollback/undo_ssh.sh", "rollback/emergency_restore.sh"]
},
"fail2ban": ["30_fail2ban_setup.sh"],
"auditd": ["40_auditd_setup.sh"],
"verify": ["90_verify.sh"],
"report": ["99_report.sh"]
},
"checks": {
"ufw": ["check_ufw.sh"],
"ssh": ["check_ssh.sh"],
"fail2ban": ["check_fail2ban.sh"],
"auditd": ["check_auditd.sh"]
},
"rollback_order": [
"emergency_restore.sh",
"undo_ssh.sh",
"undo_ufw.sh"
],
"eu_compliance": {
"data_residency": "EU",
"jurisdiction": "Ireland",
"gdpr_applicable": true
}
}

View File

@@ -0,0 +1,118 @@
# CIS Benchmarks Reference
## Overview
This skill implements controls aligned with CIS (Center for Internet Security) Benchmarks for Linux. The following sections map skill operations to specific CIS controls.
## CIS Ubuntu/Debian Linux Benchmark Mappings
### 1. Initial Setup
| CIS Control | Description | Skill Implementation |
|-------------|-------------|----------------------|
| 1.1.1.x | Disable unused filesystems | Out of scope |
| 1.5.x | Secure boot settings | Out of scope |
### 2. Services
| CIS Control | Description | Skill Implementation |
|-------------|-------------|----------------------|
| 2.1.x | Disable inetd services | Out of scope |
| 2.2.x | Special purpose services | fail2ban, auditd enabled |
### 3. Network Configuration
| CIS Control | Description | Skill Implementation |
|-------------|-------------|----------------------|
| 3.1.1 | Disable IPv6 | Not disabled (optional) |
| 3.2.x | Network parameters (host) | Handled by sysctl (future) |
| 3.4.x | Firewall configuration | **UFW enabled** |
### 4. Logging and Auditing
| CIS Control | Description | Skill Implementation |
|-------------|-------------|----------------------|
| 4.1.1 | Ensure auditing is enabled | **auditd installed** |
| 4.1.2 | Configure audit log storage | Default settings |
| 4.1.x | Audit rules | Basic rules via template |
### 5. Access, Authentication, and Authorization
| CIS Control | Description | Skill Implementation |
|-------------|-------------|----------------------|
| 5.2.1 | Ensure sshd is running | Verified in preflight |
| 5.2.2 | SSH Protocol version | Implicit (OpenSSH 7.4+) |
| 5.2.3 | SSH LogLevel | **Set to VERBOSE** |
| 5.2.4 | SSH X11Forwarding | **Disabled** |
| 5.2.5 | SSH MaxAuthTries | **Set to 3** |
| 5.2.6 | SSH IgnoreRhosts | **Set to yes** |
| 5.2.7 | SSH HostbasedAuth | **Disabled** |
| 5.2.8 | SSH PermitRootLogin | **Disabled** |
| 5.2.9 | SSH PermitEmptyPasswords | **Disabled** |
| 5.2.10 | SSH PermitUserEnvironment | **Disabled** |
| 5.2.11 | SSH strong ciphers | **Configured** |
| 5.2.12 | SSH strong MACs | **Configured** |
| 5.2.13 | SSH strong KEX | **Configured** |
| 5.2.14 | SSH Idle Timeout | **Set (ClientAliveInterval)** |
| 5.2.15 | SSH LoginGraceTime | **Set to 20** |
| 5.2.16 | SSH access restriction | Via AllowUsers (optional) |
### 6. System Maintenance
| CIS Control | Description | Skill Implementation |
|-------------|-------------|----------------------|
| 6.1.x | System file permissions | Out of scope |
| 6.2.x | User and group settings | Out of scope |
## SSH Hardening Details
The sshd_config template implements:
```
# CIS 5.2.4
X11Forwarding no
# CIS 5.2.5
MaxAuthTries 3
# CIS 5.2.6
IgnoreRhosts yes
# CIS 5.2.7
HostbasedAuthentication no
# CIS 5.2.8
PermitRootLogin no
# CIS 5.2.9
PermitEmptyPasswords no
# CIS 5.2.10
PermitUserEnvironment no
# CIS 5.2.11-13 - Strong crypto
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
# CIS 5.2.14
ClientAliveInterval 300
ClientAliveCountMax 2
# CIS 5.2.15
LoginGraceTime 20
```
## Firewall Rules
Default UFW policy:
- Default deny incoming
- Default allow outgoing
- SSH port allowed (rate-limited if configured)
- HTTP/HTTPS optional
## References
- [CIS Benchmarks](https://www.cisecurity.org/cis-benchmarks/)
- [CIS Ubuntu Linux Benchmark](https://www.cisecurity.org/benchmark/ubuntu_linux)
- [CIS Debian Linux Benchmark](https://www.cisecurity.org/benchmark/debian_linux)

View File

@@ -0,0 +1,123 @@
# Recovery Procedures
## Overview
This document describes recovery procedures for when node-hardening changes cause loss of remote access or system instability.
## Prerequisites
- Console access via IPMI, VNC, or physical connection
- Knowledge of backup file locations
- Root or sudo access
## Scenario 1: SSH Access Lost
### Symptoms
- Cannot SSH to the server
- Connection refused or timeout
### Recovery Steps
1. **Access console** (IPMI/VNC/physical)
2. **Run emergency restore**:
```bash
cd ~/.claude/skills/node-hardening
./scripts/rollback/emergency_restore.sh
```
3. **If emergency_restore fails**, manually restore:
```bash
# Disable UFW
sudo ufw --force disable
# Restore SSH config
sudo cp /path/to/outputs/backups/sshd_config.before /etc/ssh/sshd_config
# Restart SSH
sudo systemctl restart ssh
# or
sudo systemctl restart sshd
```
4. **Verify from another terminal**:
```bash
ssh user@server
```
## Scenario 2: Firewall Blocking All Traffic
### Symptoms
- All network services unreachable
- SSH, HTTP, HTTPS all timeout
### Recovery Steps
1. **Access console** (IPMI/VNC/physical)
2. **Disable UFW**:
```bash
sudo ufw --force disable
```
3. **Verify rules**:
```bash
sudo ufw status verbose
```
4. **Restore from backup if available**:
```bash
sudo iptables-restore < /path/to/outputs/backups/iptables_rules_before.txt
```
## Scenario 3: fail2ban Blocking Legitimate Access
### Symptoms
- SSH works from some IPs but not others
- Intermittent connection failures
### Recovery Steps
1. **Check banned IPs**:
```bash
sudo fail2ban-client status sshd
```
2. **Unban IP**:
```bash
sudo fail2ban-client set sshd unbanip <IP_ADDRESS>
```
3. **Whitelist operator IP** in `/etc/fail2ban/jail.local`:
```ini
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 <OPERATOR_IP>
```
4. **Restart fail2ban**:
```bash
sudo systemctl restart fail2ban
```
## Backup Locations
| File | Description |
|------|-------------|
| `outputs/backups/sshd_config.before` | Original SSH configuration |
| `outputs/backups/ufw_status_before.txt` | UFW state before changes |
| `outputs/backups/iptables_rules_before.txt` | iptables rules before changes |
## Prevention
1. **Always keep a secondary SSH session open** during changes
2. **Test from a different network** before closing sessions
3. **Have console access ready** before running apply scripts
4. **Review plan output** before running apply
## Contact
If recovery procedures fail, escalate to infrastructure team with:
- Node name and IP
- Time of last successful access
- Changes that were applied
- Error messages from recovery attempts

View File

@@ -0,0 +1,115 @@
# SSH Cipher Recommendations
## Overview
This document explains the SSH cipher, MAC, and key exchange algorithm choices used in the node-hardening skill's sshd_config template.
## Current Recommendations (2024)
### Ciphers (Encryption)
| Cipher | Recommendation | Notes |
|--------|----------------|-------|
| chacha20-poly1305@openssh.com | **Recommended** | Modern, fast, constant-time |
| aes256-gcm@openssh.com | **Recommended** | Strong, hardware-accelerated |
| aes128-gcm@openssh.com | **Acceptable** | Fast, hardware-accelerated |
| aes256-ctr | Acceptable | Legacy compatibility |
| aes128-ctr | Acceptable | Legacy compatibility |
| 3des-cbc | **Avoid** | Deprecated, slow |
| arcfour | **Avoid** | Broken |
### MACs (Message Authentication)
| MAC | Recommendation | Notes |
|-----|----------------|-------|
| hmac-sha2-512-etm@openssh.com | **Recommended** | Encrypt-then-MAC, strongest |
| hmac-sha2-256-etm@openssh.com | **Recommended** | Encrypt-then-MAC |
| umac-128-etm@openssh.com | Acceptable | Fast, Encrypt-then-MAC |
| hmac-sha2-512 | Acceptable | No ETM |
| hmac-sha2-256 | Acceptable | No ETM |
| hmac-sha1 | **Avoid** | Deprecated |
| hmac-md5 | **Avoid** | Broken |
### Key Exchange (KEX)
| KEX Algorithm | Recommendation | Notes |
|---------------|----------------|-------|
| curve25519-sha256 | **Recommended** | Modern, safe curve |
| curve25519-sha256@libssh.org | **Recommended** | Same, legacy name |
| diffie-hellman-group16-sha512 | Acceptable | 4096-bit DH |
| diffie-hellman-group18-sha512 | Acceptable | 8192-bit DH |
| diffie-hellman-group14-sha256 | Acceptable | 2048-bit DH |
| diffie-hellman-group1-sha1 | **Avoid** | Weak, deprecated |
| diffie-hellman-group-exchange-sha1 | **Avoid** | SHA1 deprecated |
### Host Key Algorithms
| Algorithm | Recommendation | Notes |
|-----------|----------------|-------|
| ssh-ed25519 | **Recommended** | Modern, compact |
| rsa-sha2-512 | **Recommended** | RSA with SHA2 |
| rsa-sha2-256 | **Recommended** | RSA with SHA2 |
| ecdsa-sha2-nistp256 | Acceptable | NIST curve concerns |
| ssh-rsa | **Avoid** | SHA1 deprecated |
| ssh-dss | **Avoid** | Weak |
## Template Configuration
The sshd_config template uses:
```
# Strong ciphers only
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
# Encrypt-then-MAC only
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Modern key exchange
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
# Preferred host key algorithms
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
```
## Compatibility Notes
### Minimum Client Versions
These settings require:
- OpenSSH 7.3+ (released 2016)
- PuTTY 0.68+ (released 2017)
### Legacy Client Support
If you need to support older clients, add fallback options:
```
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-256
KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512
```
## Testing Configuration
After applying changes, test with:
```bash
# Check server offerings
ssh -Q cipher
ssh -Q mac
ssh -Q kex
# Test connection with verbose output
ssh -vvv user@server
# Audit with ssh-audit (recommended)
pip install ssh-audit
ssh-audit localhost
```
## References
- [Mozilla SSH Guidelines](https://infosec.mozilla.org/guidelines/openssh)
- [ssh-audit](https://github.com/jtesta/ssh-audit)
- [Secure Secure Shell](https://stribika.github.io/2015/01/04/secure-secure-shell.html)
- [OpenSSH Manual](https://man.openbsd.org/sshd_config)

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
die() { log_error "$@"; exit 1; }
check_dependency() {
if command -v "$1" &>/dev/null; then
log_info "Found: $1 ($(command -v "$1"))"
return 0
else
log_warn "Missing: $1"
return 1
fi
}
is_ssh_session() {
[[ -n "${SSH_CONNECTION:-}" || -n "${SSH_CLIENT:-}" ]]
}
main() {
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
log_info "Starting $SCRIPT_NAME"
local missing=0
log_info "=== Required Dependencies ==="
check_dependency sudo || ((missing++))
check_dependency systemctl || ((missing++))
check_dependency ss || check_dependency netstat || log_warn "No ss/netstat (network inspection limited)"
check_dependency awk || ((missing++))
check_dependency sed || ((missing++))
check_dependency grep || ((missing++))
log_info "=== Hardening Tooling (may be installed during apply) ==="
check_dependency ufw || log_warn "ufw not installed (apply can install)"
check_dependency sshd || check_dependency ssh || log_warn "sshd binary not found (service may still exist)"
check_dependency fail2ban-client || log_warn "fail2ban not installed (apply can install)"
check_dependency auditctl || log_warn "auditd not installed (apply can install)"
log_info "=== Session Context ==="
if is_ssh_session; then
log_warn "Detected SSH session: ${SSH_CONNECTION:-${SSH_CLIENT:-unknown}}"
log_warn "Recommendation: keep a second session open before applying changes."
else
log_info "No SSH session detected (console/local run)"
fi
log_info "=== Privilege Check ==="
if sudo -n true 2>/dev/null; then
log_info "sudo is available without password prompt (non-interactive)"
else
log_info "sudo may prompt for password (interactive)"
fi
log_info "=== Parameters (defaults if unset) ==="
log_info "SSH_PORT=${SSH_PORT:-22}"
log_info "ALLOW_HTTP=${ALLOW_HTTP:-true}"
log_info "ALLOW_HTTPS=${ALLOW_HTTPS:-true}"
log_info "DRY_RUN=${DRY_RUN:-1} (apply scripts require DRY_RUN=0)"
if [[ $missing -gt 0 ]]; then
die "Missing $missing required dependencies. Install them before proceeding."
fi
log_info "Completed $SCRIPT_NAME"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
detect_ssh_client_ip() {
if [[ -n "${SSH_CLIENT:-}" ]]; then
awk '{print $1}' <<<"$SSH_CLIENT"
elif [[ -n "${SSH_CONNECTION:-}" ]]; then
awk '{print $1}' <<<"$SSH_CONNECTION"
else
echo ""
fi
}
main() {
mkdir -p "$OUTPUT_DIR"
local ssh_port="${SSH_PORT:-22}"
local allow_http="${ALLOW_HTTP:-true}"
local allow_https="${ALLOW_HTTPS:-true}"
local ssh_ip
ssh_ip="$(detect_ssh_client_ip)"
log_info "UFW Plan (no changes applied)"
log_info "Target SSH_PORT=$ssh_port"
[[ -n "$ssh_ip" ]] && log_info "Detected SSH client IP=$ssh_ip" || log_warn "No SSH client IP detected"
echo
echo "--- Intended policy ---"
echo "Default: deny incoming"
echo "Default: allow outgoing"
echo
echo "--- Intended allow rules ---"
echo "1) Allow SSH: $ssh_port/tcp (always)"
if [[ -n "$ssh_ip" ]]; then
echo "2) Pin SSH from current client IP: from $ssh_ip to any port $ssh_port/tcp (optional safety)"
else
echo "2) No client IP detected; IP pinning skipped"
fi
if [[ "$allow_http" == "true" ]]; then
echo "3) Allow HTTP: 80/tcp"
else
echo "3) HTTP not allowed"
fi
if [[ "$allow_https" == "true" ]]; then
echo "4) Allow HTTPS: 443/tcp"
else
echo "4) HTTPS not allowed"
fi
echo
echo "--- Safety notes ---"
echo "- Apply script will refuse unless DRY_RUN=0"
echo "- If you are on SSH, keep a second session open"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
die() { log_error "$@"; exit 1; }
require_confirm() {
local require="${REQUIRE_CONFIRM:-1}"
local phrase="${CONFIRM_PHRASE:-I UNDERSTAND THIS CAN LOCK ME OUT}"
if [[ "$require" != "1" ]]; then
return 0
fi
echo
echo "CONFIRMATION REQUIRED"
echo "Type the phrase exactly to continue:"
echo " $phrase"
read -r input
[[ "$input" == "$phrase" ]] || die "Confirmation phrase mismatch; aborting."
}
detect_ssh_client_ip() {
if [[ -n "${SSH_CLIENT:-}" ]]; then
awk '{print $1}' <<<"$SSH_CLIENT"
elif [[ -n "${SSH_CONNECTION:-}" ]]; then
awk '{print $1}' <<<"$SSH_CONNECTION"
else
echo ""
fi
}
ensure_ufw() {
if command -v ufw &>/dev/null; then
return 0
fi
log_warn "ufw not found; attempting install (Debian/Ubuntu)"
if command -v apt-get &>/dev/null; then
sudo apt-get update -y
sudo apt-get install -y ufw
else
die "ufw not installed and apt-get not available. Install ufw manually."
fi
}
main() {
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
local dry="${DRY_RUN:-1}"
[[ "$dry" == "0" ]] || die "Refusing to apply with DRY_RUN=$dry. Export DRY_RUN=0 to proceed."
require_confirm
ensure_ufw
local ssh_port="${SSH_PORT:-22}"
local allow_http="${ALLOW_HTTP:-true}"
local allow_https="${ALLOW_HTTPS:-true}"
local ssh_ip
ssh_ip="$(detect_ssh_client_ip)"
log_info "Backing up current UFW status"
sudo ufw status verbose >"$BACKUP_DIR/ufw_status_before.txt" || true
sudo iptables -S >"$BACKUP_DIR/iptables_rules_before.txt" || true
log_info "Applying UFW policy (idempotent)"
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Always allow SSH
sudo ufw allow "$ssh_port"/tcp
if [[ -n "$ssh_ip" ]]; then
sudo ufw allow from "$ssh_ip" to any port "$ssh_port" proto tcp
fi
if [[ "$allow_http" == "true" ]]; then
sudo ufw allow 80/tcp
fi
if [[ "$allow_https" == "true" ]]; then
sudo ufw allow 443/tcp
fi
log_info "Enabling UFW"
sudo ufw --force enable
log_info "Writing post-state"
sudo ufw status verbose >"$OUTPUT_DIR/ufw_status_after.txt"
log_info "UFW apply complete"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${TEMPLATE_DIR:=$SKILL_ROOT/templates}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
render_template() {
local tpl="$1"
local port="$2"
sed "s/{{SSH_PORT}}/$port/g" "$tpl"
}
main() {
mkdir -p "$OUTPUT_DIR"
local ssh_port="${SSH_PORT:-22}"
log_info "SSH Hardening Plan (no changes applied)"
log_info "Target SSH_PORT=$ssh_port"
if [[ -f /etc/ssh/sshd_config ]]; then
log_info "Current /etc/ssh/sshd_config exists"
else
log_warn "No /etc/ssh/sshd_config found; this host may use a different path"
fi
echo
echo "--- Proposed sshd_config (rendered from template) ---"
render_template "$TEMPLATE_DIR/sshd_config.tpl" "$ssh_port"
echo
echo "--- Safety notes ---"
echo "- Apply will backup /etc/ssh/sshd_config before writing"
echo "- Apply will run 'sshd -t' (syntax check) before reloading"
echo "- Apply will refuse unless DRY_RUN=0"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
: "${TEMPLATE_DIR:=$SKILL_ROOT/templates}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
die() { log_error "$@"; exit 1; }
require_confirm() {
local require="${REQUIRE_CONFIRM:-1}"
local phrase="${CONFIRM_PHRASE:-I UNDERSTAND THIS CAN LOCK ME OUT}"
if [[ "$require" != "1" ]]; then
return 0
fi
echo
echo "CONFIRMATION REQUIRED"
echo "Type the phrase exactly to continue:"
echo " $phrase"
read -r input
[[ "$input" == "$phrase" ]] || die "Confirmation phrase mismatch; aborting."
}
render_template() {
local port="$1"
sed "s/{{SSH_PORT}}/$port/g" "$TEMPLATE_DIR/sshd_config.tpl"
}
sshd_binary() {
if command -v sshd &>/dev/null; then
echo "sshd"
elif [[ -x /usr/sbin/sshd ]]; then
echo "/usr/sbin/sshd"
else
echo ""
fi
}
reload_service() {
if systemctl list-units --type=service | grep -qE '^sshd\.service'; then
sudo systemctl reload sshd
elif systemctl list-units --type=service | grep -qE '^ssh\.service'; then
sudo systemctl reload ssh
else
die "Could not find ssh/sshd service under systemctl"
fi
}
main() {
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
local dry="${DRY_RUN:-1}"
[[ "$dry" == "0" ]] || die "Refusing to apply with DRY_RUN=$dry. Export DRY_RUN=0 to proceed."
require_confirm
local ssh_port="${SSH_PORT:-22}"
local sshd
sshd="$(sshd_binary)"
[[ -n "$sshd" ]] || die "sshd binary not found"
[[ -f /etc/ssh/sshd_config ]] || die "/etc/ssh/sshd_config not found"
log_info "Backing up current sshd_config"
sudo cp -a /etc/ssh/sshd_config "$BACKUP_DIR/sshd_config.before"
log_info "Writing new sshd_config from template"
render_template "$ssh_port" | sudo tee /etc/ssh/sshd_config >/dev/null
log_info "Validating sshd config (sshd -t)"
sudo "$sshd" -t || {
log_error "sshd config validation failed; restoring backup"
sudo cp -a "$BACKUP_DIR/sshd_config.before" /etc/ssh/sshd_config
die "Aborted due to invalid sshd_config"
}
log_info "Reloading SSH service"
reload_service
log_info "SSH hardening applied. Verify you still have access before closing sessions."
log_info "If you are locked out, use scripts/rollback/emergency_restore.sh from console."
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
: "${TEMPLATE_DIR:=$SKILL_ROOT/templates}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
die() { log_error "$@"; exit 1; }
ensure_fail2ban() {
if command -v fail2ban-client &>/dev/null; then
return 0
fi
log_warn "fail2ban not found; attempting install (Debian/Ubuntu)"
if command -v apt-get &>/dev/null; then
sudo apt-get update -y
sudo apt-get install -y fail2ban
else
die "fail2ban not installed and apt-get not available. Install manually."
fi
}
render_jail() {
local port="$1"
sed "s/{{SSH_PORT}}/$port/g" "$TEMPLATE_DIR/fail2ban_jail.tpl"
}
main() {
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
local enabled="${FAIL2BAN_ENABLE:-true}"
if [[ "$enabled" != "true" && "$enabled" != "1" ]]; then
log_info "FAIL2BAN_ENABLE disabled; skipping"
exit 0
fi
local dry="${DRY_RUN:-1}"
[[ "$dry" == "0" ]] || die "Refusing to apply with DRY_RUN=$dry. Export DRY_RUN=0 to proceed."
ensure_fail2ban
local ssh_port="${SSH_PORT:-22}"
if [[ -f /etc/fail2ban/jail.local ]]; then
sudo cp -a /etc/fail2ban/jail.local "$BACKUP_DIR/jail.local.before"
fi
log_info "Writing /etc/fail2ban/jail.local"
render_jail "$ssh_port" | sudo tee /etc/fail2ban/jail.local >/dev/null
log_info "Enabling and restarting fail2ban"
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
log_info "fail2ban setup complete"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
: "${TEMPLATE_DIR:=$SKILL_ROOT/templates}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
die() { log_error "$@"; exit 1; }
ensure_auditd() {
if command -v auditctl &>/dev/null; then
return 0
fi
log_warn "auditd not found; attempting install (Debian/Ubuntu)"
if command -v apt-get &>/dev/null; then
sudo apt-get update -y
sudo apt-get install -y auditd audispd-plugins
else
die "auditd not installed and apt-get not available. Install manually."
fi
}
main() {
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
local enabled="${AUDITD_ENABLE:-true}"
if [[ "$enabled" != "true" && "$enabled" != "1" ]]; then
log_info "AUDITD_ENABLE disabled; skipping"
exit 0
fi
local dry="${DRY_RUN:-1}"
[[ "$dry" == "0" ]] || die "Refusing to apply with DRY_RUN=$dry. Export DRY_RUN=0 to proceed."
ensure_auditd
sudo systemctl enable auditd
local rules_path="/etc/audit/rules.d/node-hardening.rules"
if [[ -f "$rules_path" ]]; then
sudo cp -a "$rules_path" "$BACKUP_DIR/node-hardening.rules.before"
fi
log_info "Writing $rules_path"
sudo install -m 0640 /dev/null "$rules_path"
sudo cp "$TEMPLATE_DIR/auditd_rules.tpl" "$rules_path"
log_info "Loading audit rules"
sudo augenrules --load || sudo service auditd restart || true
log_info "auditd setup complete"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
CHECKS_DIR="$SKILL_ROOT/checks"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
run_check_bool() {
local script="$1"
if [[ -x "$CHECKS_DIR/$script" ]]; then
if "$CHECKS_DIR/$script" &>/dev/null; then
echo "true"
else
echo "false"
fi
else
echo "skip"
fi
}
main() {
mkdir -p "$OUTPUT_DIR"
local ufw_ok ssh_ok f2b_ok audit_ok
ufw_ok=$(run_check_bool check_ufw.sh)
ssh_ok=$(run_check_bool check_ssh.sh)
f2b_ok=$(run_check_bool check_fail2ban.sh)
audit_ok=$(run_check_bool check_auditd.sh)
local blockers=""
local warnings=""
local next_steps=""
if [[ "$ssh_ok" == "false" ]]; then
blockers="${blockers}\"SSH hardening check failed\","
fi
if [[ "$ufw_ok" == "false" ]]; then
warnings="${warnings}\"UFW not active\","
fi
if [[ "$f2b_ok" == "false" ]]; then
warnings="${warnings}\"fail2ban not active\","
fi
if [[ "$audit_ok" == "false" ]]; then
warnings="${warnings}\"auditd not active\","
fi
next_steps="${next_steps}\"Run ./scripts/99_report.sh\","
if [[ "$ssh_ok" == "true" && "$ufw_ok" == "true" ]]; then
next_steps="${next_steps}\"Proceed to backup-sovereign skill\","
fi
blockers="[${blockers%,}]"
warnings="[${warnings%,}]"
next_steps="[${next_steps%,}]"
cat > "$OUTPUT_DIR/status_matrix.json" <<EOF
{
"timestamp": "$(date -Iseconds)",
"skill": "node-hardening",
"node": "${NODE_NAME:-node-a}",
"checks": {
"ufw": $ufw_ok,
"ssh": $ssh_ok,
"fail2ban": $f2b_ok,
"auditd": $audit_ok
},
"blockers": $blockers,
"warnings": $warnings,
"next_steps": $next_steps
}
EOF
log_info "Status matrix written to $OUTPUT_DIR/status_matrix.json"
echo
echo "============================================"
echo " VERIFICATION SUMMARY"
echo "============================================"
echo
echo " UFW: $ufw_ok"
echo " SSH: $ssh_ok"
echo " fail2ban: $f2b_ok"
echo " auditd: $audit_ok"
echo
if [[ "$ssh_ok" == "true" ]]; then
return 0
fi
return 1
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env bash
set -euo pipefail
# === METADATA ===
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
# === CONFIGURATION ===
: "${NODE_NAME:=node-a}"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
# === FUNCTIONS ===
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
get_ufw_status() {
if command -v ufw &>/dev/null; then
if sudo ufw status 2>/dev/null | grep -q "Status: active"; then
echo "Active"
else
echo "Inactive"
fi
else
echo "Not installed"
fi
}
get_ssh_status() {
if systemctl is-active ssh &>/dev/null || systemctl is-active sshd &>/dev/null; then
echo "Running"
else
echo "Not running"
fi
}
get_fail2ban_status() {
if command -v fail2ban-client &>/dev/null; then
if systemctl is-active fail2ban &>/dev/null; then
echo "Active"
else
echo "Inactive"
fi
else
echo "Not installed"
fi
}
get_auditd_status() {
if command -v auditctl &>/dev/null; then
if systemctl is-active auditd &>/dev/null; then
echo "Active"
else
echo "Inactive"
fi
else
echo "Not installed"
fi
}
list_backups() {
if [[ -d "$BACKUP_DIR" ]]; then
ls -1 "$BACKUP_DIR" 2>/dev/null | while read -r f; do
echo "| $f | $(stat -c%s "$BACKUP_DIR/$f" 2>/dev/null || echo "?") bytes |"
done
else
echo "| (no backups) | - |"
fi
}
main() {
mkdir -p "$OUTPUT_DIR"
log_info "Starting $SCRIPT_NAME..."
local report="$OUTPUT_DIR/audit_report.md"
local status_file="$OUTPUT_DIR/status_matrix.json"
cat > "$report" <<EOF
# Node Hardening Audit Report
**Generated:** $(date -Iseconds)
**Node:** $NODE_NAME
**Skill Version:** 1.0.0
---
## Executive Summary
This report documents the hardening operations performed on **$NODE_NAME**
for sovereign EU infrastructure security.
---
## Components Status
### 1. Firewall (UFW)
| Component | Status |
|-----------|--------|
| UFW | $(get_ufw_status) |
### 2. SSH Service
| Component | Status |
|-----------|--------|
| SSH Daemon | $(get_ssh_status) |
| Config Backup | $([ -f "$BACKUP_DIR/sshd_config.before" ] && echo "Present" || echo "Not found") |
### 3. Intrusion Detection (fail2ban)
| Component | Status |
|-----------|--------|
| fail2ban | $(get_fail2ban_status) |
### 4. Audit Logging (auditd)
| Component | Status |
|-----------|--------|
| auditd | $(get_auditd_status) |
---
## Backups
| File | Size |
|------|------|
$(list_backups)
---
## Verification Results
$(if [[ -f "$status_file" ]]; then
echo '```json'
cat "$status_file"
echo '```'
else
echo "Status matrix not found. Run 90_verify.sh first."
fi)
---
## EU Compliance Declaration
| Aspect | Value |
|--------|-------|
| Data Residency | EU (Ireland - Dublin) |
| GDPR Applicable | Yes |
| Jurisdiction | Irish Law |
| Audit Logging | auditd (local only) |
---
## Rollback Procedures
If access is lost or changes need to be reverted:
1. **Emergency Restore (console):** \`./scripts/rollback/emergency_restore.sh\`
2. **Undo SSH:** \`./scripts/rollback/undo_ssh.sh\`
3. **Undo UFW:** \`./scripts/rollback/undo_ufw.sh\`
---
## Next Steps
1. Verify SSH access from a secondary session
2. Test emergency rollback procedure (recommended)
3. Proceed to **backup-sovereign** skill
4. Document hardening in LAWCHAIN (if applicable)
---
## Artifact Locations
| Artifact | Path |
|----------|------|
| UFW Status (before) | $BACKUP_DIR/ufw_status_before.txt |
| iptables Rules (before) | $BACKUP_DIR/iptables_rules_before.txt |
| sshd_config (before) | $BACKUP_DIR/sshd_config.before |
| UFW Status (after) | $OUTPUT_DIR/ufw_status_after.txt |
| Status Matrix | $OUTPUT_DIR/status_matrix.json |
| This Report | $OUTPUT_DIR/audit_report.md |
---
*Report generated by node-hardening skill v1.0.0*
*$(date -Iseconds)*
EOF
log_info "Audit report written to $report"
# Display the report
echo ""
cat "$report"
log_info "Completed $SCRIPT_NAME"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
die() { log_error "$@"; exit 1; }
reload_service() {
if systemctl list-units --type=service | grep -qE '^sshd\.service'; then
sudo systemctl restart sshd
elif systemctl list-units --type=service | grep -qE '^ssh\.service'; then
sudo systemctl restart ssh
else
die "Could not find ssh/sshd service under systemctl"
fi
}
main() {
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
log_warn "EMERGENCY RESTORE: intended to be run from console if you are locked out"
if command -v ufw &>/dev/null; then
log_warn "Disabling UFW"
sudo ufw --force disable || true
fi
if [[ -f "$BACKUP_DIR/sshd_config.before" ]]; then
log_warn "Restoring sshd_config backup"
sudo cp -a "$BACKUP_DIR/sshd_config.before" /etc/ssh/sshd_config
reload_service
else
log_warn "No sshd_config backup found; skipping SSH restore"
fi
log_info "Emergency restore complete"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
die() { log_error "$@"; exit 1; }
reload_service() {
if systemctl list-units --type=service | grep -qE '^sshd\.service'; then
sudo systemctl reload sshd
elif systemctl list-units --type=service | grep -qE '^ssh\.service'; then
sudo systemctl reload ssh
else
die "Could not find ssh/sshd service under systemctl"
fi
}
main() {
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
if [[ ! -f "$BACKUP_DIR/sshd_config.before" ]]; then
die "No backup found at $BACKUP_DIR/sshd_config.before"
fi
log_info "Restoring /etc/ssh/sshd_config from backup"
sudo cp -a "$BACKUP_DIR/sshd_config.before" /etc/ssh/sshd_config
log_info "Reloading SSH service"
reload_service
log_info "SSH rollback complete"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
: "${BACKUP_DIR:=$OUTPUT_DIR/backups}"
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
die() { log_error "$@"; exit 1; }
main() {
mkdir -p "$OUTPUT_DIR" "$BACKUP_DIR"
if ! command -v ufw &>/dev/null; then
die "ufw not installed"
fi
log_info "Disabling UFW"
sudo ufw --force disable || true
if [[ -f "$BACKUP_DIR/ufw_status_before.txt" ]]; then
log_info "Backup exists at $BACKUP_DIR/ufw_status_before.txt (informational)"
fi
log_info "UFW rollback complete"
}
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"

View File

@@ -0,0 +1,25 @@
# Managed by node-hardening skill
# Increase backlog for busy systems
-b 8192
# Identity files
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
# SSH config
-w /etc/ssh/sshd_config -p wa -k sshd
# Privilege escalation
-w /etc/sudoers -p wa -k sudo
-w /etc/sudoers.d/ -p wa -k sudo
# Systemd unit changes
-w /etc/systemd/system/ -p wa -k systemd
# Network config
-w /etc/ufw/ -p wa -k ufw
# End of rules
-e 1

View File

@@ -0,0 +1,12 @@
# Managed by node-hardening skill
[DEFAULT]
# Ban after 5 failures within 10 minutes
findtime = 10m
maxretry = 5
bantime = 1h
backend = systemd
[sshd]
enabled = true
port = {{SSH_PORT}}
logpath = %(sshd_log)s

View File

@@ -0,0 +1,27 @@
# Managed by node-hardening skill
# Backup of previous config stored in outputs/backups
Port {{SSH_PORT}}
Protocol 2
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
# Keep PAM enabled for session setups (Ubuntu default)
UsePAM yes
X11Forwarding no
AllowAgentForwarding yes
AllowTcpForwarding yes
ClientAliveInterval 300
ClientAliveCountMax 2
LogLevel VERBOSE
# Optional: restrict users
# AllowUsers {{ALLOW_USERS}}