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


Authorization Model

GitLab RBAC

User roles:

  • Guest - Read-only (issues, wikis, can’t pull code)

  • Reporter - Pull code, view CI/CD

  • Developer - Push code, run CI/CD, create merge requests

  • Maintainer - Merge MRs, manage branches, configure project

  • Owner - Manage users, project settings, delete project

Group inheritance:

Group (Owner: alice)
  └── Project A (alice = Owner via group)
      ├── Developer: bob (direct assignment)
      └── Maintainer: charlie (direct assignment)

Harbor RBAC

Project roles:

  • Guest - View images (no pull)

  • Developer - Pull/push images

  • Maintainer - Pull/push/delete images

  • Admin - Manage users, webhooks, replication

GitLab OAuth mapping:

GitLab user (OAuth) → Harbor user (auto-created)
  Default role: Developer (can pull/push images)

Kubernetes RBAC

ServiceAccounts:

  • gitlab - GitLab pods (webservice, gitaly, sidekiq, etc.)

  • harbor - Harbor pods (core, registry, jobservice, portal)

  • monitoring - Monitoring resources (future)

Permissions (via Role gitlab-role):

rules:
  - apiGroups: [""]
    resources: [secrets, configmaps]
    verbs: [get, list, watch]  # Read-only
  - apiGroups: [""]
    resources: [pods, pods/log]
    verbs: [get, list, watch]  # Read-only (for GitLab Runner)

Why minimal permissions?

  • Least privilege - Pods can’t modify cluster state

  • Secrets read-only - Pods can read secrets, can’t create/update

  • No pod creation - GitLab can’t create pods (except GitLab Runner via separate RBAC)


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 (in application-secrets)

    • Replicated to gitlabbda/gitlab-s3-credentials and gitlabbda/harbor-s3-credentials

2. Application Secrets (Wave 2):

  • smtp - SMTP password (in application-secrets)

    • Replicated to gitlabbda/gitlab-smtp-password

  • gitlab-oauth-app - OAuth client ID + secret (in application-secrets)

    • Replicated to gitlabbda/harbor-secrets

  • harbor-registry-secret - Harbor internal secret (in application-secrets)

    • Replicated to gitlabbda/harbor-secrets

  • gitlab-root-password - Initial root password (in application-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

  1. Enable 2FA - Require for all admin accounts

  2. Rotate secrets - Quarterly rotation of S3, SMTP, OAuth secrets

  3. Review audit logs - Weekly review of GitLab/Harbor audit events

  4. Update regularly - Apply security patches within 7 days

  5. Backup verification - Monthly restore test from backups

For Users

  1. Use SSH keys - Avoid HTTPS git with passwords

  2. Personal access tokens - Minimum scope, set expiration

  3. Strong passwords - 16+ characters, unique per service

  4. 2FA optional - Recommended for sensitive projects

  5. Don’t commit secrets - Use GitLab CI/CD variables for secrets

For CI/CD

  1. Protected variables - Mark secrets as “protected” (main branch only)

  2. Masked variables - Mask secrets in job logs

  3. Harbor robot accounts (future) - Dedicated service accounts for docker push/pull

  4. 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)

  1. Network Policies - Implement pod-to-pod access restrictions

  2. Trivy scanner - Deploy Harbor vulnerability scanning

  3. 2FA enforcement - Require for admin accounts

  4. Secret rotation automation - Automated quarterly rotation

Medium-term (6-12 months)

  1. Kubernetes secret encryption - Enable etcd encryption at rest

  2. PostgreSQL backup encryption - Client-side pg_basebackup encryption

  3. Harbor robot accounts - Service accounts for CI/CD

  4. WAF - Web Application Firewall in front of Traefik (Cloudflare)

Long-term (12+ months)

  1. LDAP integration - Enterprise SSO (Active Directory, Okta)

  2. Vault integration - HashiCorp Vault for dynamic secrets

  3. 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: