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:
- Technical Plan - Full implementation strategy
- Manual Setup - Production/staging user creation
- Atlas Operator Setup - PR preview automation
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:
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:
readWriteAnyDatabaseroledbAdminAnyDatabaseroleuserAdminAnyDatabaserole- Access to
syrftestdatabase - Access to
syrf_stagingdatabase - Access to
admindatabase
Required pattern:
- Database name must match
syrf_pr_*
Layer 5: Role Scoping¶
Each PR user is created with a specific, scoped role:
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 Adminrole - Verify operator
watchNamespacesis set topr-* - 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