This document outlines security best practices for deploying and managing the Liberu Control Panel.
- SSH Security
- Kubernetes Security
- Application Security
- Network Security
- Data Protection
- Monitoring and Auditing
Use SSH Keys (Not Passwords)
SSH keys provide better security than passwords:
# Generate strong SSH key
ssh-keygen -t ed25519 -C "controlpanel@example.com"
# Or 4096-bit RSA
ssh-keygen -t rsa -b 4096 -C "controlpanel@example.com"Protect Private Keys
- Always add a passphrase to private keys
- Store keys with restrictive permissions:
chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub
- Never share private keys
- Rotate keys every 90 days
Configure /etc/ssh/sshd_config on remote servers:
# Disable password authentication
PasswordAuthentication no
ChallengeResponseAuthentication no
# Disable root login
PermitRootLogin no
# Use strong ciphers and MACs
Ciphers aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-256,hmac-sha2-512
KexAlgorithms curve25519-sha256,diffie-hellman-group-exchange-sha256
# Limit authentication attempts
MaxAuthTries 3
MaxSessions 5
# Enable strict mode
StrictModes yes
# Set login grace time
LoginGraceTime 60
# Change default port (optional)
Port 2222
Restart SSH after changes:
sudo systemctl restart sshdCreate a dedicated user with minimal permissions:
# Create user
sudo useradd -m -s /bin/bash controlpanel
# Limited sudo (only necessary commands)
echo "controlpanel ALL=(ALL) NOPASSWD: /usr/local/bin/kubectl" | sudo tee /etc/sudoers.d/controlpanelConfigure in .env:
# Use connection pooling to reduce overhead
SSH_CONNECTION_POOL_SIZE=10
# Set reasonable timeouts
SSH_TIMEOUT=30
SSH_RETRY_ATTEMPTS=3
# Enable only secure ciphers
SSH_ALLOWED_CIPHERS=aes256-ctr,aes192-ctr,aes128-ctr
SSH_ALLOWED_MACS=hmac-sha2-256,hmac-sha2-512Always enable RBAC for all namespaces. The control panel automatically creates:
- ServiceAccount - Identity for pods
- Role - Limited permissions within namespace
- RoleBinding - Binds ServiceAccount to Role
Example role with minimal permissions:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: hosting-user-role
namespace: hosting-example-com
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list"]Enable NetworkPolicies to isolate traffic:
KUBERNETES_ENABLE_NETWORK_POLICIES=trueDefault policies applied:
- Deny all ingress by default
- Allow ingress from NGINX controller
- Allow internal namespace communication
- Allow DNS egress
Run as non-root:
KUBERNETES_RUN_AS_NON_ROOT=trueThe control panel sets:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000Drop capabilities:
securityContext:
capabilities:
drop:
- ALLRead-only root filesystem (optional):
KUBERNETES_READONLY_ROOTFS=trueAlways set resource limits to prevent:
- Resource exhaustion attacks
- Noisy neighbor problems
- Cluster instability
Default limits:
'default_resources' => [
'requests' => [
'memory' => '128Mi',
'cpu' => '100m',
],
'limits' => [
'memory' => '512Mi',
'cpu' => '500m',
],
],Never store secrets in plain text. The control panel:
- Encrypts credentials with Laravel Crypt
- Stores in Kubernetes Secrets
- Mounts as environment variables or volumes
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: example-com-credentials
key: db_passwordUse trusted images:
'images' => [
'nginx' => 'nginx:alpine', // Official images
'php_fpm' => 'php:8.2-fpm-alpine',
'mysql' => 'mysql:8.0',
],Scan images for vulnerabilities:
# Using Trivy
trivy image nginx:alpineUse specific tags (not latest):
image: nginx:1.25-alpine # ✓ Good
image: nginx:latest # ✗ BadStrong APP_KEY:
php artisan key:generateEnvironment file:
# Protect .env file
chmod 600 .env
chown www-data:www-data .envDatabase credentials:
# Use strong passwords
DB_PASSWORD=<generate-with-pwgen-or-1password>HTTPS only:
# Force HTTPS in production
APP_ENV=production
APP_DEBUG=false
SESSION_SECURE_COOKIE=trueEnable 2FA for all users:
- Install Filament 2FA package
- Require 2FA for admin users
- Set session timeout
SESSION_LIFETIME=120 # 2 hoursAlways validate and sanitize user input:
// Use Laravel validation
$validated = $request->validate([
'domain_name' => 'required|string|max:255|regex:/^[a-z0-9.-]+$/',
'email' => 'required|email',
]);Use Eloquent ORM or prepared statements:
// ✓ Good - Uses parameter binding
Domain::where('domain_name', $name)->first();
// ✗ Bad - SQL injection risk
DB::select("SELECT * FROM domains WHERE domain_name = '$name'");Laravel escapes output by default:
{{-- Escaped (safe) --}}
{{ $domain->name }}
{{-- Unescaped (dangerous) --}}
{!! $html !!} {{-- Only use with trusted data --}}Laravel includes CSRF protection:
<form method="POST">
@csrf
...
</form>Control panel server:
# Allow HTTP/HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Allow SSH (change port if needed)
sudo ufw allow 22/tcp
# Enable firewall
sudo ufw enableKubernetes nodes:
# Allow K8s API
sudo ufw allow 6443/tcp
# Allow kubelet
sudo ufw allow 10250/tcp
# Allow NodePort range (if needed)
sudo ufw allow 30000:32767/tcpAlways use HTTPS:
- Enable cert-manager
- Use Let's Encrypt
- Auto-renew certificates
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prodTLS version:
# Ingress annotation for TLS 1.2+
nginx.ingress.kubernetes.io/ssl-protocols: "TLSv1.2 TLSv1.3"
Rate limiting:
metadata:
annotations:
nginx.ingress.kubernetes.io/limit-rps: "100"
nginx.ingress.kubernetes.io/limit-connections: "10"Use CloudFlare or similar CDN for DDoS protection.
Encryption at rest:
# Use encrypted storage volumes
KUBERNETES_STORAGE_CLASS=encrypted-ssdBackup strategy:
- Daily automated backups
- Store in different location
- Encrypt backups
- Test restore procedure
# Backup MySQL
kubectl exec -n hosting-example-com example-com-db-0 -- \
mysqldump -u root -p$MYSQL_ROOT_PASSWORD dbname > backup.sql
# Encrypt backup
gpg --encrypt --recipient admin@example.com backup.sqlEncryption:
All credentials are encrypted:
// Automatic encryption in model
public function setPasswordAttribute($value)
{
$this->attributes['password'] = Crypt::encryptString($value);
}Key rotation:
Rotate encryption keys regularly:
# Generate new key
php artisan key:generate --show
# Update APP_KEY in .env
# Re-encrypt existing data if neededLog all security-relevant events:
Log::info("User {$user->id} deployed domain {$domain->domain_name}");
Log::warning("Failed SSH connection attempt to {$server->hostname}");Monitor logs:
# Application logs
tail -f storage/logs/laravel.log
# SSH access logs
sudo tail -f /var/log/auth.log
# Kubernetes events
kubectl get events --all-namespaces --watchSet up alerts:
- Failed login attempts
- Failed SSH connections
- Unusual resource usage
- Certificate expiration
Scan regularly:
# Scan dependencies
composer audit
# Scan Docker images
trivy image php:8.2-fpm-alpine
# Scan Kubernetes
kube-benchRegular security audits:
- Review user permissions quarterly
- Rotate credentials every 90 days
- Update dependencies monthly
- Penetration testing annually
Keep records:
- Who accessed what
- When deployments occurred
- Changes to security policies
- Change default passwords
- Enable 2FA for all admin users
- Configure SSH with keys only
- Set up firewall rules
- Enable HTTPS with valid certificates
- Configure automated backups
- Set resource limits
- Enable RBAC and NetworkPolicies
- Update dependencies (monthly)
- Rotate SSH keys (quarterly)
- Review access logs (weekly)
- Test backup restoration (quarterly)
- Scan for vulnerabilities (weekly)
- Review user permissions (quarterly)
- Update SSL certificates (automatic with cert-manager)
If a security incident occurs:
- Isolate affected systems
- Document what happened
- Investigate root cause
- Remediate vulnerabilities
- Notify affected users if required
- Review and improve processes
If you discover a security vulnerability:
- DO NOT open a public GitHub issue
- Email security@liberu.co.uk
- Provide details and steps to reproduce
- Allow time for patch before public disclosure
We take security seriously and will respond promptly to reports.