Skip to content

MongoDB Atlas Security Model

Overview

This document describes the defense-in-depth security architecture for MongoDB Atlas access in the SyRF infrastructure. All environments (production, staging, PR previews) share the same Atlas project and cluster for cost reasons, making security isolation critical.

Related Documents:

Security Architecture

┌─────────────────────────────────────────────────────────────────┐
│                     MongoDB Atlas Project                        │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    PROTECTED ZONE                         │   │
│  │  (NOT managed by Kubernetes - manual creation only)       │   │
│  │                                                           │   │
│  │  ┌─────────────────┐    ┌─────────────────┐              │   │
│  │  │ syrf_prod_app   │    │ syrf_staging_app│              │   │
│  │  │ → syrftest ONLY │    │ → syrf_staging  │              │   │
│  │  └─────────────────┘    └─────────────────┘              │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │              OPERATOR-MANAGED ZONE                        │   │
│  │  (Created/deleted by Atlas K8s Operator per PR)           │   │
│  │                                                           │   │
│  │  ┌─────────────────┐  ┌─────────────────┐                │   │
│  │  │ syrf_pr_123_app │  │ syrf_pr_456_app │  ...           │   │
│  │  │ → syrf_pr_123   │  │ → syrf_pr_456   │                │   │
│  │  │   ONLY          │  │   ONLY          │                │   │
│  │  └─────────────────┘  └─────────────────┘                │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Defense in Depth: 5 Layers

┌─────────────────────────────────────────────────────────────────────────┐
│                        DEFENSE LAYERS                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Layer 1: Manual Users (Production & Staging)                           │
│  ───────────────────────────────────────────                            │
│  • Created in Atlas Console, NOT in Kubernetes                          │
│  • Operator cannot see, modify, or delete them                          │
│  • Credentials in GCP Secret Manager, not K8s                           │
│                                                                          │
│  Layer 2: API Key Least Privilege                                       │
│  ────────────────────────────────                                       │
│  • Project Database Access Admin (not Project Owner)                    │
│  • Can only manage users, cannot modify cluster/project                 │
│                                                                          │
│  Layer 3: Namespace Isolation                                           │
│  ────────────────────────────────                                       │
│  • Operator only watches pr-* namespaces                                │
│  • CRs in staging/production namespaces are ignored                     │
│                                                                          │
│  Layer 4: Policy Enforcement (OPA/Kyverno)                              │
│  ─────────────────────────────────────────                              │
│  • Blocks readWriteAnyDatabase and similar dangerous roles              │
│  • Blocks access to syrftest and syrf_staging databases                 │
│  • Enforces syrf_pr_* database naming convention                        │
│                                                                          │
│  Layer 5: Role Scoping                                                  │
│  ─────────────────────                                                  │
│  • Each PR user has readWrite on syrf_pr_{N} ONLY                       │
│  • Even if policy is bypassed, user has no production access            │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Layer Details

Layer 1: Manual Users

Production and staging users are created manually in the Atlas Console, not via Kubernetes resources. This provides complete isolation from any automation.

User Managed By Deletion Risk
syrf_prod_app Manual (Atlas Console) None - not in K8s
syrf_staging_app Manual (Atlas Console) None - not in K8s
syrf_pr_{N}_app Automated (Operator) Intentional - auto-cleanup

Why this matters: Even if the Kubernetes cluster is compromised, the attacker cannot delete production/staging users because they're not managed by the operator.

Layer 2: API Key Least Privilege

The Atlas Operator uses an API key with Project Database Access Admin role, NOT Project Owner.

Permission Project Owner Project Database Access Admin
Manage database users
Manage custom roles
Modify cluster config
Delete clusters
Modify project settings
Manage backups

Why this matters: Even if the API key is compromised, the attacker cannot delete the cluster or modify project settings.

Layer 3: Namespace Isolation

The Atlas Operator is configured to only watch pr-* namespaces:

# Operator Helm values
watchNamespaces: "pr-*"

Why this matters: AtlasDatabaseUser CRs in production or staging namespaces are completely ignored by the operator.

Layer 4: Policy Enforcement

Policy engines (OPA Gatekeeper or Kyverno) validate all AtlasDatabaseUser resources at admission time.

Blocked patterns:

  • readWriteAnyDatabase role
  • dbAdminAnyDatabase role
  • userAdminAnyDatabase role
  • Access to syrftest database
  • Access to syrf_staging database
  • Access to admin database

Required pattern:

  • Database name must match syrf_pr_*

Layer 5: Role Scoping

Each PR user is created with a specific, scoped role:

roles:
  - roleName: readWrite
    databaseName: syrf_pr_{{.prNumber}}

Why this matters: Even if all other layers fail, the user can only access their specific PR database.

Operator Capabilities

What the Operator CAN Do

  • Create/delete database users (in operator-managed zone only)
  • Read project configuration (via externalProjectRef)
  • Create connection secrets in Kubernetes

What the Operator CANNOT Do

  • Delete or modify the Atlas project
  • Delete or modify the MongoDB cluster
  • Access production/staging users (they're not CRs)
  • Create users outside of watched namespaces (pr-*)

Attack Scenarios and Mitigations

Attack Vector Mitigated By
Malicious CR with readWriteAnyDatabase Layer 4: Policy blocks dangerous roles
Malicious CR targeting syrftest database Layer 4: Policy blocks protected databases
Attacker gains K8s access, creates CR in staging Layer 3: Operator ignores non-pr-* namespaces
Compromised API key Layer 2: Limited to user management, Layer 4 still blocks
Operator bug creates wrong user Layer 4: Policy validates all CRs
Direct Atlas Console access Out of scope (requires Atlas credentials, not K8s)
Delete production user via operator Layer 1: Production user not in K8s

Policy Implementation

Option A: OPA Gatekeeper

# ConstraintTemplate
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: atlasdatabaseuserroles
spec:
  crd:
    spec:
      names:
        kind: AtlasDatabaseUserRoles
      validation:
        openAPIV3Schema:
          type: object
          properties:
            forbiddenRoles:
              type: array
              items:
                type: string
            forbiddenDatabases:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package atlasdatabaseuserroles

        violation[{"msg": msg}] {
          input.review.object.kind == "AtlasDatabaseUser"
          role := input.review.object.spec.roles[_]
          forbidden := input.parameters.forbiddenRoles[_]
          role.roleName == forbidden
          msg := sprintf("Forbidden role '%v' - PR users cannot have '%v' access", [forbidden, forbidden])
        }

        violation[{"msg": msg}] {
          input.review.object.kind == "AtlasDatabaseUser"
          role := input.review.object.spec.roles[_]
          forbidden := input.parameters.forbiddenDatabases[_]
          role.databaseName == forbidden
          msg := sprintf("Forbidden database '%v' - PR users cannot access protected databases", [forbidden])
        }
---
# Constraint
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: AtlasDatabaseUserRoles
metadata:
  name: block-production-access
spec:
  match:
    kinds:
      - apiGroups: ["atlas.mongodb.com"]
        kinds: ["AtlasDatabaseUser"]
    namespaces: ["pr-*"]
  parameters:
    forbiddenRoles:
      - "readWriteAnyDatabase"
      - "dbAdminAnyDatabase"
      - "userAdminAnyDatabase"
      - "root"
    forbiddenDatabases:
      - "syrftest"
      - "syrf_staging"
      - "admin"

Option B: Kyverno (Simpler)

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: block-atlas-production-access
  annotations:
    policies.kyverno.io/title: Block PR Preview Access to Production
    policies.kyverno.io/description: >-
      Prevents AtlasDatabaseUser resources in PR namespaces from having
      roles that would grant access to production or staging databases.
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: block-dangerous-roles
      match:
        any:
          - resources:
              kinds:
                - AtlasDatabaseUser
              namespaces:
                - "pr-*"
      validate:
        message: "PR preview users cannot have broad database access roles"
        deny:
          conditions:
            any:
              - key: "{{ request.object.spec.roles[?roleName == 'readWriteAnyDatabase'] | length(@) }}"
                operator: GreaterThan
                value: 0
              - key: "{{ request.object.spec.roles[?roleName == 'dbAdminAnyDatabase'] | length(@) }}"
                operator: GreaterThan
                value: 0

    - name: block-protected-databases
      match:
        any:
          - resources:
              kinds:
                - AtlasDatabaseUser
              namespaces:
                - "pr-*"
      validate:
        message: "PR preview users cannot access production or staging databases"
        deny:
          conditions:
            any:
              - key: "{{ request.object.spec.roles[?databaseName == 'syrftest'] | length(@) }}"
                operator: GreaterThan
                value: 0
              - key: "{{ request.object.spec.roles[?databaseName == 'syrf_staging'] | length(@) }}"
                operator: GreaterThan
                value: 0

    - name: require-pr-specific-database
      match:
        any:
          - resources:
              kinds:
                - AtlasDatabaseUser
              namespaces:
                - "pr-*"
      validate:
        message: "PR preview users must only access syrf_pr_* databases"
        pattern:
          spec:
            roles:
              - databaseName: "syrf_pr_*"

Deletion Protection

Resource Default Behavior Our Configuration
AtlasProject Protected by default N/A (using externalProjectRef)
AtlasDatabaseUser (prod/staging) N/A Not in K8s - fully protected
AtlasDatabaseUser (PR preview) Protected by default OFF (deletion-protection: "false") for auto-cleanup

Security Checklist

Before Going Live

  • Verify production user created manually in Atlas Console
  • Verify staging user created manually in Atlas Console
  • Verify API key uses Project Database Access Admin role
  • Verify operator watchNamespaces is set to pr-*
  • Deploy policy engine (Gatekeeper or Kyverno)
  • Deploy policy blocking production access
  • Test policy with malicious CR (should be rejected)

Periodic Verification

  • Audit Atlas Database Access users monthly
  • Verify no unexpected users have broad permissions
  • Review operator logs for anomalies
  • Rotate API key annually

Monitoring and Alerts

Consider setting up alerts for:

  • Operator errors in creating/deleting users
  • Policy violations (blocked CRs)
  • Unexpected users appearing in Atlas
  • Failed authentication attempts

References