Quartz's CreatedModifiedDate plugin recognizes `modified`, `lastmod`, `updated`, and `last-modified` — but not `date-modified`. The wrong field name caused Quartz to fall through to filesystem timestamps (UTC in Dagger), showing incorrect dates on the rendered site. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
150 lines
5.3 KiB
Markdown
150 lines
5.3 KiB
Markdown
---
|
|
title: Security Model
|
|
modified: 2026-02-11
|
|
last-reviewed: 2026-02-11
|
|
tags:
|
|
- explanation
|
|
- security
|
|
---
|
|
|
|
# Security Model
|
|
|
|
> **Note:** This article was drafted by AI and reviewed by Erich. I plan to rewrite all explanatory content in my own words - these serve as placeholders to establish the documentation structure.
|
|
|
|
How BlumeOps handles network security, secrets, and access control.
|
|
|
|
## Network Security: Tailscale
|
|
|
|
The foundational security decision is using [[tailscale]] as the network layer.
|
|
|
|
### Zero Trust Networking
|
|
|
|
BlumeOps infrastructure has no public IP addresses or port forwarding. Most services are only accessible via Tailscale:
|
|
|
|
- **Encrypted by default** - WireGuard encryption for all traffic
|
|
- **Identity-based access** - ACLs based on user/device identity, not IP addresses
|
|
- **Minimal public surface** - only selected services are exposed via [[flyio-proxy]]
|
|
|
|
### Public Access via Fly.io
|
|
|
|
A small number of services are exposed to the internet through a reverse proxy on Fly.io that tunnels back to the homelab over Tailscale. The proxy uses restricted ACLs (`tag:flyio-target`) so it can only reach explicitly tagged endpoints — a compromised proxy cannot route to arbitrary services on the tailnet. See [[flyio-proxy]] for details and [[expose-service-publicly]] for the security considerations.
|
|
|
|
### Defense in Depth
|
|
|
|
Even within the tailnet, access is restricted:
|
|
|
|
```
|
|
Internet ──▶ Fly.io proxy ──▶ tag:flyio-target only (docs, observability)
|
|
|
|
Tailnet:
|
|
Admin ────────▶ All services
|
|
Member ───────▶ User-facing services only
|
|
Homelab tag ──▶ NAS (for backups)
|
|
```
|
|
|
|
See [[tailscale]] for the full ACL matrix.
|
|
|
|
### Tailscale Operator Privileges
|
|
|
|
The [[tailscale-operator]] bridges Kubernetes and the Tailscale control plane. Its Kubernetes RBAC is namespaced to `tailscale` — it can't read secrets or create pods in other namespaces. On the Tailscale side, its OAuth client can create devices, generate auth keys, and assign `tag:k8s` or `tag:flyio-target`. In practice this means anyone who can write Ingress resources to the cluster can expose a service to the tailnet (or publicly, via `tag:flyio-target`), and Tailscale admins can reconfigure how those services are routed. Both are expected parts of normal operations — but be careful about granting write access to either Kubernetes or the Tailscale admin console, since both can change what's exposed.
|
|
|
|
## Secrets Management
|
|
|
|
Secrets follow a hierarchy:
|
|
|
|
### Source of Truth: 1Password
|
|
|
|
All secrets originate in 1Password's `blumeops` vault:
|
|
- API keys, tokens, passwords
|
|
- SSH keys and certificates
|
|
- OAuth credentials
|
|
|
|
### Kubernetes: External Secrets Operator
|
|
|
|
[[external-secrets]] syncs secrets from 1Password to Kubernetes:
|
|
|
|
```
|
|
1Password ──▶ 1Password Connect ──▶ ExternalSecret ──▶ K8s Secret
|
|
```
|
|
|
|
Services reference native Kubernetes Secrets; they don't know about 1Password.
|
|
|
|
### Ansible: op CLI
|
|
|
|
Ansible playbooks fetch secrets at runtime via `op read`:
|
|
|
|
```yaml
|
|
- name: Fetch secret
|
|
ansible.builtin.command:
|
|
cmd: op read "op://vault/item/field"
|
|
delegate_to: localhost
|
|
```
|
|
|
|
Always use `op read` — never `op item get --fields`, which corrupts multi-line values by wrapping them in quotes. Secrets are held in memory as Ansible facts, never written to disk.
|
|
|
|
### Git Repository
|
|
|
|
The repository is public. Secrets must never be committed:
|
|
- `.gitignore` excludes sensitive patterns
|
|
- Pre-commit hooks scan for potential secrets (TruffleHog)
|
|
- All config files use references to secrets, not values
|
|
|
|
## Access Control Philosophy
|
|
|
|
### Principle of Least Privilege
|
|
|
|
Services and devices get minimum necessary access:
|
|
|
|
| Entity | Access |
|
|
|--------|--------|
|
|
| Admin users | Everything |
|
|
| Member users | User-facing services only |
|
|
| Homelab servers | Only what they need (NAS for backups) |
|
|
| K8s pods | No Tailscale access (use Caddy proxy) |
|
|
|
|
### Tagged Devices vs User Devices
|
|
|
|
Important Tailscale concept:
|
|
- **User devices** (like gilbert) have user identity and inherit user ACLs
|
|
- **Tagged devices** (like indri with `tag:homelab`) lose user identity
|
|
|
|
Don't tag user devices - it breaks user-based access rules.
|
|
|
|
## Authentication Patterns
|
|
|
|
### Service-to-Service
|
|
|
|
Internal services use:
|
|
- Kubernetes service discovery (no auth needed within cluster)
|
|
- Tailscale identity for cross-host communication
|
|
|
|
### User-to-Service
|
|
|
|
Users authenticate via:
|
|
- Service-specific credentials (stored in 1Password)
|
|
- Some services support Tailscale identity (future)
|
|
|
|
### AI/Automation Access
|
|
|
|
Claude Code and automation use:
|
|
- SSH keys for git operations
|
|
- ArgoCD tokens for deployments
|
|
- 1Password CLI for secret retrieval (requires user approval)
|
|
|
|
## What's Not Protected
|
|
|
|
Honest assessment of security boundaries:
|
|
|
|
- **Local network attacks** - If someone is on your home WiFi, they could potentially access the NAS directly
|
|
- **Physical access** - No disk encryption on servers (trade-off for reliability)
|
|
- **Supply chain** - Container images from upstream registries
|
|
- **Operator error** - Misconfigured ACLs or leaked credentials
|
|
|
|
The model assumes a trusted home network and focuses on protecting against internet-based attacks.
|
|
|
|
## Related
|
|
|
|
- [[tailscale]] - ACL configuration
|
|
- [[1password]] - Secrets management
|
|
- [[external-secrets]] - Kubernetes secrets
|
|
- [[architecture]] - Overall system design
|