Security Best Practices#
Comprehensive guide for securing your embapi production deployment.
Security Overview#
embapi handles sensitive data including embeddings, metadata, and API credentials. This guide covers essential security measures for production deployments.
HTTPS/TLS Configuration#
Why HTTPS is Required#
- Encryption in transit: Protects API keys and data from interception
- Authentication: Verifies server identity
- Compliance: Required by most security standards
Using a Reverse Proxy#
Never expose embapi directly to the internet. Always use a reverse proxy with TLS termination.
Nginx with Let’s Encrypt#
Install Certbot:
sudo apt install nginx certbot python3-certbot-nginxConfigure nginx (/etc/nginx/sites-available/embapi):
upstream embapi_backend {
server 127.0.0.1:8880;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
# SSL certificates (managed by Certbot)
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Proxy settings
location / {
proxy_pass http://embapi_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffer settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# Rate limiting (optional, see below)
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
}Enable site and get certificate:
sudo ln -s /etc/nginx/sites-available/embapi /etc/nginx/sites-enabled/
sudo certbot --nginx -d api.example.com
sudo systemctl reload nginxAuto-renewal:
# Certbot creates a systemd timer automatically
sudo systemctl status certbot.timer
# Test renewal
sudo certbot renew --dry-runTraefik (Docker)#
docker-compose.yml:
version: '3.8'
services:
traefik:
image: traefik:v2.10
command:
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
embapi:
build: .
labels:
- "traefik.enable=true"
- "traefik.http.routers.embapi.rule=Host(`api.example.com`)"
- "traefik.http.routers.embapi.entrypoints=websecure"
- "traefik.http.routers.embapi.tls.certresolver=letsencrypt"
# Redirect HTTP to HTTPS
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.embapi-http.rule=Host(`api.example.com`)"
- "traefik.http.routers.embapi-http.entrypoints=web"
- "traefik.http.routers.embapi-http.middlewares=redirect-to-https"Caddy (Automatic HTTPS)#
Caddyfile:
api.example.com {
reverse_proxy localhost:8880
# Automatic HTTPS via Let's Encrypt
tls admin@example.com
# Security headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options "DENY"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
}
}API Key Management#
Admin Key Security#
The SERVICE_ADMINKEY has full control over the system.
Best practices:
Generate securely:
openssl rand -base64 32Store securely:
- Password manager (1Password, Bitwarden)
- Secrets manager (Vault, AWS Secrets Manager)
- Never in version control
- Never in logs or error messages
Rotate regularly:
- Every 90 days minimum
- After team member departure
- After suspected compromise
Audit usage:
- Log all admin operations
- Monitor for unusual activity
- Review regularly
User API Keys#
User API keys are automatically generated and encrypted before database storage.
Security features:
- One-time display: Keys shown only at creation
- Encrypted storage: AES-256-GCM encryption
- Non-recoverable: Lost keys cannot be retrieved
For users:
Secure storage:
- Use environment variables
- Never hardcode in applications
- Never commit to repositories
Use HTTPS:
- Always transmit over TLS
- Validate server certificates
Rotate if compromised:
- Delete old user and create new one
- Update all client applications
Bearer Token Authentication#
All API requests require authentication:
curl -X GET https://api.example.com/v1/projects/alice \
-H "Authorization: Bearer your-api-key-here"Security:
- Use
Authorizationheader (not query parameters) - Never log full API keys
- Validate on every request
- Use HTTPS to prevent interception
Encryption Key Security#
The ENCRYPTION_KEY protects all user API keys in the database.
Critical Security Points#
⚠️ CRITICAL: If the encryption key is lost or changed, all user API keys become permanently unrecoverable.
Best practices:
Generate securely:
openssl rand -hex 32Backup separately:
- Store in multiple secure locations
- Separate from database backups
- Document recovery procedure
- Test recovery process
Never rotate in production:
- Cannot be changed without re-encrypting all keys
- Requires database migration
- Risk of data loss
Protect at rest:
- Encrypt .env files:
gpg --encrypt .env - Use secrets management (Vault, AWS Secrets Manager)
- Restrict file permissions:
chmod 600 .env
- Encrypt .env files:
Protect in transit:
- Never send over unencrypted channels
- Use secure channels for team sharing
- Avoid email/chat
Encryption Details#
- Algorithm: AES-256-GCM
- Key derivation: SHA-256 hash of input key
- Nonce: Unique per encryption
- Authentication: GCM provides authentication
- Storage format: Base64-encoded ciphertext
Disaster Recovery#
Document and test recovery procedure:
## Encryption Key Recovery Procedure
1. Retrieve backup encryption key from [location]
2. Verify key integrity: [checksum/hash]
3. Update deployment configuration
4. Restart services
5. Verify user authentication works
6. Document incidentDatabase Security#
Network Security#
Restrict access:
-- In pg_hba.conf # Allow only from application server host embapi embapi_user 10.0.1.0/24 md5Firewall rules:
# UFW example sudo ufw allow from 10.0.1.0/24 to any port 5432 sudo ufw deny 5432Use VPC/private network:
- Keep database on private network
- No public internet exposure
- VPN for remote administration
Database Authentication#
Strong passwords:
# Generate secure password openssl rand -base64 32Dedicated user:
-- Not superuser CREATE USER embapi_user WITH PASSWORD 'secure_password'; GRANT ALL PRIVILEGES ON DATABASE embapi TO embapi_user;SSL/TLS connections:
# postgresql.conf ssl = on ssl_cert_file = 'server.crt' ssl_key_file = 'server.key' ssl_ca_file = 'ca.crt'
Database Encryption#
Encryption at rest:
- Use encrypted filesystems (LUKS, dm-crypt)
- Cloud provider encryption (AWS RDS encryption)
- Transparent Data Encryption (TDE)
Backup encryption:
# Encrypt backup pg_dump -U postgres embapi | gzip | \ gpg --encrypt --recipient admin@example.com > backup.sql.gz.gpg
Database Auditing#
Enable audit logging:
-- Install pgaudit
CREATE EXTENSION pgaudit;
-- Configure logging
ALTER SYSTEM SET pgaudit.log = 'write, ddl';
ALTER SYSTEM SET pgaudit.log_catalog = off;
ALTER SYSTEM SET pgaudit.log_parameter = on;
-- Reload config
SELECT pg_reload_conf();Network Security#
Firewall Configuration#
# UFW example
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH
sudo ufw allow 22/tcp
# Allow HTTP/HTTPS (reverse proxy)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Application port (if not behind proxy)
# sudo ufw allow from trusted_network to any port 8880
# Database (from app server only)
sudo ufw allow from 10.0.1.0/24 to any port 5432
sudo ufw enableNetwork Segmentation#
Deploy in isolated networks:
Internet
↓
[Reverse Proxy] ← (DMZ/Public subnet)
↓
[embapi] ← (Application subnet)
↓
[PostgreSQL] ← (Database subnet)Rate Limiting#
Protect against abuse and DoS attacks.
Nginx:
# Define rate limit zone
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=api_burst:10m rate=100r/s;
}
server {
location /v1/ {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://embapi_backend;
}
}Application-level (future enhancement):
- Implement token bucket or leaky bucket
- Per-user rate limits
- Different limits for different endpoints
Backup Strategies#
Database Backups#
Automated daily backups:
#!/bin/bash # /usr/local/bin/backup-embapi.sh DATE=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="/backups/embapi" # Create backup pg_dump -U embapi_user embapi | gzip > \ "$BACKUP_DIR/db-$DATE.sql.gz" # Encrypt backup gpg --encrypt --recipient admin@example.com \ "$BACKUP_DIR/db-$DATE.sql.gz" # Remove unencrypted backup rm "$BACKUP_DIR/db-$DATE.sql.gz" # Verify backup gpg --decrypt "$BACKUP_DIR/db-$DATE.sql.gz.gpg" | gunzip | head -n 5 # Upload to offsite storage aws s3 cp "$BACKUP_DIR/db-$DATE.sql.gz.gpg" \ s3://backups/embapi/ --storage-class GLACIER # Cleanup old local backups (keep 7 days) find $BACKUP_DIR -name "db-*.sql.gz.gpg" -mtime +7 -deleteCron schedule:
0 2 * * * /usr/local/bin/backup-embapi.shTest restores regularly:
# Monthly restore test 0 3 1 * * /usr/local/bin/test-restore.sh
Configuration Backups#
# Backup environment configuration
cp .env .env.backup
gpg --encrypt --recipient admin@example.com .env.backup
# Backup with timestamp
tar czf config-$(date +%Y%m%d).tar.gz .env docker-compose.yml
gpg --encrypt --recipient admin@example.com config-*.tar.gzBackup Retention Policy#
- Daily backups: Keep 7 days locally
- Weekly backups: Keep 4 weeks offsite
- Monthly backups: Keep 12 months in cold storage
- Yearly backups: Keep 7 years (compliance dependent)
Offsite Backups#
Always maintain offsite backups:
- Cloud storage: AWS S3, Azure Blob Storage, GCP Cloud Storage
- Different geographic region
- Encrypted before upload
- Test restoration procedures
Monitoring and Alerting#
What to Monitor#
Authentication failures:
# Parse logs for 401 responses grep "401" /var/log/nginx/access.log | wc -lUnusual API usage:
- Spike in requests
- Requests to non-existent endpoints
- Large data transfers
Database health:
- Connection count
- Query performance
- Disk usage
- Replication lag (if applicable)
System resources:
- CPU usage
- Memory usage
- Disk I/O
- Network throughput
Log Management#
Centralized logging:
- ELK Stack (Elasticsearch, Logstash, Kibana)
- Splunk
- Graylog
- CloudWatch Logs (AWS)
Log retention:
- Application logs: 30 days minimum
- Access logs: 90 days minimum
- Audit logs: 1 year minimum (compliance dependent)
Log security:
- Never log API keys in full (mask:
api_key=abc...xyz) - Never log passwords
- Encrypt archived logs
- Restrict access to logs
- Never log API keys in full (mask:
Alerting#
Set up alerts for:
- Failed authentication attempts (>10/minute)
- Database connection failures
- Disk space >80% full
- Service downtime
- Unusual network traffic
- SSL certificate expiration (30 days before)
Security Checklist#
Use this checklist for production deployments:
Pre-Deployment#
- Strong, random
SERVICE_ADMINKEYgenerated - Strong, random
ENCRYPTION_KEYgenerated (32+ chars) - Strong database password set
-
.envfile encrypted or in secrets manager -
.envnot in version control - Encryption key backed up separately from database
Network & Access#
- HTTPS/TLS configured with valid certificate
- Reverse proxy deployed (nginx/Traefik/Caddy)
- Firewall configured and enabled
- Database on private network only
- Rate limiting configured
- HSTS header enabled
- Security headers configured
Database#
- PostgreSQL 11+ with pgvector installed
- Dedicated database user (not superuser)
- Strong database password
- SSL/TLS for database connections
- Database firewall rules configured
- pg_hba.conf restricts access by IP
- Regular backups configured
- Backup encryption enabled
- Restore procedure tested
Application#
- Service runs as non-root user
- Debug logging disabled (
SERVICE_DEBUG=false) - Resource limits configured
- Health checks enabled
- Logging configured
- Monitoring configured
- Alerting configured
Operations#
- Backup procedure documented
- Restore procedure documented and tested
- Disaster recovery plan created
- Security incident response plan created
- Key rotation schedule defined
- Access control documented (who has admin key)
- Regular security updates scheduled
Compliance#
- Data retention policy defined
- Privacy policy reviewed
- Terms of service reviewed
- GDPR compliance (if applicable)
- Data processing agreements in place
- Audit logging enabled
Incident Response#
Suspected API Key Compromise#
Immediate actions:
- Identify compromised user
- Delete user (invalidates API key)
- Create new user with new API key
- Review access logs for unauthorized access
Investigation:
- Determine scope of compromise
- Check for data exfiltration
- Document incident
Notification:
- Notify affected parties (if required)
- Document lessons learned
- Update security procedures
Database Breach#
Immediate actions:
- Isolate database server
- Revoke network access
- Change all database credentials
- Activate incident response team
Assessment:
- Determine data accessed
- Check encryption effectiveness
- Review audit logs
Recovery:
- Restore from clean backup
- Apply security patches
- Update firewall rules
- Notify affected parties (if required)
- Document and learn
Encryption Key Loss#
⚠️ CRITICAL SITUATION: If encryption key is lost, all user API keys are unrecoverable.
Recovery attempt:
- Check all backup locations
- Review documentation
- Contact all team members
If unrecoverable:
- All users must be deleted and recreated
- New API keys issued to all users
- All client applications must be updated
- Communicate timeline to users
Prevention:
- Review backup procedures
- Add redundant backup locations
- Document key locations
- Test recovery process
Security Updates#
Regular Updates#
- Weekly: Review security advisories
- Monthly: Apply security patches
- Quarterly: Security audit and penetration testing
- Yearly: Full security review and compliance audit
Update Procedure#
- Test in staging environment
- Backup production database and configuration
- Schedule maintenance window
- Apply updates
- Verify functionality
- Monitor for issues
Subscribe to Security Advisories#
- PostgreSQL security announcements
- pgvector security updates
- Docker security advisories
- Go security advisories
- Operating system security updates