homelab_automation/documentation/TERMINAL_COMMAND_HISTORY.md
Bruno Charest 5bc12d0729
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Add terminal session management with heartbeat monitoring, idle timeout detection, session reuse logic, and command history panel UI with search and filtering capabilities
2025-12-18 13:49:40 -05:00

324 lines
8.7 KiB
Markdown

# Terminal Command History - Documentation
## Overview
The Terminal Command History feature logs validated terminal commands executed via the web SSH terminal, providing:
- **Command history** per host and user
- **Search functionality** with instant filtering
- **Security-first approach** - only safe commands are logged, sensitive data is blocked or masked
## Architecture
```
Browser <--> ttyd <--> Terminal Gateway (PTY proxy) <--> SSH <--> Remote Host
|
v
Command Logger (buffer)
|
v
Command Policy Engine
|
v
Database (terminal_command_logs)
```
## Components
### Backend
| File | Description |
|------|-------------|
| `app/security/command_policy.py` | Security policy engine (blocklist/allowlist/masking) |
| `app/services/terminal_command_logger.py` | Buffer management and Enter detection |
| `app/services/terminal_gateway.py` | PTY gateway for command interception |
| `app/models/terminal_command_log.py` | SQLAlchemy model for command logs |
| `app/crud/terminal_command_log.py` | CRUD operations for command logs |
| `app/routes/terminal.py` | API endpoints for command history |
| `app/schemas/terminal.py` | Pydantic schemas for API responses |
### Frontend
| File | Description |
|------|-------------|
| `app/main.js` | Terminal history UI logic |
| `app/index.html` | CSS styles for history panel |
## Configuration
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `TERMINAL_COMMAND_RETENTION_DAYS` | `30` | Days to retain command logs |
| `COMMAND_POLICY_CONFIG` | (none) | Path to custom policy YAML file |
### Example .env
```env
# Terminal command history retention (days)
TERMINAL_COMMAND_RETENTION_DAYS=30
# Custom command policy (optional)
# COMMAND_POLICY_CONFIG=/path/to/policy.yaml
```
## Security Policy
### Blocklist (Commands NEVER Logged)
Commands matching these patterns are **blocked** and never stored:
- Password/secret keywords: `password`, `passwd`, `token`, `apikey`, `secret`
- Docker login with credentials
- `curl`/`wget` with Authorization headers
- Export of sensitive environment variables
- Access to SSH keys (`~/.ssh/id_rsa`, etc.)
- Access to `/etc/shadow`
- MySQL/PostgreSQL with passwords on command line
- Cloud CLI authentication commands
- Kubernetes secrets
- Ansible vault operations
### Allowlist (Commands That ARE Logged)
Only commands matching these patterns are logged:
- Basic commands: `ls`, `cd`, `pwd`, `whoami`, `id`, `uname`
- System info: `df`, `du`, `free`, `uptime`
- Process info: `ps`, `top`, `htop`
- Network info: `ip addr`, `ifconfig`, `netstat`, `ping`
- Systemd: `systemctl status|start|stop|restart`
- Logs: `journalctl`, `tail`, `less`
- Docker: `docker ps|logs|images|inspect|stats`
- Package managers: `apt list`, `dnf list` (read operations)
- Git: `git status|log|diff|branch`
- File operations: `cat`, `grep`, `find` (non-sensitive paths)
### Masking
Even for allowed commands, sensitive values are masked:
- `--password=secret``--password=***`
- `--token=abc123``--token=***`
- `Authorization: Bearer xyz``Authorization: Bearer ***`
## Extending the Policy
### Custom Policy File (YAML)
Create a custom policy file:
```yaml
# /etc/homelab/command_policy.yaml
# Policy mode: 'strict' (allowlist only) or 'permissive' (log everything not blocked)
mode: strict
# Additional blocklist patterns (regex)
blocklist:
- '\bmy-custom-secret\b'
- '\bcompany-token\b'
# Additional allowlist patterns (regex)
allowlist:
- '^my-safe-tool\b'
- '^company-cli\s+(status|info)\b'
# Additional masking patterns (regex, replacement)
mask:
- ['(--company-key[=\s]+)\S+', '\1***']
```
Then set the environment variable:
```env
COMMAND_POLICY_CONFIG=/etc/homelab/command_policy.yaml
```
### Adding Patterns Programmatically
```python
from app.security.command_policy import get_command_policy
policy = get_command_policy()
# Add to blocklist
policy.blocklist_patterns.append(re.compile(r'\bmy-secret\b', re.IGNORECASE))
# Add to allowlist
policy.allowlist_patterns.append(re.compile(r'^my-safe-command\b', re.IGNORECASE))
```
## API Endpoints
### Get Command History for Host
```http
GET /api/terminal/{host_id}/command-history?query=&limit=50&offset=0
Authorization: Bearer <token>
```
Response:
```json
{
"commands": [
{
"id": 1,
"command": "systemctl status nginx",
"created_at": "2024-12-18T10:30:00Z",
"host_name": "web-server-1",
"username": "admin"
}
],
"total": 42,
"host_id": "host-123",
"query": null
}
```
### Get Unique Commands for Host
```http
GET /api/terminal/{host_id}/command-history/unique?query=&limit=50
Authorization: Bearer <token>
```
Response:
```json
{
"commands": [
{
"command": "docker ps",
"command_hash": "abc123...",
"last_used": "2024-12-18T10:30:00Z",
"execution_count": 15
}
],
"total": 10,
"host_id": "host-123"
}
```
### Get Global Command History
```http
GET /api/terminal/command-history?query=&host_id=&limit=50&offset=0
Authorization: Bearer <token>
```
### Clear Host Command History (Admin Only)
```http
DELETE /api/terminal/{host_id}/command-history
Authorization: Bearer <token>
```
### Purge Old Command History (Admin Only)
```http
POST /api/terminal/command-history/purge?days=30
Authorization: Bearer <token>
```
## Database Schema
### Table: `terminal_command_logs`
| Column | Type | Description |
|--------|------|-------------|
| `id` | INTEGER | Primary key |
| `created_at` | DATETIME | Timestamp (UTC) |
| `host_id` | VARCHAR | Foreign key to hosts |
| `user_id` | VARCHAR | Foreign key to users (nullable) |
| `terminal_session_id` | VARCHAR(64) | Session reference |
| `command` | TEXT | Masked command text |
| `command_hash` | VARCHAR(64) | SHA-256 hash for dedup |
| `source` | VARCHAR(20) | Always "terminal" |
| `is_blocked` | BOOLEAN | Whether command was blocked |
| `blocked_reason` | VARCHAR(255) | Reason for blocking |
| `username` | VARCHAR(100) | User display name |
| `host_name` | VARCHAR(100) | Host display name |
### Indexes
- `ix_terminal_cmd_host_created` (host_id, created_at)
- `ix_terminal_cmd_user_created` (user_id, created_at)
- `ix_terminal_cmd_host_hash` (host_id, command_hash)
## Frontend Usage
### Opening Command History
1. Open a terminal session for a host
2. Click the **History** button (clock icon) in the terminal header
3. Search for commands or browse recent ones
4. Click a command to copy it to clipboard
5. Paste into terminal with `Ctrl+Shift+V`
### Search Features
- **Instant filtering**: Type to filter commands
- **All hosts toggle**: Search across all hosts or just current
- **Execution count**: See how often a command was used
- **Relative timestamps**: "Il y a 5min", "Il y a 2h", etc.
## Limitations
1. **No real-time interception**: Currently, commands are not captured in real-time from the terminal. This would require a WebSocket proxy enhancement.
2. **Buffer reconstruction**: Complex terminal interactions (cursor movement, tab completion) may not be perfectly reconstructed.
3. **Platform dependency**: Full PTY interception requires Unix-like systems. Windows has limited support.
4. **ttyd integration**: The current implementation works alongside ttyd but doesn't intercept its WebSocket traffic directly.
## Future Enhancements
1. **WebSocket proxy**: Intercept ttyd WebSocket to capture commands in real-time
2. **Command favorites**: Allow users to star frequently used commands
3. **Command aliases**: Create shortcuts for common commands
4. **Export history**: Download command history as CSV/JSON
5. **Audit log integration**: Link to broader audit logging system
## Troubleshooting
### Commands Not Being Logged
1. Check if the command matches the allowlist patterns
2. Verify the command isn't being blocked by the blocklist
3. Check the application logs for policy decisions
### History Panel Empty
1. Ensure the host has had terminal sessions
2. Check database connectivity
3. Verify the retention policy hasn't purged old logs
### Performance Issues
1. Increase `TERMINAL_COMMAND_RETENTION_DAYS` carefully
2. Monitor database size
3. Run periodic purge jobs
## Running Tests
```bash
# Run command policy tests
pytest tests/backend/test_command_policy.py -v
# Run buffer/logger tests
pytest tests/backend/test_terminal_command_logger.py -v
# Run API endpoint tests
pytest tests/backend/test_terminal_command_history.py -v
```
## Migration
Apply the database migration:
```bash
alembic upgrade head
```
This creates the `terminal_command_logs` table with all required indexes.