From 2fe194ba8228080d16d9757d8030f12544074b75 Mon Sep 17 00:00:00 2001 From: Joakim Date: Tue, 16 Dec 2025 21:45:22 +0100 Subject: [PATCH] Implement modular nftables architecture and Gitea SSH firewall management - Restructure security playbook with modular nftables loader - Base rules loaded first, service rules second, drop rule last - Add Gitea self-contained firewall management (port 2222) - Add fail2ban protection for Gitea SSH brute force attacks - Update documentation with new firewall architecture - Create comprehensive Gitea deployment and testing guide This enables self-contained service roles to manage their own firewall rules without modifying the central security playbook. Each service deploys rules to /etc/nftables.d/ which are loaded before the final drop rule, maintaining the defense-in-depth security model. --- docs/gitea-deployment-guide.md | 541 +++++++++++++++++++++++++++++ docs/security-hardening.md | 76 ++-- now-what.md | 4 +- playbooks/security.yml | 76 +++- rick-infra.yml | 16 +- roles/gitea/README.md | 93 ++++- roles/gitea/defaults/main.yml | 7 + roles/gitea/handlers/main.yml | 14 +- roles/gitea/tasks/fail2ban.yml | 75 ++++ roles/gitea/tasks/firewall.yml | 51 +++ roles/gitea/tasks/main.yml | 12 + roles/gitea/templates/gitea.nft.j2 | 11 + 12 files changed, 933 insertions(+), 43 deletions(-) create mode 100644 docs/gitea-deployment-guide.md create mode 100644 roles/gitea/tasks/fail2ban.yml create mode 100644 roles/gitea/tasks/firewall.yml create mode 100644 roles/gitea/templates/gitea.nft.j2 diff --git a/docs/gitea-deployment-guide.md b/docs/gitea-deployment-guide.md new file mode 100644 index 0000000..796afc8 --- /dev/null +++ b/docs/gitea-deployment-guide.md @@ -0,0 +1,541 @@ +# Gitea Deployment and Testing Guide + +Comprehensive guide for deploying and testing Gitea Git service with SSH access on rick-infra. + +## Deployment + +### 1. Prerequisites + +Ensure you have the required vault variables set in your host_vars: + +```yaml +# host_vars/arch-vps/vault.yml (encrypted) +vault_gitea_db_password: "your_secure_password_here" +``` + +### 2. Deploy Gitea Role + +Run the rick-infra playbook with Gitea role: + +```bash +# Deploy Gitea to arch-vps +ansible-playbook -i inventory/hosts.yml rick-infra.yml --limit arch-vps + +# Or deploy only Gitea role +ansible-playbook -i inventory/hosts.yml rick-infra.yml --tags gitea --limit arch-vps +``` + +### 3. Verify Deployment + +Check that all services are running: + +```bash +# SSH into the server +ssh root@arch-vps + +# Check Gitea service status +systemctl status gitea + +# Check if Gitea is listening on HTTP port +ss -tlnp | grep 3000 + +# Check if Gitea SSH is listening +ss -tlnp | grep 2222 + +# Verify firewall rules +nft list ruleset | grep 2222 + +# Check fail2ban status +fail2ban-client status gitea-ssh +``` + +Expected output: +- Gitea service: `active (running)` +- HTTP port 3000: listening on `127.0.0.1:3000` +- SSH port 2222: listening on `0.0.0.0:2222` +- nftables: Rule allowing `tcp dport 2222` +- fail2ban: `gitea-ssh` jail active + +## Testing Guide + +### Test 1: Web Interface Access + +**Purpose**: Verify HTTPS access through Caddy reverse proxy + +```bash +# From your local machine +curl -I https://git.jnss.me +``` + +**Expected result**: +- HTTP/2 200 OK +- Redirects to login page +- Valid TLS certificate + +**Action**: Open browser to `https://git.jnss.me` and verify web interface loads + +--- + +### Test 2: Firewall Port Verification + +**Purpose**: Confirm port 2222 is accessible from external networks + +```bash +# From your local machine (not from the server) +nc -zv git.jnss.me 2222 +``` + +**Expected result**: +``` +Connection to git.jnss.me 2222 port [tcp/*] succeeded! +``` + +**If this fails**: The firewall rule is not active or nftables service is not running. + +**Troubleshooting**: +```bash +# On the server +ssh root@arch-vps + +# Check if nftables service is running +systemctl status nftables + +# List all firewall rules +nft list ruleset + +# Verify Gitea rule file exists +cat /etc/nftables.d/gitea.nft + +# Manually reload nftables +systemctl reload nftables +``` + +--- + +### Test 3: SSH Connection Test + +**Purpose**: Verify Gitea SSH server accepts connections + +```bash +# From your local machine +ssh -T -p 2222 git@git.jnss.me +``` + +**Expected result** (before adding SSH key): +``` +Hi there, You've successfully authenticated, but Gitea does not provide shell access. +If this is unexpected, please log in with password and setup Gitea under another user. +``` + +**OR** (if authentication fails): +``` +git@git.jnss.me: Permission denied (publickey). +``` + +This is normal - it means Gitea SSH server is responding, you just need to add your SSH key. + +**If connection times out**: Port 2222 is blocked or Gitea SSH is not running. + +--- + +### Test 4: SSH Key Setup and Authentication + +**Purpose**: Add SSH key to Gitea and test authentication + +**Step 4.1**: Create Gitea admin account +1. Visit `https://git.jnss.me` +2. Click "Register" (if registration is enabled) or use initial admin setup +3. Create your user account + +**Step 4.2**: Generate SSH key (if needed) +```bash +# On your local machine +ssh-keygen -t ed25519 -C "your_email@example.com" + +# View your public key +cat ~/.ssh/id_ed25519.pub +``` + +**Step 4.3**: Add SSH key to Gitea +1. Log into Gitea web interface +2. Click your profile → Settings +3. Click "SSH / GPG Keys" tab +4. Click "Add Key" +5. Paste your public key (`id_ed25519.pub` contents) +6. Give it a name and click "Add Key" + +**Step 4.4**: Test SSH authentication +```bash +# From your local machine +ssh -T -p 2222 git@git.jnss.me +``` + +**Expected result**: +``` +Hi there, ! You've successfully authenticated with the key named +``` + +--- + +### Test 5: Repository Operations + +**Purpose**: Test actual Git operations over SSH + +**Step 5.1**: Create a test repository in Gitea web interface +1. Click "+" → "New Repository" +2. Name: `test-repo` +3. Click "Create Repository" + +**Step 5.2**: Clone the repository +```bash +# From your local machine +git clone ssh://git@git.jnss.me:2222/your_username/test-repo.git +cd test-repo +``` + +**Expected result**: Repository clones successfully + +**Step 5.3**: Make a commit and push +```bash +# Create a test file +echo "# Test Repository" > README.md + +# Commit and push +git add README.md +git commit -m "Initial commit" +git push origin main +``` + +**Expected result**: +``` +Enumerating objects: 3, done. +Counting objects: 100% (3/3), done. +Writing objects: 100% (3/3), 234 bytes | 234.00 KiB/s, done. +Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 +To ssh://git.jnss.me:2222/your_username/test-repo.git + * [new branch] main -> main +``` + +**Step 5.4**: Verify in web interface +1. Refresh Gitea web UI +2. Navigate to `test-repo` +3. Verify `README.md` appears + +--- + +### Test 6: fail2ban Protection + +**Purpose**: Verify SSH brute force protection is active + +**Step 6.1**: Check fail2ban status +```bash +# On the server +ssh root@arch-vps + +# Check gitea-ssh jail +fail2ban-client status gitea-ssh +``` + +**Expected result**: +``` +Status for the jail: gitea-ssh +|- Filter +| |- Currently failed: 0 +| |- Total failed: 0 +| `- File list: /var/lib/gitea/log/gitea.log +`- Actions + |- Currently banned: 0 + |- Total banned: 0 + `- Banned IP list: +``` + +**Step 6.2**: Simulate failed authentication (optional) +```bash +# From your local machine, try connecting with wrong key multiple times +ssh -T -p 2222 -i /path/to/wrong/key git@git.jnss.me +# Repeat this 5+ times quickly +``` + +**Step 6.3**: Check if IP was banned +```bash +# On the server +fail2ban-client status gitea-ssh +``` + +**Expected result**: Your IP should appear in "Currently banned" list after 5 failed attempts. + +**Step 6.4**: Unban yourself (if needed) +```bash +# On the server +fail2ban-client set gitea-ssh unbanip YOUR_IP_ADDRESS +``` + +--- + +### Test 7: Firewall Rule Persistence + +**Purpose**: Verify firewall rules survive reboot + +**Step 7.1**: Check current rules +```bash +# On the server +ssh root@arch-vps +nft list ruleset | grep 2222 +``` + +**Step 7.2**: Reboot server +```bash +# On the server +reboot +``` + +**Step 7.3**: After reboot, verify rules are still active +```bash +# Wait for server to come back up, then SSH in +ssh root@arch-vps + +# Check nftables rules again +nft list ruleset | grep 2222 + +# Verify Gitea SSH still accessible from outside +exit + +# From local machine +ssh -T -p 2222 git@git.jnss.me +``` + +**Expected result**: Port 2222 rule persists after reboot, SSH access still works. + +--- + +## Troubleshooting + +### Issue: Connection timeout on port 2222 + +**Symptoms**: `ssh: connect to host git.jnss.me port 2222: Connection timed out` + +**Diagnosis**: +```bash +# On server +systemctl status gitea # Check if Gitea is running +ss -tlnp | grep 2222 # Check if SSH is listening +nft list ruleset | grep 2222 # Check firewall rule +systemctl status nftables # Check firewall service +``` + +**Solutions**: +1. **Gitea not running**: `systemctl start gitea` +2. **Firewall rule missing**: Re-run Ansible playbook with Gitea role +3. **nftables not running**: `systemctl start nftables` +4. **Rule file missing**: Check `/etc/nftables.d/gitea.nft` exists + +--- + +### Issue: Permission denied (publickey) + +**Symptoms**: SSH connection succeeds but authentication fails + +**Diagnosis**: +```bash +# Verbose SSH connection +ssh -vvv -T -p 2222 git@git.jnss.me +``` + +**Solutions**: +1. **SSH key not added to Gitea**: Add your public key in Gitea web UI +2. **Wrong SSH key used**: Specify correct key: `ssh -i ~/.ssh/id_ed25519 -T -p 2222 git@git.jnss.me` +3. **Key permissions wrong**: `chmod 600 ~/.ssh/id_ed25519` + +--- + +### Issue: fail2ban not protecting Gitea + +**Symptoms**: `fail2ban-client status gitea-ssh` shows jail doesn't exist + +**Diagnosis**: +```bash +# Check if filter exists +ls -la /etc/fail2ban/filter.d/gitea-ssh.conf + +# Check if jail is configured +grep -A 10 "gitea-ssh" /etc/fail2ban/jail.local + +# Check fail2ban logs +journalctl -u fail2ban | grep gitea +``` + +**Solutions**: +1. **Jail not configured**: Re-run Ansible playbook with Gitea role +2. **fail2ban not running**: `systemctl start fail2ban` +3. **Log file not found**: Check Gitea is logging to `/var/lib/gitea/log/gitea.log` + +--- + +### Issue: Git clone works but push fails + +**Symptoms**: Can clone but `git push` gives permission error + +**Diagnosis**: +- Check repository permissions in Gitea web UI +- Verify you have write access to the repository + +**Solutions**: +1. **Not repository owner**: Ask owner to give you write access +2. **Repository is archived**: Unarchive in settings +3. **Branch protected**: Check branch protection rules + +--- + +## Verification Checklist + +Use this checklist to verify your Gitea deployment: + +- [ ] Gitea web interface accessible at `https://git.jnss.me` +- [ ] Port 2222 accessible from external network (`nc -zv git.jnss.me 2222`) +- [ ] SSH connection succeeds (`ssh -T -p 2222 git@git.jnss.me`) +- [ ] SSH key added to Gitea account +- [ ] SSH authentication works (shows username in response) +- [ ] Can clone repository via SSH +- [ ] Can push commits to repository +- [ ] nftables rule for port 2222 exists and is active +- [ ] fail2ban jail `gitea-ssh` is running +- [ ] Gitea service auto-starts on boot +- [ ] nftables rules persist after reboot + +--- + +## Post-Deployment Configuration + +### Disable Public Registration (Recommended) + +If you don't want anyone to create accounts: + +1. Edit `host_vars/arch-vps/main.yml` or `group_vars/production/main.yml` +2. Add: + ```yaml + gitea_disable_registration: true + ``` +3. Re-run playbook: + ```bash + ansible-playbook -i inventory/hosts.yml rick-infra.yml --tags gitea --limit arch-vps + ``` + +### Configure Email (Optional) + +For password resets and notifications, configure SMTP: + +1. Edit Gitea configuration directly: + ```bash + ssh root@arch-vps + nano /etc/gitea/app.ini + ``` + +2. Add mailer section: + ```ini + [mailer] + ENABLED = true + FROM = gitea@jnss.me + PROTOCOL = smtps + SMTP_ADDR = smtp.example.com + SMTP_PORT = 465 + USER = gitea@jnss.me + PASSWD = your_smtp_password + ``` + +3. Restart Gitea: + ```bash + systemctl restart gitea + ``` + +### Enable Actions/CI (Optional) + +Gitea Actions provides GitHub Actions-compatible CI/CD: + +1. Edit `roles/gitea/templates/app.ini.j2` +2. Add Actions section +3. Re-run playbook + +--- + +## nftables Architecture + +### Modular Firewall Design + +Rick-infra uses a modular nftables architecture that allows services to self-manage their firewall rules: + +**Load Order:** +1. **Base rules** (`/etc/nftables.conf`) - Infrastructure essentials (SSH, HTTP, HTTPS, ICMP) +2. **Service rules** (`/etc/nftables.d/[0-8]*.nft`) - Service-specific ports (e.g., `50-gitea.nft`) +3. **Drop rule** (`/etc/nftables.d/99-drop.nft`) - Final catch-all drop + +**Key Files:** +- `/etc/nftables.conf` - Base infrastructure firewall rules +- `/etc/nftables-load.conf` - Loader script that orchestrates rule loading +- `/etc/nftables.d/50-gitea.nft` - Gitea SSH port (2222) rule +- `/etc/nftables.d/99-drop.nft` - Final drop rule (loaded last) + +**How It Works:** +``` +┌─────────────────────────────────────────────────┐ +│ /etc/nftables-load.conf │ +│ │ +│ 1. include "/etc/nftables.conf" │ +│ └─> Allow: SSH(22), HTTP(80), HTTPS(443) │ +│ │ +│ 2. include "/etc/nftables.d/[0-8]*.nft" │ +│ └─> 50-gitea.nft: Allow SSH(2222) │ +│ │ +│ 3. include "/etc/nftables.d/99-drop.nft" │ +│ └─> Drop all other traffic │ +└─────────────────────────────────────────────────┘ +``` + +This ensures service rules are evaluated **before** the drop rule, allowing each service role to be self-contained. + +## Security Best Practices + +1. **Use strong database password**: Ensure `vault_gitea_db_password` is strong +2. **Enable 2FA**: Enable two-factor authentication in Gitea settings +3. **Monitor fail2ban**: Regularly check banned IPs: `fail2ban-client status gitea-ssh` +4. **Keep updated**: Run security playbook regularly for system updates +5. **Review SSH keys**: Periodically audit SSH keys in Gitea user accounts +6. **Backup repositories**: Regular backups of `/var/lib/gitea/repositories` +7. **Monitor logs**: Check Gitea logs for suspicious activity: `journalctl -u gitea` + +--- + +## Quick Reference Commands + +```bash +# Service management +systemctl status gitea +systemctl restart gitea +journalctl -u gitea -f + +# Firewall +nft list ruleset | grep 2222 +systemctl restart nftables +cat /etc/nftables.d/50-gitea.nft + +# fail2ban +fail2ban-client status gitea-ssh +fail2ban-client get gitea-ssh banned +fail2ban-client set gitea-ssh unbanip IP_ADDRESS + +# Network +ss -tlnp | grep 2222 +nc -zv git.jnss.me 2222 + +# SSH testing +ssh -T -p 2222 git@git.jnss.me +ssh -vvv -T -p 2222 git@git.jnss.me # Verbose mode + +# Git operations +git clone ssh://git@git.jnss.me:2222/user/repo.git +git remote add origin ssh://git@git.jnss.me:2222/user/repo.git +``` + +--- + +**Rick-Infra Gitea Deployment Guide** +Self-contained Git service with automatic firewall and security management. diff --git a/docs/security-hardening.md b/docs/security-hardening.md index bbf1a5c..1178d04 100644 --- a/docs/security-hardening.md +++ b/docs/security-hardening.md @@ -88,31 +88,42 @@ ssh root@your-vps "journalctl -u sshd | grep -i 'failed\|invalid'" ### Firewall Configuration -#### nftables Firewall Rules +#### Modular nftables Architecture +Rick-infra uses a **modular firewall architecture** that enables self-contained service roles: + +**Structure:** +``` +/etc/nftables.conf Base infrastructure rules (SSH, HTTP, HTTPS) +/etc/nftables-load.conf Orchestration script (loads rules in order) +/etc/nftables.d/ + ├── 50-gitea.nft Service-specific rules (Gitea SSH port 2222) + └── 99-drop.nft Final drop rule (loaded last) +``` + +**Load Order:** +1. Base infrastructure rules (always allowed) +2. Service-specific rules (00-98 prefix) +3. Final drop rule (99-drop.nft) + +**Example: Current Ruleset** ```bash -# Deployed firewall configuration table inet filter { chain input { type filter hook input priority 0; policy drop; - # Allow loopback traffic - iifname "lo" accept - - # Allow established connections + # Base infrastructure rules + iif "lo" accept ct state established,related accept - - # Allow SSH (rate limited) - tcp dport 22 ct state new limit rate 5/minute accept - - # Allow HTTP/HTTPS - tcp dport {80, 443} accept - - # Allow ICMP (rate limited) + tcp dport 22 ct state new accept + tcp dport {80, 443} ct state new accept icmp type echo-request limit rate 1/second accept - # Log dropped packets - log prefix "DROPPED: " drop + # Service-specific rules (loaded from /etc/nftables.d/) + tcp dport 2222 ct state new accept comment "Gitea SSH (Port 2222)" + + # Final drop rule (99-drop.nft) + counter drop comment "Drop all other traffic" } chain forward { @@ -129,15 +140,38 @@ table inet filter { ```bash # Check firewall status -ssh root@your-vps "nft list ruleset" +nft list ruleset -# Monitor dropped connections -ssh root@your-vps "journalctl -k | grep DROPPED" +# View service-specific rules +ls -la /etc/nftables.d/ +cat /etc/nftables.d/50-gitea.nft -# Temporary rule addition (emergency access) -ssh root@your-vps "nft add rule inet filter input tcp dport 8080 accept" +# Reload firewall (after manual changes) +systemctl restart nftables + +# Test configuration syntax +nft -c -f /etc/nftables-load.conf + +# Add service rule (example for future services) +# Create /etc/nftables.d/60-newservice.nft with your rules +echo 'add rule inet filter input tcp dport 8080 accept comment "New Service"' > /etc/nftables.d/60-newservice.nft +systemctl restart nftables ``` +#### Adding New Service Ports + +When deploying new services that need firewall access: + +1. Create rule file: `/etc/nftables.d/XX-servicename.nft` (XX = 00-98) +2. Add rule: `add rule inet filter input tcp dport PORT accept comment "Service Name"` +3. Restart nftables: `systemctl restart nftables` + +**Naming Convention:** +- `00-19`: Infrastructure services +- `20-79`: Application services +- `80-98`: Custom/temporary rules +- `99`: Drop rule (reserved) + ### Intrusion Detection (fail2ban) #### fail2ban Configuration diff --git a/now-what.md b/now-what.md index 7468ae2..bc6aafc 100644 --- a/now-what.md +++ b/now-what.md @@ -1,7 +1,7 @@ # Now what? -- [ ] Redeploy on clean VPS to test playbook - - [ ] Must set up mini-vps for sigvild and devigo +- [x] Redeploy on clean VPS to test playbook + - [x] Must set up mini-vps for sigvild and devigo - [ ] What gets served on jnss.me? - [ ] Backups diff --git a/playbooks/security.yml b/playbooks/security.yml index 3b9ac3a..b668a67 100644 --- a/playbooks/security.yml +++ b/playbooks/security.yml @@ -123,23 +123,37 @@ name: nftables state: present - - name: Create nftables configuration + - name: Create nftables rules directory + file: + path: /etc/nftables.d + state: directory + mode: '0755' + + - name: Create base nftables configuration copy: content: | #!/usr/sbin/nft -f - # Main firewall table + # Flush existing rules for clean slate + flush ruleset + + # Main firewall table - Rick-Infra Security + # Architecture: Base rules -> Service rules -> Drop rule table inet filter { chain input { type filter hook input priority 0; policy drop; + # ====================================== + # Base Infrastructure Rules + # ====================================== + # Allow loopback interface iif "lo" accept # Allow established and related connections ct state established,related accept - # Allow SSH (port 22) + # Allow SSH (port 22) - Infrastructure access tcp dport 22 ct state new accept # Allow HTTP and HTTPS for Caddy reverse proxy @@ -148,9 +162,6 @@ # Allow ping with rate limiting icmp type echo-request limit rate 1/second accept icmpv6 type echo-request limit rate 1/second accept - - # Log and drop everything else - counter drop } chain forward { @@ -165,9 +176,43 @@ mode: '0755' backup: yes register: nft_config_changed + + - name: Create nftables drop rule (loaded last) + copy: + content: | + # Final drop rule - Rick-Infra Security + # This file is loaded LAST to drop all unmatched traffic + # Service-specific rules in /etc/nftables.d/ are loaded before this + + add rule inet filter input counter drop comment "Drop all other traffic" + dest: /etc/nftables.d/99-drop.nft + mode: '0644' + backup: yes + register: nft_drop_changed + + - name: Create nftables loader script + copy: + content: | + #!/usr/sbin/nft -f + + # Rick-Infra nftables loader + # Loads rules in correct order: base -> services -> drop + + # Load base infrastructure rules + include "/etc/nftables.conf" + + # Load service-specific rules (00-98 range) + include "/etc/nftables.d/[0-8]*.nft" + + # Load final drop rule (99-drop.nft) + include "/etc/nftables.d/99-drop.nft" + dest: /etc/nftables-load.conf + mode: '0755' + backup: yes + register: nft_loader_changed - name: Test nftables configuration syntax - command: nft -c -f /etc/nftables.conf + command: nft -c -f /etc/nftables-load.conf changed_when: false failed_when: false register: nft_test @@ -177,18 +222,31 @@ msg: "nftables configuration test failed: {{ nft_test.stderr }}" when: nft_test.rc != 0 + - name: Update nftables systemd service to use loader + lineinfile: + path: /usr/lib/systemd/system/nftables.service + regexp: '^ExecStart=' + line: 'ExecStart=/usr/sbin/nft -f /etc/nftables-load.conf' + backup: yes + register: nft_service_changed + + - name: Reload systemd daemon if service changed + systemd: + daemon_reload: yes + when: nft_service_changed.changed + - name: Flush existing nftables rules before applying new configuration command: nft flush ruleset failed_when: false changed_when: false - when: nft_config_changed.changed + when: nft_config_changed.changed or nft_drop_changed.changed or nft_loader_changed.changed - name: Enable and start nftables service systemd: name: nftables enabled: yes state: restarted - when: nft_config_changed.changed + when: nft_config_changed.changed or nft_drop_changed.changed or nft_loader_changed.changed or nft_service_changed.changed - name: Wait for nftables to be active pause: diff --git a/rick-infra.yml b/rick-infra.yml index 3f51794..1bba12a 100644 --- a/rick-infra.yml +++ b/rick-infra.yml @@ -25,13 +25,13 @@ # name: authentik # tags: ['authentik', 'sso', 'auth'] - # - name: Deploy Gitea - # include_role: - # name: gitea - # tags: ['gitea', 'git', 'development'] - - - name: Deploy Nextcloud + - name: Deploy Gitea include_role: - name: nextcloud - tags: ['nextcloud', 'cloud', 'storage'] + name: gitea + tags: ['gitea', 'git', 'development'] + + # - name: Deploy Nextcloud + # include_role: + # name: nextcloud + # tags: ['nextcloud', 'cloud', 'storage'] diff --git a/roles/gitea/README.md b/roles/gitea/README.md index 5639b4b..ad44fe7 100644 --- a/roles/gitea/README.md +++ b/roles/gitea/README.md @@ -9,15 +9,19 @@ Self-contained Gitea Git service for rick-infra following the established archit - ✅ **PostgreSQL integration**: Uses shared PostgreSQL infrastructure - ✅ **Caddy integration**: Deploys reverse proxy configuration - ✅ **Security hardened**: SystemD restrictions and secure defaults +- ✅ **Firewall management**: Automatically configures nftables for SSH access +- ✅ **fail2ban protection**: Brute force protection for SSH authentication - ✅ **Production ready**: HTTPS, SSH access, LFS support ## Architecture - **Dependencies**: PostgreSQL infrastructure role - **Database**: Self-managed gitea database and user -- **Network**: HTTP on :3000, SSH on :2222 (localhost) -- **Web access**: https://git.domain.com (via Caddy) +- **Network**: HTTP on :3000 (localhost), SSH on :2222 (public) +- **Web access**: https://git.domain.com (via Caddy reverse proxy) - **SSH access**: ssh://git@git.domain.com:2222 +- **Firewall**: Port 2222 automatically opened via nftables +- **Security**: fail2ban monitors and blocks SSH brute force attempts ## Configuration @@ -42,6 +46,9 @@ gitea_db_password: "{{ vault_gitea_db_password }}" gitea_app_name: "Gitea: Git with a cup of tea" gitea_disable_registration: false gitea_enable_lfs: true + +# Firewall and Security +gitea_manage_firewall: true # Automatically manage nftables rules ``` ## Usage @@ -56,12 +63,94 @@ gitea_enable_lfs: true - Caddy web server (for HTTPS access) - Vault password: `vault_gitea_db_password` +## SSH Access + +Gitea provides Git repository access via SSH on port 2222: + +```bash +# Clone a repository +git clone ssh://git@git.jnss.me:2222/username/repository.git + +# Or add as remote +git remote add origin ssh://git@git.jnss.me:2222/username/repository.git +``` + +### SSH Key Setup + +1. **Generate SSH key** (if you don't have one): + ```bash + ssh-keygen -t ed25519 -C "your_email@example.com" + ``` + +2. **Copy your public key**: + ```bash + cat ~/.ssh/id_ed25519.pub + ``` + +3. **Add to Gitea**: + - Log into Gitea web interface + - Go to Settings → SSH/GPG Keys + - Click "Add Key" + - Paste your public key + +4. **Test SSH connection**: + ```bash + ssh -T -p 2222 git@git.jnss.me + ``` + +## Firewall and Security + +### Automatic Firewall Management + +The Gitea role automatically manages firewall rules via nftables: + +- **Port 2222** is opened automatically when Gitea is deployed +- Firewall rules are stored in `/etc/nftables.d/gitea.nft` +- Rules are integrated with the main security playbook configuration +- To disable automatic firewall management, set `gitea_manage_firewall: false` + +### fail2ban Protection + +SSH brute force protection is automatically configured: + +- **Jail**: `gitea-ssh` monitors Gitea SSH authentication attempts +- **Max retries**: 5 failed attempts +- **Find time**: 10 minutes (600 seconds) +- **Ban time**: 1 hour (3600 seconds) +- **Action**: IP banned via nftables + +Check fail2ban status: +```bash +# Check Gitea SSH jail status +fail2ban-client status gitea-ssh + +# View banned IPs +fail2ban-client get gitea-ssh banned + +# Unban an IP if needed +fail2ban-client set gitea-ssh unbanip 203.0.113.100 +``` + +### Firewall Verification + +```bash +# List active nftables rules +nft list ruleset + +# Check if Gitea SSH port is open +nft list ruleset | grep 2222 + +# Verify from external machine +nc -zv git.jnss.me 2222 +``` + ## Self-Contained Design This role follows rick-infra's self-contained service pattern: - Creates its own database and user via PostgreSQL infrastructure - Manages its own configuration and data - Deploys its own Caddy reverse proxy config +- Manages its own firewall rules and security (nftables, fail2ban) - Independent lifecycle from other services --- diff --git a/roles/gitea/defaults/main.yml b/roles/gitea/defaults/main.yml index 4517af6..a78c630 100644 --- a/roles/gitea/defaults/main.yml +++ b/roles/gitea/defaults/main.yml @@ -66,6 +66,13 @@ gitea_require_signin: false # SSH settings gitea_start_ssh_server: true +# ================================================================= +# Firewall Configuration +# ================================================================= + +# Firewall management +gitea_manage_firewall: true # Set to false if firewall is managed externally + # ================================================================= # Infrastructure Dependencies (Read-only) # ================================================================= diff --git a/roles/gitea/handlers/main.yml b/roles/gitea/handlers/main.yml index 928be19..accbf03 100644 --- a/roles/gitea/handlers/main.yml +++ b/roles/gitea/handlers/main.yml @@ -15,4 +15,16 @@ systemd: name: caddy state: reloaded - when: caddy_service_enabled | default(false) \ No newline at end of file + when: caddy_service_enabled | default(false) + +- name: reload nftables + systemd: + name: nftables + state: reloaded + # Safety: only reload if service is active + when: ansible_connection != 'local' + +- name: restart fail2ban + systemd: + name: fail2ban + state: restarted \ No newline at end of file diff --git a/roles/gitea/tasks/fail2ban.yml b/roles/gitea/tasks/fail2ban.yml new file mode 100644 index 0000000..ac5ef09 --- /dev/null +++ b/roles/gitea/tasks/fail2ban.yml @@ -0,0 +1,75 @@ +--- +# Gitea fail2ban Configuration - Rick-Infra +# Protects Gitea SSH from brute force attacks +# Integrates with system fail2ban service + +- name: Install fail2ban + pacman: + name: fail2ban + state: present + +- name: Create Gitea fail2ban filter + copy: + content: | + # Fail2ban filter for Gitea SSH authentication failures + # Rick-Infra: Gitea role + + [Definition] + # Match failed authentication attempts in Gitea logs + failregex = .*(Failed authentication attempt|authentication failed|Invalid user|Failed login attempt).*from\s+ + .*level=warning.*msg=.*authentication.*failed.*ip= + + ignoreregex = + dest: /etc/fail2ban/filter.d/gitea-ssh.conf + mode: '0644' + backup: yes + notify: restart fail2ban + +- name: Ensure fail2ban jail.local exists + file: + path: /etc/fail2ban/jail.local + state: touch + mode: '0644' + modification_time: preserve + access_time: preserve + +- name: Add Gitea SSH jail to fail2ban + blockinfile: + path: /etc/fail2ban/jail.local + marker: "# {mark} ANSIBLE MANAGED BLOCK - Gitea SSH" + block: | + # Gitea SSH Protection - Rick-Infra + [gitea-ssh] + enabled = true + port = {{ gitea_ssh_port }} + filter = gitea-ssh + logpath = {{ gitea_home }}/log/gitea.log + maxretry = 5 + findtime = 600 + bantime = 3600 + banaction = nftables + backup: yes + notify: restart fail2ban + +- name: Enable and start fail2ban service + systemd: + name: fail2ban + enabled: yes + state: started + +- name: Add fail2ban restart handler + meta: flush_handlers + +- name: Display fail2ban status for Gitea + debug: + msg: | + 🛡️ fail2ban configured for Gitea SSH + 📍 Filter: /etc/fail2ban/filter.d/gitea-ssh.conf + 📍 Jail: gitea-ssh (in /etc/fail2ban/jail.local) + 🔒 Protection: Port {{ gitea_ssh_port }} + ⏱️ Ban time: 1 hour (3600 seconds) + 🔢 Max retries: 5 attempts in 10 minutes + + Check status: fail2ban-client status gitea-ssh + +# Rick-Infra: Self-contained fail2ban protection per role diff --git a/roles/gitea/tasks/firewall.yml b/roles/gitea/tasks/firewall.yml new file mode 100644 index 0000000..e29bce2 --- /dev/null +++ b/roles/gitea/tasks/firewall.yml @@ -0,0 +1,51 @@ +--- +# Gitea Firewall Configuration - Rick-Infra +# Self-contained firewall management for Gitea SSH access +# Opens port 2222 for Gitea's SSH server + +- name: Install nftables (if not present) + pacman: + name: nftables + state: present + +- name: Create nftables rules directory + file: + path: /etc/nftables.d + state: directory + mode: '0755' + +- name: Deploy Gitea nftables rules + template: + src: gitea.nft.j2 + dest: /etc/nftables.d/50-gitea.nft + mode: '0644' + notify: reload nftables + register: gitea_nft_deployed + +- name: Validate nftables loader configuration + command: nft -c -f /etc/nftables-load.conf + changed_when: false + failed_when: false + register: nft_validation + +- name: Display nftables validation results + debug: + msg: "{{ 'nftables configuration valid' if nft_validation.rc == 0 else 'nftables validation failed: ' + nft_validation.stderr }}" + when: nft_validation is defined + +- name: Enable and start nftables service + systemd: + name: nftables + enabled: yes + state: started + +- name: Display Gitea firewall status + debug: + msg: | + 🔥 Gitea firewall configuration deployed + 📍 Rule file: /etc/nftables.d/50-gitea.nft + 🔓 Port opened: {{ gitea_ssh_port }} (Gitea SSH) + + ⚠️ Note: nftables will reload automatically via handler + +# Rick-Infra: Self-contained firewall management per role diff --git a/roles/gitea/tasks/main.yml b/roles/gitea/tasks/main.yml index 0422198..fc261bd 100644 --- a/roles/gitea/tasks/main.yml +++ b/roles/gitea/tasks/main.yml @@ -16,6 +16,18 @@ name: gitea state: present +# Firewall configuration - self-managed by Gitea role +- name: Configure firewall for Gitea SSH + import_tasks: firewall.yml + tags: ['firewall'] + when: gitea_manage_firewall | default(true) + +# fail2ban protection - self-managed by Gitea role +- name: Configure fail2ban for Gitea SSH + import_tasks: fail2ban.yml + tags: ['fail2ban', 'security'] + when: gitea_manage_firewall | default(true) + - name: Install Git pacman: name: git diff --git a/roles/gitea/templates/gitea.nft.j2 b/roles/gitea/templates/gitea.nft.j2 new file mode 100644 index 0000000..81cbfff --- /dev/null +++ b/roles/gitea/templates/gitea.nft.j2 @@ -0,0 +1,11 @@ +# Gitea SSH Firewall Rules - Rick-Infra +# Generated by Ansible Gitea role +# Allows incoming SSH connections on port {{ gitea_ssh_port }} +# +# This file is loaded BEFORE the final drop rule (99-drop.nft) +# Filename: 50-gitea.nft (ensures proper load order) + +# Add Gitea SSH port to the input chain +add rule inet filter input tcp dport {{ gitea_ssh_port }} ct state new accept comment "Gitea SSH (Port {{ gitea_ssh_port }})" + +# Rick-Infra: Self-contained firewall rule for Gitea SSH access