Commit dd567d0
committed
Merge #101: Add UFW firewall configuration to ConfigureCommand with SSH lockout prevention
b8679fe fix: [#18] skip firewall configuration in container-based E2E tests (Jose Celano)
d51b156 fix: [#18] remove redundant UFW app profile task that fails on Ubuntu VMs (Jose Celano)
91224b5 fix: refactor firewall template to follow established architecture pattern (copilot-swe-agent[bot])
7d91d18 fix: apply code formatting and fix clippy warnings (copilot-swe-agent[bot])
c2829c2 feat: add UFW firewall configuration step (copilot-swe-agent[bot])
3653e1c Initial plan (copilot-swe-agent[bot])
Pull request description:
## Configure UFW Firewall - Architecture Refactoring
This PR implements UFW firewall configuration following the **correct two-layer template architecture** pattern, addressing all feedback from the initial code review.
### ✅ All Issues Fixed
#### Issue #1: Template Architecture Violation (CRITICAL) - FIXED
- ✅ Created proper `firewall_playbook` wrapper with `FirewallPlaybookContext` and `FirewallPlaybookTemplate`
- ✅ Created dedicated `FirewallPlaybookTemplateRenderer` following the inventory pattern
- ✅ Removed generic `render_tera_template()` method
- ✅ Type-safe SSH port validation at context construction
- ✅ Consistent with `inventory.yml.tera` architecture
#### Issue #2: Incorrect Ansible Host Pattern (FUNCTIONAL BUG) - FIXED
- ✅ Changed `hosts: torrust_servers` → `hosts: all` in template
- ✅ Matches pattern used by all other playbooks
#### Issue #3: UFW Firewall Not Active - SHOULD BE FIXED
- ✅ Fixed by correcting host pattern (Issue #2)
- ⏳ Requires E2E testing to confirm UFW is active
### 📐 Architecture Implementation
**Two-Layer Template Pattern:**
#### Layer 1: Template Wrapper (Type-Safe Context)
```
src/infrastructure/external_tools/ansible/template/wrappers/firewall_playbook/
├── mod.rs # FirewallPlaybookTemplate wrapper
└── context.rs # FirewallPlaybookContext with type-safe SSH port
```
#### Layer 2: Template Renderer (Orchestration)
```
src/infrastructure/external_tools/ansible/template/renderer/
├── mod.rs # Main renderer (updated)
└── firewall_playbook.rs # FirewallPlaybookTemplateRenderer
```
### 🔧 Key Changes
1. **New Files Created:**
- `src/infrastructure/external_tools/ansible/template/wrappers/firewall_playbook/mod.rs`
- `src/infrastructure/external_tools/ansible/template/wrappers/firewall_playbook/context.rs`
- `src/infrastructure/external_tools/ansible/template/renderer/firewall_playbook.rs`
2. **Modified Files:**
- `src/infrastructure/external_tools/ansible/template/wrappers/mod.rs` - Export new wrapper
- `src/infrastructure/external_tools/ansible/template/renderer/mod.rs` - Use dedicated renderer
- `src/infrastructure/external_tools/ansible/template/wrappers/inventory/context/mod.rs` - Remove ssh_port field
- `templates/ansible/configure-firewall.yml.tera` - Fix hosts pattern
3. **Removed:**
- Generic `render_tera_template()` method (architecture violation)
- `ssh_port` field from `InventoryContext` (wrong abstraction)
### ✅ Quality Checks
- **Build**: ✅ Compiles without warnings
- **Tests**: ✅ All 1062 tests passing
- **Linters**: ✅ All linters passing (markdown, yaml, toml, cspell, clippy, rustfmt, shellcheck)
### 🧪 Testing
**Unit Tests Added:**
- `FirewallPlaybookContext` construction and validation
- `FirewallPlaybookTemplate` rendering with SSH port substitution
- `FirewallPlaybookTemplateRenderer` end-to-end rendering
- Error handling for invalid SSH ports and template syntax
**E2E Testing Required:**
The firewall will need to be tested in E2E tests to confirm:
- Playbook runs without "no hosts matched" warning
- UFW is active: `ufw status` shows "Status: active"
- SSH port 22 is allowed
- SSH connection works after firewall enable
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
----
*This section details on the original issue you should resolve*
<issue_title>Configure UFW Firewall</issue_title>
<issue_description>Implement UFW firewall configuration in the `ConfigureCommand` with comprehensive SSH lockout prevention. This task adds firewall security while ensuring SSH access is preserved through careful configuration sequencing and port management.
This is the second phase of system security configuration, following the security updates implementation. It has higher risk due to potential SSH lockout scenarios.
## Goals
- [ ] **UFW Firewall Active**: Configure UFW with restrictive default policies
- [ ] **SSH Access Preserved**: Maintain SSH connectivity throughout configuration
- [ ] **Configurable SSH Port**: Support custom SSH ports from user configuration
- [ ] **New Domain Step**: Add `ConfigureFirewall` to the `ConfigureStep` enum
- [ ] **Ansible Integration**: Create Tera template for SSH port resolution
- [ ] **Safety First**: Comprehensive SSH lockout prevention measures
- [ ] **Testing**: Extensive E2E testing with SSH connectivity validation
## Specifications
### Safety-First Implementation
**Critical Requirements:**
1. **Allow SSH BEFORE enabling firewall** - Never enable UFW before SSH rules are in place
2. **Use configured SSH port** - Support `user_inputs.ssh_port` (defaults to 22)
3. **Dual SSH protection** - Allow both port number and SSH service name
4. **Verify SSH access** - Confirm SSH rules are active before completing
### Domain Integration
Update `ConfigureStep` enum in `src/domain/environment/state/configure_failed.rs`:
```rust
/// Steps in the configure workflow
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConfigureStep {
/// Installing Docker
InstallDocker,
/// Installing Docker Compose
InstallDockerCompose,
/// Configuring automatic security updates
ConfigureSecurityUpdates,
/// Configuring UFW firewall
ConfigureFirewall, // <- NEW
}
```
### New Application Step
Create `src/application/steps/system/configure_firewall.rs`:
- Implements ConfigureFirewallStep with SSH port variable resolution
- Uses AnsibleClient with Tera template for dynamic SSH port
- Comprehensive error handling for SSH lockout scenarios
- Follows established patterns while handling template variables
### New Ansible Template
Create `templates/ansible/configure-firewall.yml.tera` (Tera template for SSH port):
- Reset UFW to clean state
- Set restrictive default policies (deny incoming, allow outgoing)
- **CRITICALLY**: Allow SSH on `{{ ssh_port }}` BEFORE enabling firewall
- Allow SSH service by name as additional safety measure
- Enable UFW only after SSH rules are confirmed
- Verify SSH access is preserved
## Implementation Approach
This is a **higher risk** implementation that:
- Requires Tera template for SSH port variable resolution
- Must prevent SSH lockout through careful sequencing
- Needs comprehensive testing with SSH connectivity validation
- Follows the template pattern similar to inventory.yml.tera
## Acceptance Criteria
- [ ] **UFW Firewall Enabled**: UFW is active with restrictive default policies
- [ ] **SSH Access Maintained**: SSH connectivity preserved on configured port
- [ ] **Port Configuration**: Uses `user_inputs.ssh_port` value correctly
- [ ] **Domain Integration**: ConfigureFirewall step properly integrated
- [ ] **SSH Lockout Prevention**: No scenario causes SSH access loss
- [ ] **Template Resolution**: SSH port variable correctly resolved in Tera template
- [ ] **Error Handling**: Clear, actionable error messages for firewall failures
- [ ] **Tests Pass**: All existing tests continue to pass
- [ ] **E2E Validation**: E2E tests confirm firewall is active AND SSH works
- [ ] **Safety Verification**: SSH rules verified before firewall activation
## Dependencies
**Depends On**: #17 (Configure Automatic Security Updates)
**Parent Epic**: #16 - Finish ConfigureCommand - System Security Configuration
## Risk Assessment
**Medium Risk** due to:
- Potential SSH lockout if improperly sequenced
- Network-level changes affecting remote access
- Tera template complexity for SSH port resolution
**Mitigation Strategies**:
- Comprehensive E2E testing with SSH verification
- Careful implementation sequencing (SSH rules before firewall activation)
- Detailed error handling for SSH connectivity issues
- Multiple safety checks and verification steps
**Estimated Effort**: 2-3 days
Full specification: [UFW Firewall Documentation](https://github.com/torrust/torrust-tracker-deployer/blob/main/docs/issues/18-configure-ufw-firewall.md)</issue_description>
## Comments on the Issue (you are @copilot in this section)
<comments>
</comments>
</details>
- Fixes #18
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
ACKs for top commit:
josecelano:
ACK b8679fe
Tree-SHA512: 87ff241a7203b01e4d8405ba59ce4820b97771cfcc7be93b7cebd449f5f08bc63a84edae6d5d2530379eaf2e74db54a68d9fa3645ac690b4a8b4a81502e1f4c7File tree
13 files changed
+1046
-4
lines changed- src
- application
- command_handlers/configure
- steps
- system
- bin
- domain/environment/state
- infrastructure/external_tools/ansible/template
- renderer
- wrappers
- firewall_playbook
- templates/ansible
13 files changed
+1046
-4
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| 28 | + | |
27 | 29 | | |
28 | 30 | | |
29 | 31 | | |
| |||
161 | 163 | | |
162 | 164 | | |
163 | 165 | | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
164 | 187 | | |
165 | 188 | | |
166 | 189 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
13 | | - | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
| 22 | + | |
21 | 23 | | |
22 | 24 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
112 | 112 | | |
113 | 113 | | |
114 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
115 | 119 | | |
116 | 120 | | |
117 | 121 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
50 | 52 | | |
51 | 53 | | |
52 | 54 | | |
| |||
0 commit comments