Identity & Access Management¶
Architecture decisions for authentication, authorization, and secrets management.
Overview¶
┌─────────────┐
│ Zoho │
│ (Email) │
└──────┬──────┘
│
┌──────▼──────┐
│ Zitadel │
│ (OIDC) │
└──────┬──────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Tailscale │ │ AWS IAM │ │ Apps │
│ (VPN) │ │ (KMS) │ │ (SSO) │
└─────┬─────┘ └─────┬─────┘ └───────────┘
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ SSH │ │ SOPS │
│ Access │ │ (Secrets) │
└───────────┘ └───────────┘
Components¶
Email: Zoho Mail¶
Why Zoho:
- Cost-effective ($1-3/user/month)
- Custom domain support (
@minnova.io) - SMTP for sending (notifications, password resets)
- Privacy-focused (no ads)
Setup flow:
- Admin signs up with personal email
- Add domain
minnova.io - Verify via DNS TXT record
- Create corporate mailboxes
- Update MX records in Cloudflare
Cloudflare coexistence:
- Keep Cloudflare email routing for catch-all/forwarding
- Zoho handles primary mailboxes
- Both can work together
Identity Provider: Zitadel¶
Why Zitadel over Keycloak:
| Aspect | Zitadel | Keycloak |
|---|---|---|
| Resource usage | ~256MB RAM | ~1GB+ RAM |
| Setup | Simple, modern | Complex, enterprise |
| Technology | Go binary | Java application |
| Cloud option | Yes (free tier) | No |
| Best for | Small teams | Large enterprises |
Zitadel provides:
- OIDC/OAuth2 authentication
- User management (add/remove employees)
- MFA support
- SSO for all internal apps
- SCIM for user provisioning
Deployment options:
- Zitadel Cloud - Free tier, zero maintenance
- Self-hosted - Run on Hetzner VPS
Network Access: Tailscale on Ingress Node¶
Tailscale installed only on the ingress node, not every machine. See Network Architecture.
Why this pattern:
- Fewer Tailscale devices (cost, management)
- Centralized access audit
- Workers have no public exposure
- SSH jump to private workers
Access flow:
User authenticates via Zitadel
↓
Tailscale grants access to ingress node
↓
SSH jump to private workers
↓
User removed from Zitadel → loses all access
Secrets Management: SOPS + AWS KMS¶
Current state: SOPS with Age encryption (manual key distribution)
Target state: SOPS with AWS KMS (identity-based access)
Why migrate to KMS:
- No manual key distribution
- Access tied to identity (revoke user → lose decrypt)
- Audit trail via CloudTrail
- Machine identity via OIDC (no stored secrets)
Human Users¶
User authenticates via Zitadel
↓
AWS IAM federation (OIDC trust)
↓
Assumes IAM role with KMS:Decrypt
↓
SOPS decrypts using KMS
Machine Users (Atlantis, CI/CD)¶
Machines use Workload Identity (OIDC for machines):
GitHub Actions / Atlantis
↓
OIDC token issued by platform
↓
AWS trusts OIDC provider
↓
Assumes role → KMS access
No secrets stored anywhere - GitHub Actions uses aws-actions/configure-aws-credentials with OIDC.
Hybrid Configuration¶
During transition, support both Age and KMS:
# .sops.yaml
creation_rules:
- path_regex: live/.*/secrets/.*\.yaml$
kms: arn:aws:kms:eu-central-1:ACCOUNT:key/KEY-ID
age: age195pjehrjy5ry8hl5s6jc9zys2gkuyp4mrc2fprs7yy704p8hufjq2d9vu4
Revocation Matrix¶
| Entity | How to revoke |
|---|---|
| Human user | Delete from Zitadel → loses IAM federation |
| Atlantis | Delete IAM role or remove from KMS key policy |
| GitHub Actions | Remove OIDC trust or delete role |
| Emergency | Remove key from KMS key policy |
Implementation Roadmap¶
Phase 1: Email Foundation¶
- Sign up for Zoho Mail
- Verify
minnova.iodomain - Create
paulo@,leo@,hello@mailboxes - Configure MX records in Cloudflare
- Test send/receive
Phase 2: Identity Provider¶
- Deploy Zitadel (cloud or self-hosted)
- Configure SMTP (Zoho) for notifications
- Create organization and users
- Set up MFA policies
- Migrate Cloudflare Access to Zitadel
Phase 3: Network Access¶
- Set up Tailscale account
- Connect Tailscale to Zitadel OIDC
- Configure ACLs
- Enable Tailscale SSH
- Restrict Hetzner firewall to Tailscale only
Phase 4: Secrets Migration¶
- Create AWS KMS key in
eu-central-1 - Set up IAM OIDC federation with Zitadel
- Create IAM roles for humans and machines
- Add KMS to
.sops.yaml(alongside Age) - Configure GitHub Actions OIDC
- Update Atlantis to use OIDC
Phase 5: Account Migration¶
- Update AWS account email to corporate
- Update Hetzner account email to corporate
- Update GitHub organization email to corporate
- Update Cloudflare account email to corporate
Cost Estimate¶
| Service | Monthly Cost |
|---|---|
| Zoho Mail (2 users, Lite) | ~$2 |
| Zitadel Cloud (free tier) | $0 |
| Tailscale (free tier) | $0 |
| AWS KMS (1 key) | ~$1 |
| Total | ~$3/month |
Security Considerations¶
Defense in Depth¶
- Email - Foundation for password resets, MFA
- OIDC - Single source of truth for identity
- VPN - Network-level access control
- KMS - Encryption tied to identity
Principle of Least Privilege¶
- Users get minimum required access
- Machines get scoped IAM roles
- KMS key policies restrict who can decrypt
Auditability¶
- Zitadel logs all authentication events
- AWS CloudTrail logs KMS usage
- Tailscale logs network access
- Git tracks all infrastructure changes
Related Documentation¶
- Network Architecture - Traffic flow and ingress
- VPN Strategy - Detailed VPN comparison
- Orchestration - Container orchestration options