6.4 KiB
6.4 KiB
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:
// 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):
# 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
# 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)
# 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
- ✅ Dry-run mode (generate only, no execution)
- ✅ CLI preview (user sees exact commands)
- ✅ Confirmation modal (explicit yes/no)
- ✅ 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
# 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:
-- 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:
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
# 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
# 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)
# 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):
# ✅ 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
<!-- 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
# 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)