Explanation
Security Model¶
Overview¶
GitLab BDA implements defense-in-depth security with multiple layers: authentication, authorization, secrets management, network isolation, and TLS encryption. This document explains the security architecture and WHY each decision was made.
Key security principles:
External Secrets Operator - Centralized secret management
OAuth2/OIDC - Single sign-on via GitLab
TLS everywhere - All external traffic encrypted
RBAC - Least-privilege Kubernetes permissions
Network isolation - Pod-to-pod encryption (Cilium)
Authentication Architecture¶
User Authentication¶
GitLab (Primary Identity Provider):
Users: Username/password, OAuth (GitHub, Google), LDAP (future)
API: Personal access tokens, OAuth2 tokens, SSH keys
2FA: Optional (recommended for admins)
Harbor (Secondary via OAuth):
Users: OAuth2 via GitLab (single sign-on)
Robot accounts: Service accounts for CI/CD (future)
Flow:
User → GitLab (login) → Session cookie
↓
Harbor UI → OAuth redirect → GitLab authorization → Callback with token
↓
Harbor → Verify token with GitLab → Create/update Harbor account
Why OAuth for Harbor?
Single sign-on - One account (GitLab) for both systems
Centralized user management - Add user to GitLab → automatic Harbor access
Token-based - Revoke GitLab token → Harbor access revoked
Service Authentication¶
Kubernetes Services (internal):
PostgreSQL: User/password (CNPG-generated secret
gitlab-postgres-app)Redis: No auth (namespace-isolated, trusted network)
S3: Access key + secret key (Hetzner S3 credentials)
Why no Redis auth?
Namespace isolation - Only GitLab/Harbor pods in same namespace
Network policy (future) - Restrict pod-to-pod access
Performance - No auth overhead for cache operations
Secrets Management¶
External Secrets Operator (ESO)¶
Architecture:
application-secrets namespace (source)
↓ ClusterSecretStore
ExternalSecret CRs (gitlabbda namespace)
↓ Sync
Kubernetes Secrets (gitlabbda namespace)
↓ Mount
Pods (environment variables, volume mounts)
Why ESO?
Centralized - All secrets in one namespace (
application-secrets)GitOps-safe - ExternalSecret CRs in git, actual secrets not in git
Rotation - Update secret in source → auto-sync to deployment
Multi-deployment - Multiple deployments reference same secrets
Secret Categories¶
1. Infrastructure Secrets (Wave 1):
hetzner-s3- S3 access key + secret key (inapplication-secrets)Replicated to
gitlabbda/gitlab-s3-credentialsandgitlabbda/harbor-s3-credentials
2. Application Secrets (Wave 2):
smtp- SMTP password (inapplication-secrets)Replicated to
gitlabbda/gitlab-smtp-password
gitlab-oauth-app- OAuth client ID + secret (inapplication-secrets)Replicated to
gitlabbda/harbor-secrets
harbor-registry-secret- Harbor internal secret (inapplication-secrets)Replicated to
gitlabbda/harbor-secrets
gitlab-root-password- Initial root password (inapplication-secrets)Replicated to
gitlabbda/gitlab-initial-root-password
3. Generated Secrets (by operators):
gitlab-postgres-app- PostgreSQL password (CNPG-generated)All TLS secrets - cert-manager-generated (Let’s Encrypt)
Secret Rotation¶
Manual rotation (S3, SMTP, OAuth):
# 1. Update secret in application-secrets namespace
kubectl edit secret hetzner-s3 -n application-secrets
# 2. ESO detects change, updates replicated secrets (30s)
kubectl get externalsecret gitlab-s3-credentials -n gitlabbda -w
# 3. Restart pods to pick up new secret
kubectl rollout restart deployment gitlab-webservice -n gitlabbda
Automatic rotation (PostgreSQL):
CNPG rotates password on Cluster CR update
Pooler automatically reconnects with new password
For complete secrets reference, see Secrets Reference.
Network Security¶
TLS Encryption¶
External traffic (ALL encrypted):
GitLab UI/API:
https://gitlab.staging.bluedynamics.eu(Let’s Encrypt cert)GitLab Pages:
https://*.pages.staging.bluedynamics.eu(wildcard cert)Harbor UI/Registry:
https://registry.staging.bluedynamics.eu(Let’s Encrypt cert)GitLab SSH: Port 22 (SSH protocol, built-in encryption)
Internal traffic (Cilium encrypted):
Pod-to-pod: Wireguard encryption (KUP6S cluster default)
Pod-to-PostgreSQL: Unencrypted (trusted network, Cilium encryption)
Pod-to-Redis: Unencrypted (trusted network, Cilium encryption)
Pod-to-S3: HTTPS (TLS to Hetzner S3 endpoint)
Why unencrypted PostgreSQL/Redis?
Cilium encryption - Pod-to-pod already encrypted at network layer
Performance - No double encryption overhead (Cilium + TLS)
Trusted network - All pods in same cluster, Cilium network policies
Network Policies (Future)¶
Current: No NetworkPolicies (all pods can talk to all pods)
Future: Implement NetworkPolicies for strict isolation
# Example: Redis only accessible by GitLab/Harbor
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: redis-ingress
spec:
podSelector:
matchLabels: {app: redis}
ingress:
- from:
- podSelector:
matchLabels: {app.kubernetes.io/part-of: gitlab}
- podSelector:
matchLabels: {app.kubernetes.io/part-of: harbor}
Data Security¶
Data at Rest¶
Encrypted storage:
Hetzner Cloud Volumes: Hetzner-managed encryption (default)
Longhorn PVCs: Encrypted by underlying node storage (Hetzner volumes)
Hetzner S3: Server-side encryption (S3 default)
Not encrypted (low risk):
ConfigMaps (no sensitive data)
Kubernetes secrets (base64-encoded, but not encrypted at rest in etcd)
Future improvement: Enable Kubernetes secret encryption at rest (etcd encryption)
Data in Transit¶
Encrypted:
All HTTPS traffic (GitLab, Harbor, Pages)
SSH git operations (SSH protocol)
S3 uploads/downloads (HTTPS)
Pod-to-pod (Cilium Wireguard)
Unencrypted (trusted network):
Pod-to-PostgreSQL (within cluster)
Pod-to-Redis (within cluster)
Backup Security¶
GitLab backups (S3):
Encryption: S3 server-side encryption (Hetzner S3 default)
Access control: S3 credentials (access key + secret key)
Retention: 7 days (automated cleanup)
PostgreSQL backups (S3 via CNPG Barman):
Encryption: Unencrypted (future: pg_basebackup encryption)
Access control: S3 credentials
Retention: 30 days
Why unencrypted PostgreSQL backups?
S3 server-side encryption - Hetzner encrypts at rest
Transport encryption - HTTPS to S3
Future: Add pg_basebackup client-side encryption
Vulnerability Management¶
Container Image Security¶
Base images:
GitLab: Official GitLab Helm chart images (GitLab maintains)
Harbor: Official goharbor images (Harbor maintains)
PostgreSQL: CloudNativePG custom images (CNPG maintains)
Redis: Official redis:7-alpine (minimal attack surface)
Image scanning (future):
Deploy Trivy scanner in Harbor
Scan all pushed images
Block deployment on critical CVEs (policy)
Dependency Updates¶
Update strategy:
GitLab: Follow upstream releases (monthly minor, weekly patch)
Harbor: Follow upstream releases (quarterly minor, monthly patch)
PostgreSQL: CNPG operator handles updates (rolling upgrades)
Redis: Pin to redis:7-alpine, update manually
CVE monitoring:
Subscribe to security mailing lists (GitLab, Harbor, CNPG)
Check https://cve.mitre.org for component CVEs
Apply security patches within 7 days (critical), 30 days (high)
Access Control¶
SSH Key Management¶
GitLab SSH keys:
Users upload public keys via GitLab UI
GitLab Shell validates keys on git push/pull
Private keys never stored (user manages locally)
Infrastructure SSH (cluster nodes):
Key-based authentication only (no passwords)
Root access via authorized_keys
Separate from application SSH (different keys)
API Token Security¶
GitLab Personal Access Tokens:
User-generated via GitLab UI
Scopes:
api,read_repository,write_repository, etc.Revocable (user or admin can revoke)
Used for: CI/CD, Harbor docker login, automation
Best practices:
Minimum scope - Only grant needed permissions
Expiration - Set expiration date (90 days recommended)
Rotation - Rotate tokens annually
No sharing - Each user/service has own token
Harbor Robot Accounts (Future)¶
Current: Users use GitLab tokens for docker login
Future: Create Harbor robot accounts for CI/CD
# Create robot account via Harbor UI
# Name: robot$gitlab-ci
# Permissions: Pull/push to specific project
# Expiration: 1 year
# Use in CI/CD
docker login registry.example.com -u robot$gitlab-ci -p <robot-token>
Audit Logging¶
GitLab Audit Events¶
Logged events:
User login/logout
Repository push
Project settings changes
User/group membership changes
SSH key additions
Storage: PostgreSQL (gitlab database, audit_events table)
Retention: Unlimited (until database cleanup)
Access: GitLab UI (Admin Area → Monitoring → Audit Events)
Harbor Audit Logs¶
Logged events:
Image push/pull
User login
Repository creation/deletion
Replication jobs
Scan results
Storage: PostgreSQL (harbor database, audit_log table)
Retention: Unlimited
Access: Harbor UI (Projects → Logs)
Kubernetes Audit Logs¶
Current: Cluster-level audit logs (managed by cluster infrastructure)
Events logged:
Pod creation/deletion
ConfigMap/Secret updates
RBAC changes
API requests
Storage: Loki (cluster monitoring)
For audit log queries, see Main Cluster Docs: Monitoring.
Security Best Practices¶
For Administrators¶
Enable 2FA - Require for all admin accounts
Rotate secrets - Quarterly rotation of S3, SMTP, OAuth secrets
Review audit logs - Weekly review of GitLab/Harbor audit events
Update regularly - Apply security patches within 7 days
Backup verification - Monthly restore test from backups
For Users¶
Use SSH keys - Avoid HTTPS git with passwords
Personal access tokens - Minimum scope, set expiration
Strong passwords - 16+ characters, unique per service
2FA optional - Recommended for sensitive projects
Don’t commit secrets - Use GitLab CI/CD variables for secrets
For CI/CD¶
Protected variables - Mark secrets as “protected” (main branch only)
Masked variables - Mask secrets in job logs
Harbor robot accounts (future) - Dedicated service accounts for docker push/pull
Least privilege - CI jobs only access needed resources
Compliance Considerations¶
GDPR (Data Protection)¶
User data stored:
GitLab: Email, name, SSH keys, git commits (author/committer)
Harbor: Email (from GitLab OAuth), image push/pull logs
Data retention:
Active users: Indefinite (until account deleted)
Deleted users: 30 days (soft delete), then purged
Right to erasure:
# Delete user from GitLab (Admin UI)
# → Harbor account auto-orphaned (no foreign key)
# → Manually delete Harbor user if needed
SOC2 (Security Controls)¶
Access controls: ✓ RBAC, least privilege Encryption: ✓ TLS, SSH, Cilium Wireguard Audit logging: ✓ GitLab, Harbor, Kubernetes Backup/recovery: ✓ Daily backups, tested restore Incident response: Future (document incident response plan)
Security Roadmap¶
Short-term (Next 6 months)¶
Network Policies - Implement pod-to-pod access restrictions
Trivy scanner - Deploy Harbor vulnerability scanning
2FA enforcement - Require for admin accounts
Secret rotation automation - Automated quarterly rotation
Medium-term (6-12 months)¶
Kubernetes secret encryption - Enable etcd encryption at rest
PostgreSQL backup encryption - Client-side pg_basebackup encryption
Harbor robot accounts - Service accounts for CI/CD
WAF - Web Application Firewall in front of Traefik (Cloudflare)
Long-term (12+ months)¶
LDAP integration - Enterprise SSO (Active Directory, Okta)
Vault integration - HashiCorp Vault for dynamic secrets
Compliance automation - Automated SOC2/ISO27001 evidence collection
Summary¶
Security layers:
Authentication - GitLab (primary), Harbor OAuth (SSO), Kubernetes RBAC
Secrets management - External Secrets Operator (centralized, rotation-ready)
Network security - TLS everywhere (external), Cilium Wireguard (internal)
Data security - Encrypted at rest (Hetzner), encrypted in transit (TLS/SSH)
Audit - GitLab, Harbor, Kubernetes audit logs
Key strengths:
Single sign-on - One identity (GitLab) for all systems
Centralized secrets - ESO with application-secrets namespace
Defense in depth - Multiple security layers
GitOps-safe - No secrets in git
Known gaps (roadmap):
No NetworkPolicies - All pods can talk to all pods (future)
No image scanning - Manual Trivy scanning (future: Harbor Trivy)
No 2FA enforcement - Optional (future: require for admins)
For detailed specifications:
Secrets Reference - All secrets documented
RBAC Reference (future) - Complete RBAC spec
Harbor Integration - OAuth flow details