212 lines
5.6 KiB
Markdown
212 lines
5.6 KiB
Markdown
# Security Guide
|
|||
|
|
|
||
|
|
Praktik keamanan untuk infrastruktur `asepharyana-hub`.
|
||
|
|
|
||
|
|
## Ringkasan
|
||
|
|
|
||
|
|
| Area | Status | Prioritas |
|
||
|
|
|------|--------|-----------|
|
||
|
|
| Secrets management | GitHub encrypted secrets | Tinggi |
|
||
|
|
| TLS termination | Traefik + cert volume mounts | Tinggi |
|
||
|
|
| Container security | Non-root user (scraper-api) | Sedang |
|
||
|
|
| Network security | Tailscale overlay, app-shared-net | Sedang |
|
||
|
|
| Access control | SSH key, GitHub permissions | Sedang |
|
||
|
|
| Monitoring | Belum ada alert system | Rendah |
|
||
|
|
| Firewall | UFW/iptables (manual) | Sedang |
|
||
|
|
| Backup | lihat `docs/backup-recovery.md` | Sedang |
|
||
|
|
|
||
|
|
## Secrets Management
|
||
|
|
|
||
|
|
### Yang Tidak Boleh di-Commit
|
||
|
|
|
||
|
|
- [ ] `.env` production (disimpan sebagai GitHub secret `ENV_FILE_PRODUCTION`)
|
||
|
|
- [ ] SSH private keys
|
||
|
|
- [ ] API tokens, JWT secret
|
||
|
|
- [ ] Docker registry tokens
|
||
|
|
- [ ] Database passwords
|
||
|
|
- [ ] TLS certificate private keys
|
||
|
|
|
||
|
|
### GitHub Secrets
|
||
|
|
|
||
|
|
Setting di Settings > Secrets and variables > Actions:
|
||
|
|
|
||
|
|
| Secret | Tujuan | Rotasi |
|
||
|
|
|--------|--------|--------|
|
||
|
|
| `SSH_PRIVATE_KEY` | Akses SSH ke VPS | 6 bulan |
|
||
|
|
| `VPS_HOST` | IP VPS | Tidak berubah |
|
||
|
|
| `VPS_USER` | User SSH | Tidak berubah |
|
||
|
|
| `VPS_TARGET_DIR` | Directory di VPS | Tidak berubah |
|
||
|
|
| `ENV_FILE_PRODUCTION` | Full `.env` production | Saat ada perubahan |
|
||
|
|
|
||
|
|
### Update Secrets dengan aman
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Baca current .env dari VPS via SSH
|
||
|
|
ssh root@45.127.35.244 "cat /root/asepharyana-hub/.env" | gh secret set ENV_FILE_PRODUCTION --repo asepharyana/asepharyana-hub --repos
|
||
|
|
```
|
||
|
|
|
||
|
|
### Production `.env` tidak boleh di-commit
|
||
|
|
|
||
|
|
`.env` di root repo adalah untuk development lokal. Production `.env` hanya ada di:
|
||
|
|
1. GitHub secret `ENV_FILE_PRODUCTION`
|
||
|
|
2. File `/root/asepharyana-hub/.env` di VPS (hasil SCP dari CI/CD)
|
||
|
|
|
||
|
|
## TLS / SSL
|
||
|
|
|
||
|
|
### Konfigurasi
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
# Traefik TLS certs dari file mount (bukan auto-ACME)
|
||
|
|
volumes:
|
||
|
|
- ${TRAEFIK_CERT_MY_ID_PEM:-/root/asepharyana.my.id.pem}:/etc/traefik/certs/asepharyana.my.id.pem:ro
|
||
|
|
- ${TRAEFIK_CERT_MY_ID_KEY:-/root/asepharyana.my.id.key}:/etc/traefik/certs/asepharyana.my.id.key:ro
|
||
|
|
```
|
||
|
|
|
||
|
|
### Best Practices
|
||
|
|
|
||
|
|
- Certificates disimpan di host (`/root/`), bukan di repo
|
||
|
|
- Volume mount read-only (`:ro`)
|
||
|
|
- Private key hanya bisa dibaca oleh root (chmod 600)
|
||
|
|
- Renew certificates sebelum expired (monitor expiry)
|
||
|
|
- Dua domain: `asepharyana.my.id` + `asepharyana.web.id`
|
||
|
|
|
||
|
|
## Container Security
|
||
|
|
|
||
|
|
### Non-Root User
|
||
|
|
|
||
|
|
Scraper API berjalan sebagai `appuser` (UID 1001):
|
||
|
|
|
||
|
|
```dockerfile
|
||
|
|
RUN groupadd -g 1001 appgroup && \
|
||
|
|
useradd -u 1001 -g appgroup -s /bin/sh appuser
|
||
|
|
USER appuser
|
||
|
|
```
|
||
|
|
|
||
|
|
Service baru harus mengikuti pattern yang sama.
|
||
|
|
|
||
|
|
### Read-Only Filesystem
|
||
|
|
|
||
|
|
Untuk container yang tidak perlu write ke filesystem:
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
services:
|
||
|
|
app:
|
||
|
|
image: app:latest
|
||
|
|
read_only: true
|
||
|
|
tmpfs:
|
||
|
|
- /tmp
|
||
|
|
```
|
||
|
|
|
||
|
|
### Docker Socket
|
||
|
|
|
||
|
|
Hanya Traefik yang perlu akses ke Docker socket (read-only):
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
volumes:
|
||
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||
|
|
```
|
||
|
|
|
||
|
|
Service lain tidak boleh mount Docker socket.
|
||
|
|
|
||
|
|
### Image Security
|
||
|
|
|
||
|
|
- Build dari base image resmi dan minimal (`debian:bookworm-slim`, `redis:alpine`, `nats:latest`)
|
||
|
|
- Multi-stage build untuk production image (tidak include build tools)
|
||
|
|
- Update base image secara berkala
|
||
|
|
|
||
|
|
## Network Security
|
||
|
|
|
||
|
|
### Firewall (UFW/iptables)
|
||
|
|
|
||
|
|
Di VPS (`orangevps`):
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Hanya buka port yang diperlukan
|
||
|
|
sudo ufw default deny incoming
|
||
|
|
sudo ufw default allow outgoing
|
||
|
|
sudo ufw allow 22/tcp # SSH
|
||
|
|
sudo ufw allow 80/tcp # HTTP redirect
|
||
|
|
sudo ufw allow 443/tcp # HTTPS
|
||
|
|
sudo ufw allow 4222/tcp # NATS (jika perlu external akses)
|
||
|
|
sudo ufw enable
|
||
|
|
```
|
||
|
|
|
||
|
|
Di `imrnes`:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Hanya dari Tailscale interface
|
||
|
|
sudo ufw allow in on tailscale0 to any port 6432 proto tcp # PostgreSQL
|
||
|
|
sudo ufw allow in on tailscale0 to any port 6379 proto tcp # Redis
|
||
|
|
sudo ufw enable
|
||
|
|
```
|
||
|
|
|
||
|
|
### Network Segmentation
|
||
|
|
|
||
|
|
- Semua container di network `app-shared-net` (internal bridge)
|
||
|
|
- Tidak ada port yang di-expose ke host kecuali Traefik (80,443)
|
||
|
|
- Redis hanya accessible via Docker DNS (`redis:6379`) — tidak di-expose
|
||
|
|
- Database hanya via Tailscale — tidak accessible dari public internet
|
||
|
|
|
||
|
|
### SSH Hardening
|
||
|
|
|
||
|
|
Konfigurasi di `/etc/ssh/sshd_config`:
|
||
|
|
|
||
|
|
```
|
||
|
|
Port 22
|
||
|
|
PermitRootLogin prohibit-password
|
||
|
|
PasswordAuthentication no
|
||
|
|
PubkeyAuthentication yes
|
||
|
|
AllowUsers root
|
||
|
|
MaxAuthTries 3
|
||
|
|
ClientAliveInterval 300
|
||
|
|
ClientAliveCountMax 2
|
||
|
|
```
|
||
|
|
|
||
|
|
## Access Control
|
||
|
|
|
||
|
|
### GitHub Repository
|
||
|
|
|
||
|
|
- `contents: write` hanya untuk workflow `update-manifest` dan `update-submodule`
|
||
|
|
- `packages: write` hanya untuk workflow `build`
|
||
|
|
- `security-events: write` hanya untuk workflow `security`
|
||
|
|
- Branch protection di `main`: require PR review, status checks
|
||
|
|
|
||
|
|
### VPS
|
||
|
|
|
||
|
|
- SSH hanya dengan key-based authentication
|
||
|
|
- Key disimpan di GitHub secret, bukan di repo
|
||
|
|
- Rotate SSH key secara berkala (minimal 6 bulan)
|
||
|
|
- Jangan gunakan password login
|
||
|
|
|
||
|
|
## Monitoring Keamanan
|
||
|
|
|
||
|
|
### Saat Ini
|
||
|
|
|
||
|
|
- Traefik access logs (format JSON, buffer size 100)
|
||
|
|
- Docker logs via `docker logs`
|
||
|
|
- CodeQL analysis untuk Rust code (setiap PR + weekly)
|
||
|
|
|
||
|
|
### Rekomendasi
|
||
|
|
|
||
|
|
- [ ] Alert untuk SSH failed login (fail2ban)
|
||
|
|
- [ ] Log monitoring (Loki / Promtail)
|
||
|
|
- [ ] Container vulnerability scanning (Trivy / Snyk)
|
||
|
|
- [ ] Certificate expiry monitoring
|
||
|
|
- [ ] Disk usage alert
|
||
|
|
- [ ] Unauthorized access detection
|
||
|
|
|
||
|
|
## Checklist Security
|
||
|
|
|
||
|
|
- [ ] SSH password authentication disabled
|
||
|
|
- [ ] Root login via SSH key only
|
||
|
|
- [ ] UFW/iptables configured
|
||
|
|
- [ ] Docker socket only mounted where necessary (read-only)
|
||
|
|
- [ ] Container berjalan sebagai non-root user
|
||
|
|
- [ ] `.env` tidak di-commit
|
||
|
|
- [ ] GitHub secrets ter-encrypt
|
||
|
|
- [ ] TLS certificates valid dan belum expired
|
||
|
|
- [ ] CodeQL analysis berjalan
|
||
|
|
- [ ] Backup database berjalan
|
||
|
|
- [ ] SSH key di-rotate
|
||
|
|
- [ ] Docker image di-scan untuk vulnerability
|