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:
170
node-hardening/SKILL.md
Normal file
170
node-hardening/SKILL.md
Normal 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)
|
||||
15
node-hardening/checks/check_auditd.sh
Executable file
15
node-hardening/checks/check_auditd.sh
Executable 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
|
||||
15
node-hardening/checks/check_fail2ban.sh
Executable file
15
node-hardening/checks/check_fail2ban.sh
Executable 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
|
||||
23
node-hardening/checks/check_ssh.sh
Executable file
23
node-hardening/checks/check_ssh.sh
Executable 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
|
||||
15
node-hardening/checks/check_ufw.sh
Executable file
15
node-hardening/checks/check_ufw.sh
Executable 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
|
||||
54
node-hardening/config.json
Normal file
54
node-hardening/config.json
Normal 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
|
||||
}
|
||||
}
|
||||
118
node-hardening/references/cis_benchmarks.md
Normal file
118
node-hardening/references/cis_benchmarks.md
Normal 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)
|
||||
123
node-hardening/references/recovery_procedures.md
Normal file
123
node-hardening/references/recovery_procedures.md
Normal 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
|
||||
115
node-hardening/references/ssh_cipher_recommendations.md
Normal file
115
node-hardening/references/ssh_cipher_recommendations.md
Normal 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)
|
||||
78
node-hardening/scripts/00_preflight.sh
Executable file
78
node-hardening/scripts/00_preflight.sh
Executable 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 "$@"
|
||||
64
node-hardening/scripts/10_ufw_plan.sh
Executable file
64
node-hardening/scripts/10_ufw_plan.sh
Executable 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 "$@"
|
||||
100
node-hardening/scripts/11_ufw_apply.sh
Executable file
100
node-hardening/scripts/11_ufw_apply.sh
Executable 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 "$@"
|
||||
42
node-hardening/scripts/20_ssh_plan.sh
Executable file
42
node-hardening/scripts/20_ssh_plan.sh
Executable 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 "$@"
|
||||
92
node-hardening/scripts/21_ssh_apply.sh
Executable file
92
node-hardening/scripts/21_ssh_apply.sh
Executable 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 "$@"
|
||||
66
node-hardening/scripts/30_fail2ban_setup.sh
Executable file
66
node-hardening/scripts/30_fail2ban_setup.sh
Executable 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 "$@"
|
||||
62
node-hardening/scripts/40_auditd_setup.sh
Executable file
62
node-hardening/scripts/40_auditd_setup.sh
Executable 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 "$@"
|
||||
98
node-hardening/scripts/90_verify.sh
Executable file
98
node-hardening/scripts/90_verify.sh
Executable 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 "$@"
|
||||
199
node-hardening/scripts/99_report.sh
Executable file
199
node-hardening/scripts/99_report.sh
Executable 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 "$@"
|
||||
47
node-hardening/scripts/rollback/emergency_restore.sh
Executable file
47
node-hardening/scripts/rollback/emergency_restore.sh
Executable 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 "$@"
|
||||
38
node-hardening/scripts/rollback/undo_ssh.sh
Executable file
38
node-hardening/scripts/rollback/undo_ssh.sh
Executable 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 "$@"
|
||||
32
node-hardening/scripts/rollback/undo_ufw.sh
Executable file
32
node-hardening/scripts/rollback/undo_ufw.sh
Executable 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 "$@"
|
||||
25
node-hardening/templates/auditd_rules.tpl
Normal file
25
node-hardening/templates/auditd_rules.tpl
Normal 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
|
||||
12
node-hardening/templates/fail2ban_jail.tpl
Normal file
12
node-hardening/templates/fail2ban_jail.tpl
Normal 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
|
||||
27
node-hardening/templates/sshd_config.tpl
Normal file
27
node-hardening/templates/sshd_config.tpl
Normal 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}}
|
||||
Reference in New Issue
Block a user