Covers 6 options for storing passwords securely: 1. .env file (current, good for personal use) 2. Environment variables (better for production) 3. Separate secrets file 4. Docker secrets 5. HashiCorp Vault (enterprise) 6. Git secrets (CI/CD only) Recommendation: Current .env setup is fine for personal/research use Improvement: chmod 600 .env (done) Includes security checklist, rotation procedures, and testing
8.3 KiB
Secrets Management Guide
Overview
POTE needs sensitive information like database passwords and SMTP credentials. This guide covers secure storage options.
Option 1: .env File (Current Default)
Good for: Personal use, single server, local development
Setup
# Create .env file
cp .env.example .env
nano .env # Add secrets
# Secure permissions
chmod 600 .env
chown poteapp:poteapp .env
✅ Pros
- Simple, works immediately
- No additional setup
- Standard practice for Python projects
⚠️ Cons
- Secrets stored in plain text on disk
- Risk if server is compromised
- No audit trail
🔒 Security Checklist
.envin.gitignore(already done ✅)- File permissions:
chmod 600 .env - Never commit to git
- Backup securely (encrypted)
- Rotate passwords regularly
Option 2: Environment Variables (Better)
Good for: Systemd services, Docker, production
Setup for Systemd Service
Create /etc/systemd/system/pote.service:
[Unit]
Description=POTE Daily Update
After=network.target postgresql.service
[Service]
Type=oneshot
User=poteapp
WorkingDirectory=/home/poteapp/pote
Environment="DATABASE_URL=postgresql://poteuser:PASSWORD@localhost:5432/potedb"
Environment="SMTP_HOST=mail.levkin.ca"
Environment="SMTP_PORT=587"
Environment="SMTP_USER=test@levkin.ca"
Environment="SMTP_PASSWORD=YOUR_PASSWORD"
Environment="FROM_EMAIL=test@levkin.ca"
ExecStart=/home/poteapp/pote/venv/bin/python scripts/automated_daily_run.sh
[Install]
WantedBy=multi-user.target
Secure the service file:
sudo chmod 600 /etc/systemd/system/pote.service
sudo systemctl daemon-reload
✅ Pros
- Secrets not in git or project directory
- Standard Linux practice
- Works with systemd timers
⚠️ Cons
- Still visible in
systemctl show - Requires root to edit
Option 3: Separate Secrets File (Compromise)
Good for: Multiple environments, easier rotation
Setup
Create /etc/pote/secrets (outside project):
sudo mkdir -p /etc/pote
sudo nano /etc/pote/secrets
Content:
export SMTP_PASSWORD="your_password_here"
export DATABASE_PASSWORD="your_db_password_here"
Secure it:
sudo chmod 600 /etc/pote/secrets
sudo chown poteapp:poteapp /etc/pote/secrets
Update scripts to source it:
#!/bin/bash
# Load secrets
if [ -f /etc/pote/secrets ]; then
source /etc/pote/secrets
fi
# Load .env (without passwords)
source .env
# Run POTE
python scripts/send_daily_report.py
✅ Pros
- Secrets separate from code
- Easy to rotate
- Can be backed up separately
⚠️ Cons
- Extra file to manage
- Still plain text
Option 4: Docker Secrets (For Docker Deployments)
Good for: Docker Compose, Docker Swarm
Setup
Create secret files:
echo "your_smtp_password" | docker secret create smtp_password -
echo "your_db_password" | docker secret create db_password -
Update docker-compose.yml:
version: '3.8'
services:
pote:
image: pote:latest
secrets:
- smtp_password
- db_password
environment:
SMTP_HOST: mail.levkin.ca
SMTP_USER: test@levkin.ca
SMTP_PASSWORD_FILE: /run/secrets/smtp_password
DATABASE_PASSWORD_FILE: /run/secrets/db_password
secrets:
smtp_password:
external: true
db_password:
external: true
Update code to read from files:
# In src/pote/config.py
def get_secret(key: str, default: str = "") -> str:
"""Read secret from file or environment."""
file_path = os.getenv(f"{key}_FILE")
if file_path and Path(file_path).exists():
return Path(file_path).read_text().strip()
return os.getenv(key, default)
class Settings(BaseSettings):
smtp_password: str = Field(default_factory=lambda: get_secret("SMTP_PASSWORD"))
✅ Pros
- Docker-native solution
- Encrypted in Swarm mode
- Never in logs
⚠️ Cons
- Requires Docker
- More complex setup
Option 5: HashiCorp Vault (Enterprise)
Good for: Teams, multiple projects, compliance
Setup
- Install Vault server
- Store secrets:
vault kv put secret/pote \
smtp_password="your_password" \
db_password="your_db_password"
- Update POTE to fetch from Vault:
import hvac
client = hvac.Client(url='http://vault:8200', token=os.getenv('VAULT_TOKEN'))
secrets = client.secrets.kv.v2.read_secret_version(path='pote')
smtp_password = secrets['data']['data']['smtp_password']
✅ Pros
- Centralized secrets management
- Audit logs
- Dynamic secrets
- Access control
⚠️ Cons
- Complex setup
- Requires Vault infrastructure
- Overkill for single user
Option 6: Git Secrets (For CI/CD ONLY)
Good for: GitHub Actions, Gitea Actions
Setup in Gitea/GitHub
-
Go to Repository Settings → Secrets
-
Add secrets:
SMTP_PASSWORDDB_PASSWORD
-
Reference in
.github/workflows/ci.yml:
env:
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
DATABASE_URL: postgresql://user:${{ secrets.DB_PASSWORD }}@postgres/db
⚠️ Important
- Only for CI/CD pipelines
- NOT for deployed servers
- Secrets are injected during workflow runs
🎯 Recommendation for Your Setup
Personal/Research Use (Current)
Keep .env file with better security:
# On Proxmox
ssh poteapp@your-proxmox-ip
cd ~/pote
# Secure .env
chmod 600 .env
chown poteapp:poteapp .env
# Verify
ls -la .env
# Should show: -rw------- 1 poteapp poteapp
Backup strategy:
# Encrypted backup of .env
gpg -c .env # Creates .env.gpg
# Store .env.gpg somewhere safe (encrypted USB, password manager)
Production/Team Use
Use environment variables + systemd:
- Remove passwords from
.env - Create systemd service with
Environment=directives - Secure service file:
chmod 600 /etc/systemd/system/pote.service
🔒 General Security Best Practices
✅ DO
- Use strong, unique passwords
- Restrict file permissions (
chmod 600) - Keep
.envin.gitignore - Rotate passwords regularly (every 90 days)
- Use encrypted backups
- Audit who has server access
❌ DON'T
- Commit secrets to git (even private repos)
- Store passwords in code
- Share
.envfiles via email/Slack - Use the same password everywhere
- Leave default passwords
- Store secrets in public cloud storage
🧪 Test Your Security
Check if .env is protected
# Should be in .gitignore
git check-ignore .env # Should output: .env
# Should have restricted permissions
ls -la .env # Should show: -rw------- (600)
# Should not be committed
git log --all --full-history --oneline -- .env # Should be empty
Verify secrets aren't in git history
# Search for passwords in git history
git log --all --full-history --source --pickaxe-all -S 'smtp_password'
# Should find nothing
🔄 Password Rotation Procedure
Every 90 days (or if compromised):
- Generate new password in mailcow
- Update
.env:nano .env # Change SMTP_PASSWORD - Test:
python scripts/send_daily_report.py --test-smtp - No restart needed (scripts read
.envon each run)
📊 Security Level Comparison
| Level | Method | Effort | Protection |
|---|---|---|---|
| 🔓 Basic | .env (default perms) |
None | Low |
| 🔒 Good | .env (chmod 600) |
1 min | Medium |
| 🔒 Better | Environment variables | 10 min | Good |
| 🔒 Better | Separate secrets file | 10 min | Good |
| 🔐 Best | Docker Secrets | 30 min | Very Good |
| 🔐 Best | Vault | 2+ hours | Excellent |
🎯 Your Current Status
✅ Already secure enough for personal use:
.envin.gitignore✅- Not committed to git ✅
- Local server only ✅
⚠️ Recommended improvement (2 minutes):
chmod 600 .env
🔐 Optional (if paranoid):
- Use separate secrets file in
/etc/pote/ - Encrypt backups with GPG
- Set up password rotation schedule
Summary
For your levkin.ca setup:
- Current approach (
.envfile) is fine ✅ - Add
chmod 600 .envfor better security (2 minutes) - Don't commit
.envto git (already protected ✅) - Consider upgrading to environment variables if you deploy to production
Your current setup is appropriate for a personal research project. Don't over-engineer it unless you have specific compliance requirements or a team.