248 lines
6.4 KiB
Markdown
248 lines
6.4 KiB
Markdown
# Security Model & Best Practices
|
|
|
|
## Credential Management
|
|
|
|
### CRITICAL: Never Store Plaintext Passwords
|
|
|
|
All SSH credentials must be handled securely:
|
|
|
|
#### Option 1: Runtime Credentials (Recommended)
|
|
User provides SSH credentials at push time:
|
|
```typescript
|
|
// Frontend
|
|
const response = await fetch('/api/v1/configurations/123/push', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
configuration_id: 123,
|
|
device_id: 456,
|
|
ssh_username: 'admin',
|
|
ssh_password: 'SecurePass123', // ONLY sent at push time
|
|
confirmed: true,
|
|
dry_run: false
|
|
})
|
|
})
|
|
```
|
|
|
|
Backend receives credentials in request, uses immediately, discards after execution.
|
|
|
|
#### Option 2: Encrypted Storage (Advanced)
|
|
If storing credentials (not recommended without vault):
|
|
```python
|
|
# Backend
|
|
from app.core.security import encrypt_credential, decrypt_credential
|
|
|
|
# Encrypt before storing
|
|
encrypted = encrypt_credential("plaintext_password")
|
|
device.ssh_password_encrypted = encrypted
|
|
|
|
# Decrypt only when pushing
|
|
plaintext = decrypt_credential(device.ssh_password_encrypted)
|
|
executor = SSHExecutor(..., password=plaintext)
|
|
# Use immediately
|
|
plaintext = None # Clear from memory
|
|
```
|
|
|
|
⚠️ **Never** use hardcoded encryption keys. Use:
|
|
- AWS KMS
|
|
- HashiCorp Vault
|
|
- Azure Key Vault
|
|
- Environment variables (CI/CD only)
|
|
|
|
## Access Control
|
|
|
|
### User Isolation
|
|
- Each user owns their projects
|
|
- Projects contain devices & configs
|
|
- API enforces `project.owner_id == current_user.id`
|
|
|
|
```python
|
|
# Backend example
|
|
def get_project(project_id: int, current_user: User):
|
|
project = db.query(Project).filter(Project.id == project_id).first()
|
|
if project.owner_id != current_user.id:
|
|
raise HTTPException(status_code=403, detail="Forbidden")
|
|
return project
|
|
```
|
|
|
|
### Token-Based Authentication
|
|
- JWT tokens expire after 30 minutes
|
|
- Refresh token pattern (future: V1)
|
|
- Token contains only user email (no sensitive data)
|
|
|
|
```python
|
|
# core/security.py
|
|
def create_access_token(data: dict):
|
|
to_encode = data.copy()
|
|
expire = datetime.utcnow() + timedelta(minutes=30)
|
|
to_encode.update({"exp": expire})
|
|
|
|
encoded_jwt = jwt.encode(
|
|
to_encode,
|
|
settings.secret_key,
|
|
algorithm=settings.algorithm
|
|
)
|
|
return encoded_jwt
|
|
```
|
|
|
|
## SSH Security
|
|
|
|
### Pre-Push Validation
|
|
1. ✅ Dry-run mode (generate only, no execution)
|
|
2. ✅ CLI preview (user sees exact commands)
|
|
3. ✅ Confirmation modal (explicit yes/no)
|
|
4. ✅ Backup running-config before push
|
|
|
|
### Connection Security
|
|
- SSH only (no Telnet)
|
|
- Device authentication via username/password or SSH keys (future)
|
|
- 30-second timeout (prevent hanging)
|
|
- Error handling without exposing internals
|
|
|
|
```python
|
|
# ssh/__init__.py
|
|
def connect(self):
|
|
try:
|
|
device = {
|
|
"device_type": "cisco_ios",
|
|
"host": self.ip_address,
|
|
"username": self.username,
|
|
"password": self.password,
|
|
"port": self.ssh_port,
|
|
"timeout": self.timeout, # 30s timeout
|
|
}
|
|
self.connection = ConnectHandler(**device)
|
|
except NetmikoAuthenticationException as e:
|
|
raise SSHConnectionError(f"Auth failed") # No password leak
|
|
except NetmikoTimeoutException as e:
|
|
raise SSHConnectionError(f"Timeout") # Generic error
|
|
```
|
|
|
|
### Audit Logging
|
|
|
|
All SSH operations logged to `push_logs` table:
|
|
```sql
|
|
-- What was pushed
|
|
- device_id
|
|
- configuration_id
|
|
- commands_sent (plain text)
|
|
- device_output (verbatim)
|
|
|
|
-- When & by whom
|
|
- created_at
|
|
- pushed_by (username)
|
|
|
|
-- Safety
|
|
- pre_push_backup (running-config before)
|
|
- was_rolled_back (if reverted)
|
|
- rollback_reason
|
|
```
|
|
|
|
Example query to review push history:
|
|
```sql
|
|
SELECT id, device_id, created_at, pushed_by, status
|
|
FROM push_logs
|
|
WHERE device_id = 456
|
|
ORDER BY created_at DESC
|
|
LIMIT 10;
|
|
```
|
|
|
|
## Network Security
|
|
|
|
### HTTPS in Production
|
|
```python
|
|
# main.py production config
|
|
if not settings.debug:
|
|
# Redirect HTTP to HTTPS
|
|
app.add_middleware(HTTPSRedirectMiddleware)
|
|
# Add HSTS header
|
|
app.add_middleware(HSTSMiddleware, max_age=31536000)
|
|
```
|
|
|
|
### CORS Configuration
|
|
```python
|
|
# main.py
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["https://your-domain.com"], # NOT *
|
|
allow_credentials=True,
|
|
allow_methods=["GET", "POST", "PUT", "DELETE"],
|
|
allow_headers=["Authorization", "Content-Type"],
|
|
)
|
|
```
|
|
|
|
### API Rate Limiting (TODO: V1)
|
|
```python
|
|
# Prevent brute force / DoS
|
|
# 100 requests per minute per IP
|
|
# 1000 requests per hour per user
|
|
```
|
|
|
|
## Database Security
|
|
|
|
### SQL Injection Prevention
|
|
Use SQLAlchemy ORM (parameterized queries):
|
|
```python
|
|
# ✅ Safe - parameterized
|
|
user = db.query(User).filter(User.email == user_email).first()
|
|
|
|
# ❌ NEVER - raw SQL with string formatting
|
|
user = db.execute(f"SELECT * FROM users WHERE email = '{user_email}'")
|
|
```
|
|
|
|
### Sensitive Data
|
|
- Passwords: BCrypt hashed (never plaintext)
|
|
- SSH credentials: Encrypted or runtime-only
|
|
- API keys: Environment variables (never in code)
|
|
|
|
## Disclaimer & User Awareness
|
|
|
|
### In UI
|
|
```html
|
|
<!-- Frontend -->
|
|
<div class="security-disclaimer">
|
|
<p>⚠️ <strong>WARNING</strong></p>
|
|
<p>This tool executes commands on live network devices.</p>
|
|
<p>Review all configurations before pushing.</p>
|
|
<p>Test in lab environment first.</p>
|
|
<p>Backup devices before any changes.</p>
|
|
<p>All operations are logged for audit.</p>
|
|
</div>
|
|
```
|
|
|
|
### In API
|
|
```python
|
|
# main.py
|
|
@app.get("/api/v1/docs")
|
|
def docs():
|
|
"""
|
|
Include security warning in OpenAPI docs
|
|
"""
|
|
return {
|
|
"warning": "This API modifies network device configurations via SSH. Use with caution.",
|
|
"docs": "...",
|
|
}
|
|
```
|
|
|
|
## Deployment Checklist
|
|
|
|
- [ ] **Secrets**: Use environment variables (AWS Secrets Manager, etc.)
|
|
- [ ] **SSL/TLS**: Enforce HTTPS only
|
|
- [ ] **CORS**: Lock down to your domain
|
|
- [ ] **Database**: PostgreSQL with strong credentials, encrypted connection
|
|
- [ ] **SSH Keys**: Support SSH key auth (not just passwords)
|
|
- [ ] **Logging**: Centralized logging (ELK, Datadog, etc.)
|
|
- [ ] **Monitoring**: Alert on failed pushes, timeout errors
|
|
- [ ] **Backup**: Database backups daily
|
|
- [ ] **Compliance**: Log retention policy (min 90 days)
|
|
- [ ] **Testing**: Penetration test before launch
|
|
|
|
## Future: V1 Features
|
|
|
|
- [ ] SSH key authentication (instead of passwords)
|
|
- [ ] OAuth 2.0 / SSO integration
|
|
- [ ] Role-based access control (RBAC)
|
|
- [ ] Encryption at rest (database)
|
|
- [ ] Multi-factor authentication (MFA)
|
|
- [ ] Webhook integration for audit events
|
|
- [ ] Compliance reporting (PCI-DSS, SOC2)
|