Skip to content

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:

  1. Admin signs up with personal email
  2. Add domain minnova.io
  3. Verify via DNS TXT record
  4. Create corporate mailboxes
  5. 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:

  1. Zitadel Cloud - Free tier, zero maintenance
  2. 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.io domain
  • 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

  1. Email - Foundation for password resets, MFA
  2. OIDC - Single source of truth for identity
  3. VPN - Network-level access control
  4. 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