What to create on your MongoDB deployment so DBSheriff can monitor it.
Covers both deployment shapes this engine supports — self-managed
(Community or Enterprise, any host) and MongoDB Atlas — since the two
diverge sharply on RBAC and on which controls are reachable from a plain
database connection at all. Every fact below was verified live: on-prem
against a real mongo:8 Community Edition container, and on Atlas against
a real, Terraform-provisioned M10 replica set — not written from
MongoDB's docs alone. Amazon DocumentDB is supported in part (§6: running
operations, not completed ones), and Azure Cosmos DB for the MongoDB API is
not supported (§6).
On an installed host there is no scripts/ folder, so read every scripts/<name>.py
below as sudo /opt/dbsheriff/current/deploy/onprem/stack.sh tool <name> ..., for example
sudo /opt/dbsheriff/current/deploy/onprem/stack.sh tool setup_dbsheriff mongodb mongo1.acme.internal. The scripts/ form
is for a source checkout. The details are under "How to start the scripts" in
Before you install: creating DBSheriff's database users.
| Script | What it does | Example |
|---|---|---|
setup_dbsheriff.py |
Recommended on self-managed. One command: checks/enables the prerequisite, then creates the accounts — but not the dbsheriff_view_users role that mongo_monitor holds on self-managed (§2): create that role first with create_reset_dbuser.py, selecting view_users on its own, or the mongo_monitor step fails because the role does not exist yet. On Atlas, the prerequisite half still works; the accounts half reports why it can't (see below) instead of creating anything. |
scripts/setup_dbsheriff.py mongodb mongo1.acme.internal |
install_prereqs.py |
Prerequisite only — identical on both deployment shapes, and the only one of the three that's fully automated on Atlas. | scripts/install_prereqs.py mongodb mongo1.acme.internal |
create_reset_dbuser.py |
Accounts only, self-managed only — see below for why Atlas isn't automatable here. Its all choice creates view_users last, after mongo_monitor, so select view_users on its own first. |
scripts/create_reset_dbuser.py mongodb mongo1.acme.internal |
Pass an Atlas SRV hostname (e.g. cluster0.xxxxx.mongodb.net) instead of
a plain host to target Atlas — pick atlas at the platform prompt either
way. install_prereqs.py and setup_dbsheriff.py ask which database you're
registering for monitoring (profiling is per-database, identical question and
identical command on both deployment shapes); create_reset_dbuser.py asks no
such question and does not enable profiling — it grants read on every
non-system database and leaves profiling as a manual follow-up. All three
connect only as a database user — never a
cloud credential, and never the Atlas Admin API Service Account below,
which has no host/port/username/password shape at all. Add --dry-run to
preview without changing anything, or --check for a read-only status
report — see
pre installation database users
for the full flag reference shared by every engine.
On Atlas, account creation itself isn't automatable by any of these
scripts — confirmed live, not a guess. createUser/updateUser return
a clean Unauthorized over a direct database connection for every account
type, even one holding Atlas's own atlasAdmin role — Atlas routes all
user management through its own control plane, full stop. Running
create_reset_dbuser.py/setup_dbsheriff.py against an Atlas cluster
still does something useful: it reports this clearly per account instead
of failing with a raw driver error, and it still handles the profiling
prerequisite. Create the four accounts themselves via the Atlas UI
(Database Access) or Terraform instead — §1 below has the exact
roles each one needs either way.
Atlas now has functional parity with self-managed, except for the PS01, PS05, PS06, PS11 and PS13 posture checks (NOT_CHECKED on Atlas, §3), the A04 connection-quality check and role_table_grants (§5) — kill, lock,
cluster-wide identity listing, and a real audit-logging posture check all
work on both deployment shapes, live-verified end to end. Getting there
needs one extra credential Atlas requires that self-managed doesn't: an
Atlas Admin API Service Account (§1), since several MongoDB
operations DBSheriff needs are structurally blocked from a database
connection on Atlas, full stop, regardless of role — not a missing grant,
a platform design choice. Read §5 for the handful of things that stay
genuinely different between the two shapes even with that credential in
place (mainly around encryption-at-rest and TLS posture checks, and the
shape of the audit trail).
| Item | Required? | What it unlocks | Without it | How |
|---|---|---|---|---|
| The accounts of §1 with the grants of §2 | required | statements, kill, space, availability, classification | DBSheriff cannot connect or cannot act | §2 |
Profiling enabled on every monitored database (db.setProfilingLevel(1, {slowms: 0})) |
required | the entire completed-statement/audit/perf-scan pipeline — MongoDB has no separate audit log to fall back to | system.profile never gets written; no completed queries, no P13, no statement audit at all. The setting does not always survive: an Atlas rolling restart or a new primary resets it to level 0, and slowms has been seen to change from 0 on its own. DBSheriff reads the level, slowms, sampleRate and filter about every 30 seconds and raises a capture-gap alert (R96) when the level is 0, slowms is not 0, sampleRate is below 1 or a profile filter is set (the last two leave most statements out of system.profile), and sets them back through the enableProfiler action that the monitor account holds (the dbsheriff_profiler role of §1 and §2; an account created before that role existed needs the accounts step run again, see below); where the account lacks it, re-run install_prereqs.py |
run once per database (self-managed and Atlas both — this is a plain database command, not an Atlas control-plane setting) — automated by install_prereqs.py/setup_dbsheriff.py, which ask which database |
system.profile as a capped collection of 64 to 256 MB (recommended; on Atlas as well, see the Atlas block below) |
recommended | complete completed-statement capture: at slowms: 0 every operation is one profile document, and the default 1 MB ring holds about a thousand of them, so a few thousand trivial operations overwrite the one that mattered before any reader sees it |
the producer still reads the ring oldest-first in pages, and raises a capture-gap alert (R96) when it finds the ring wrapped, but what was overwritten is gone | install_prereqs.py offers it (profile_size), and setup_dbsheriff.py does when run with --optional-prereqs=profile_size, or the commands below. On Atlas the same commands work for a database user holding atlasAdmin (a narrower user is refused, and the script then prints the commands to run as one that may) |
dbsheriff_profiler custom role (self-managed and Atlas, §1 and §2; not on Amazon DocumentDB, §6) |
required for the profiler health check | the monitor reads the profiling level and slowms (profile -1) and sets level 1 / slowms 0 again after a restart or a failover |
the check is skipped with one log line, and a switched-off profiler is seen only as an empty system.profile |
self-managed: made by scripts/create_reset_dbuser.py mongodb <host> and setup_dbsheriff.py as part of the mongo_monitor step; Atlas: a custom role you create and add to mongo_monitor, §1. An account created before this role existed needs the accounts step run again (choose reset), §2 |
dbsheriff_view_users custom role (self-managed only, §2; on Amazon DocumentDB it holds viewUser only, §6) |
required for PS01/PS05 and Account Inventory identities on self-managed | cluster-wide usersInfo({forAllDBs: true}) |
PS01/PS05 report NOT_CHECKED; Account Inventory shows nothing for this server |
db.createRole(), §2 — create via scripts/create_reset_dbuser.py mongodb <host>, selecting view_users |
| Atlas Admin API Service Account (Atlas only, §1) | required for lock, identities, and PS09 on Atlas | real Atlas-side lock/unlock, real cluster-wide identity listing, a real audit-logging posture check | those 3 features fall back to the self-managed mechanism, which is structurally blocked on Atlas — lock fails cleanly with LOCK_ERROR, identities/PS09 report nothing/an unreliable result |
Atlas UI → Organization → Access Manager → Applications → Service Accounts, §1 |
| Atlas Database Auditing (Atlas UI/API only, not a database command) | optional | a real audit trail behind PS09's now-accurate check | PS09 correctly reports it as off (this is now a real, trustworthy finding, not a guess) | Atlas UI → Security → Advanced |
The floor. A clusterMonitor+read monitor account gives you live +
completed statement monitoring and space/availability metrics on both
deployment shapes, once profiling is on. Kill, lock, identities, and PS09
all work on both shapes too — self-managed through database roles, Atlas
through a combination of Atlas-specific roles and the Admin API Service
Account. See §5 for what's still genuinely different in the details.
| Account | What it does | Self-managed role | Atlas role |
|---|---|---|---|
mongo_monitor |
live + completed statements (system.profile), space (dbStats), availability (serverStatus), profiler health (profile -1, and level 1 / slowms 0 set again) |
clusterMonitor@admin + read@\<db> + dbsheriff_view_users@admin (custom, §2) + dbsheriff_profiler@admin (custom, §2) |
clusterMonitor@admin + read@\<db> + dbsheriff_profiler (a custom role, below) |
mongo_kill_user |
db.killOp(<opid>) |
hostManager@admin |
killOpSession@admin |
mongo_lock_user |
revokes all roles / restores them | userAdminAnyDatabase@admin |
read@admin (a formality — the real lock mechanism on Atlas goes through the Admin API Service Account below, not this account; see §2) |
mongo_classify |
classification scan (collection/field sampling) | read@\<db> per database to scan |
read@\<db> per database to scan |
| Atlas Admin API Service Account (Atlas only) | real lock/unlock, real identity listing, real audit-logging check — everything a database connection structurally cannot do on Atlas | n/a | see below — not a database account at all |
// self-managed — run against the admin database
db.createRole({
role: "dbsheriff_view_users",
privileges: [
{ resource: { cluster: true }, actions: ["viewUser"] },
// viewRole (2026-09-20): needed for governance.role_table_grants'
// rolesInfo call (custom-role discovery for DB Role auto-tagging) —
// not yet granted on existing installs, see §2.
{ resource: { cluster: true }, actions: ["viewRole"] }
],
roles: []
});
// LIVE-281: the one action the profiler health check needs, on the monitored database only (repeat the privilege per database you
// register; never the cluster, never admin, config or local)
db.createRole({
role: "dbsheriff_profiler",
privileges: [
{ resource: { db: "<your database>", collection: "" }, actions: ["enableProfiler"] }
],
roles: []
});
db.createUser({
user: "mongo_monitor", pwd: "<pick a strong password>",
roles: [
{ role: "clusterMonitor", db: "admin" },
{ role: "read", db: "<your database>" },
{ role: "dbsheriff_view_users", db: "admin" },
{ role: "dbsheriff_profiler", db: "admin" }
]
});
db.createUser({
user: "mongo_kill_user", pwd: "<pick a strong password>",
roles: [{ role: "hostManager", db: "admin" }]
});
db.createUser({
user: "mongo_lock_user", pwd: "<pick a strong password>",
roles: [{ role: "userAdminAnyDatabase", db: "admin" }]
});
db.createUser({
user: "mongo_classify", pwd: "<pick a strong password>",
roles: [{ role: "read", db: "<your database>" }]
});
// enable profiling on every database you register (repeat per database)
use <your database>
db.setProfilingLevel(1, { slowms: 0 });
// recommended: make the profiler's ring large enough to survive a burst (64 to 256 MB; the default is 1 MB).
// It drops the existing profile documents, so do it once, before the first monitoring poll.
db.setProfilingLevel(0);
db.system.profile.drop();
db.createCollection("system.profile", { capped: true, size: 134217728 }); // 128 MB
db.setProfilingLevel(1, { slowms: 0 });
// Atlas database accounts — via the Atlas UI (Database Access) or
// Terraform (mongodbatlas_database_user, provider >= 2.0.0).
// monitor: clusterMonitor@admin + read@<db> + the custom role dbsheriff_profiler (below)
// kill: killOpSession@admin (NOT hostManager — Atlas rejects it, §2)
// lock: read@admin — a formality only, see the Service Account below
// classify: read@<db>
// LIVE-281: a custom role (Atlas UI: Database Access, Custom Roles; Terraform: mongodbatlas_custom_db_role; Admin API:
// POST /groups/{groupId}/customDBRoles/roles) with the one action the profiler health check needs, on the monitored database only,
// then give it to mongo_monitor next to its two built-in roles (Terraform: roles { role_name = "dbsheriff_profiler", database_name = "admin" }):
// { "roleName": "dbsheriff_profiler",
// "actions": [ { "action": "ENABLE_PROFILER", "resources": [ { "db": "<your database>", "collection": "" } ] } ] }
// profiling is still a plain database command on Atlas, not an Atlas UI setting:
use <your database>
db.setProfilingLevel(1, { slowms: 0 });
// recommended on Atlas too, and more so than on a self-managed server: the Atlas profiler ring is 1 MB, and DBSheriff's own
// polling writes into it, so the default ring holds about four minutes of operations (measured on an M10). A database user with the
// atlasAdmin role may recreate it (live-verified: profiling off, drop, create, profiling on all accepted); do it once, before the
// first monitoring poll, because it drops the existing profile documents. install_prereqs.py does this when run as such a user.
db.setProfilingLevel(0);
db.system.profile.drop();
db.createCollection("system.profile", { capped: true, size: 134217728 }); // 128 MB
db.setProfilingLevel(1, { slowms: 0 });
The Atlas Admin API Service Account is not a database user at all — it's an OAuth2 client-credentials principal, created in the Atlas UI under Organization → Access Manager → Applications → Service Accounts → Create. Grant it access to this specific project with these roles:
| Role (Atlas UI name) | Needed for | Notes |
|---|---|---|
| Project Database Access Admin | lock/unlock, real identity listing | The one role that lets DBSheriff actually modify database users through the Admin API — confirmed live this is sufficient for both, no broader role needed. |
| Project Cluster Log Viewer | — | Its name suggests it covers audit-log access; confirmed live it does not — kept here only because it's harmless and was part of this credential's original grant. Safe to omit. |
| Project Read Only | baseline | Included automatically by every other project role — listed for clarity, not strictly needed as its own grant. |
| Project Owner | PS09 (audit-logging check) specifically | The one operation genuinely needing broader access — confirmed live via a real 401 that Cluster Log Viewer does not cover the audit-log configuration endpoint despite its name. If you don't need PS09's Atlas-side accuracy, you can skip granting Owner and keep the credential scoped to just Database Access Admin. |
Also required: an API Access List entry on the Service Account itself
(separate from the cluster's own network access list, and — unlike the
cluster's list — Atlas rejects 0.0.0.0/0 here) for whichever IP address
DBSheriff's own containers/scripts actually call the Admin API from.
Store the resulting Client ID + Client Secret, together with the
project's group ID, as a JSON blob {"client_id", "client_secret",
"group_id"} in the service_account_key field of that server's registration,
sent through the Monitored Servers API (POST /api/monitored-servers). The
Admin Console form has no field for it on a MongoDB server, and the value is
kept encrypted in governance.monitored_servers.service_account_key_encrypted
(the registry's monitor-tier credential slot — unused by MongoDB's own
database connection, same "generic credential blob, engine decides the shape"
convention BigQuery/Databricks/Snowflake already use for their own API
credentials), so do not write the JSON into that column with SQL: DBSheriff
cannot decrypt it, logs bad service_account_key and treats the key as absent.
clusterMonitor + read@\<db> — the monitor account's whole job:
currentOp (live queries), serverStatus (connections/availability),
dbStats (space), and reading system.profile (completed queries,
once profiling is on). Identical on both deployment shapes. The profiler
health check adds the dbsheriff_profiler role below.dbsheriff_profiler (LIVE-281) — a minimal custom role that gives the monitor
the enableProfiler action on the monitored database and nothing else.
The producer's profiler health check runs profile -1 on the registered
database (to see whether the profiler is off or its slowms has drifted) and,
when it is, profile 1 with slowms: 0 again; MongoDB asks for
enableProfiler on that database for both. No built-in role carries only
that action (dbAdmin adds index and collection management), so it is a
custom role, scoped as narrowly as MongoDB allows: one privilege per
database, resource { db: <database>, collection: "" }, never the cluster
and never admin, config or local. What it lets the account do: the
monitor can change the profiling level of those databases (including
switching the profiler off, which is how the statement record of that
database would be silenced), so keep mongo_monitor's password as protected
as the other DBSheriff accounts. DBSheriff itself raises the R96 capture-gap
alert when it finds the profiler off. On self-managed servers
create_reset_dbuser.py and setup_dbsheriff.py make the role as part of the
monitor step (they update it when it exists, so a database made since is
covered by running the step again). On Atlas the account is created by
hand, so create the custom role there too: the Admin API action name is
ENABLE_PROFILER on the resource { "db": "<your database>", "collection": "" },
then add the role to mongo_monitor.
Amazon DocumentDB has no profiler (setProfilingLevel answers code 303,
§6), so the role is not made there and the monitor is created without it.
An account created before this role existed does not have the action:
the health check then skips with one INFO line (it asks again after an hour)
and a switched-off profiler is seen only as an empty system.profile. Run the
accounts step again for the monitor (scripts/create_reset_dbuser.py mongodb <host>,
choose reset, which also sets a new password for the account, or setup_dbsheriff.py; on Atlas add the role by hand), or, without a new
password, create the role as in §1 and run
db.grantRolesToUser("mongo_monitor", [{ role: "dbsheriff_profiler", db: "admin" }]).
After an Atlas restart that has already switched the profiler off, run
install_prereqs.py again.dbsheriff_view_users (self-managed only) — a minimal custom role,
not a built-in one. usersInfo({forAllDBs: true}) needs the
viewUser cluster action, which no built-in role grants on its own
without also granting far more (userAdminAnyDatabase or broader).
Also needs viewRole (2026-09-20) for the governance collector's
rolesInfo({showPrivileges: true}) call, which discovers which CUSTOM
roles grant access to a collection already approved as sensitive (for
DB Role auto-discovery, governance.role_table_grants) — without it,
that one collection step logs a warning and collects 0 roles rather
than failing the hourly cycle. This custom role has no Atlas
equivalent at all — confirmed live:
Atlas custom database roles simply have no viewUser-equivalent action
in their action list. There is no database-connection path to
cluster-wide identity listing on Atlas — that's exactly what the Atlas
Admin API Service Account's GET /groups/{groupId}/databaseUsers is
for instead.killOp needs
hostManager on self-managed; Atlas rejects hostManager outright
(ATLAS_INVALID_ROLE) and requires its own purpose-built
killOpSession role instead, which bundles killOp together with
currentOp's inprog action (self-managed's hostManager does
not include inprog — this split is exactly what broke MongoDB
kills entirely on self-managed for a while; DBSheriff now runs its
currentOp verification through the monitor connection instead of
the kill connection specifically because of this, see
its MongoDB kill routine).atlasAdmin (Atlas's real
superuser-equivalent role) gets a clean Unauthorized attempting
createUser/updateUser directly against the cluster. Atlas's own
docs describe this as rolling back any user modification attempted
outside its own control plane. So mongo_lock_user's database role is
effectively a formality on Atlas — the actual lock/unlock mechanism
goes entirely through the Atlas Admin API Service Account's
PATCH /groups/{groupId}/databaseUsers/{db}/{username} (Project
Database Access Admin role), independently live-verified: genuinely
revokes real read access (confirmed via a blocked connection
afterward, not just trusting the API's 200 response), then genuinely
restores it the same way.read@\<db> grant, same on both shapes
— MongoDB has no schema/catalog layer to read column names from the
way a relational engine does, so classification samples a bounded
number of real documents per collection to infer field names. No
broader grant is needed or helps.producer-mongo-pool) — no setup beyond §1-2 for mongo_monitorcurrentOp({active: true}) for live (currently-running) operations;
system.profile (via the same monitor connection, read@\<db>) for
completed ones, polled and watermarked the same way every other
event-log-shaped engine (BigQuery/Snowflake/Databricks) works — not a
cumulative-counter diff the way pg_stat_statements is. Completed
queries of the same shape (queryHash) within one poll cycle are
grouped into a single message with a real calls_delta count — this
does feed R38 BURST_CALL_VOLUME (live-verified: a genuine burst
triggered a real, server-side account lock via this exact path — see
docs/engines-feature-matrix.md's MongoDB row).
Admin/DDL commands (create, drop, collMod, createIndexes,
dropIndexes, renameCollection) are mapped to SQL-DDL-shaped strings
(the producer's DDL command table) so the shared,
keyword-based is_ddl classifier recognizes them without a
MongoDB-specific branch — without this, PRIVILEGED-strict-DDL scoring
never fires for MongoDB DDL at all.
Accounts that are never scored. The accounts DBSheriff itself logs in as are kept out of scoring, the statement audit and the coverage
figures: they are read from the user of each connection URI you registered (monitor, kill, lock, posture, classification), plus the
default names of this guide (mongo_monitor, mongo_kill_user, mongo_lock_user, mongo_classify), which are also exempt from the
honeytoken rule (R64). On an Atlas cluster (a *.mongodb.net host in the monitor URI) the principals Atlas runs itself are kept out too:
__system (replication, about 25 profile entries a minute), mms-monitoring-agent ($collStats on every collection every ten minutes) and
mms-automation. Your own exclusion list (Admin Console) is added on top of both.
Collections named inside a command. A statement names every collection it touches, not only the one it ran against: the from of a
$lookup or $graphLookup, the collection of a $unionWith, the target of $merge and $out, the output of mapReduce, and the
collection of distinct, count and findAndModify. A read of a sensitive collection (or of the honeytoken) through one of them is scored
as a read of that collection.
mongodb-governance-collector)Every 30s: system.profile → governance.mongodb_statement_audit (this
is MongoDB's statement-audit trail — self-managed Community has no
native audit log at all, confirmed structurally absent, not just
unconfigured: buildInfo().modules is an empty array and every
audit-related getParameter call fails outright).
Every hour: identities, config snapshot, protection, access log,
coverage, posture (the posture scan, §4 below). Identities use
usersInfo on self-managed (needs dbsheriff_view_users) and the Atlas
Admin API's GET /databaseUsers on Atlas (needs the Service Account,
§1) — both confirmed live returning real, correctly-normalized account
data. Each hourly step runs independently; one step failing no longer
blocks the others, including the posture scan itself.
PS01, PS05, PS06, PS07, PS08, PS09, PS11, PS13)| Code | Check | Self-managed | Atlas |
|---|---|---|---|
| PS01 | Default/demo account enabled | Real | NOT_CHECKED — the check lists users with usersInfo, which Atlas does not allow over a database connection (§2); the Service Account is not used for it |
| PS05 | Over-broad admin role membership | Real | NOT_CHECKED (same) |
| PS06 | Unencrypted connection observed | Real | NOT_CHECKED, with the reason in the note. Atlas refuses any connection that is not TLS (live-proved: a real non-TLS client connection attempt was flatly rejected), and the cumulative handshake counts of its mongod include Atlas's own internal infrastructure connections, so the check cannot say anything about your account's sessions. |
| PS07 | TLS not enforced server-wide | Real | Real — Atlas genuinely sets net.tls.mode=requireTLS at the mongod level |
| PS08 | Authentication not enforced | Real | Real — Atlas genuinely sets security.authorization=enabled |
| PS09 | Audit logging not enabled | Real (Community has none, by design) | Real, via the Admin API's GET /auditLog (needs the Service Account with Project Owner, §1) — confirmed live this reports the genuine, current Database Auditing toggle for the project, not a guess. Without Project Owner granted, PS09 and PS13 report NOT_CHECKED; the self-managed check, which is unreliable on Atlas (reads a flag Atlas's real audit config never sets), is used only when no Service Account is configured at all. |
| PS11 | Encryption at rest not enabled | Real | NOT_CHECKED, with the reason in the note. Atlas encrypts every cluster at rest by default (AES-256, cloud-provider-managed, cannot be disabled) and never sets the self-managed WiredTiger flag this check reads, so the check cannot say anything about the cluster. |
| PS13 | Audit log not adequately protected | NOT_CHECKED whenever PS09 fails |
NOT_CHECKED regardless of PS09's result — Atlas has no local-vs-syslog destination concept for this check to evaluate either way |
On Atlas, PS06 and PS11 are NOT_CHECKED — both are
platform-structural (the platform refuses non-TLS connections and encrypts
every cluster at rest; no Admin API endpoint changes that), not a missing
credential — and PS01 and PS05 are NOT_CHECKED too. PS07, PS08 and PS09
(with Project Owner) are as trustworthy on Atlas as on self-managed; PS13
is NOT_CHECKED as above.
mongo_classify, §1)Samples up to MONGO_SCAN_SAMPLE_SIZE (default 100) documents per
collection per database, extracting field names one level deep into
nested documents (e.g. address.street) — never field values unless
value-sampling is separately enabled. system.* collections (in
practice, just system.profile once profiling is on) are excluded from
scanning.
// run as mongo_monitor
db.adminCommand({ currentOp: true, active: true }); // should succeed, empty or real ops
db.<yourCollection>.find({}).limit(1); // read access confirmed
Expected producer log line on first successful poll:
[<server>][poll] nothing new (or a captured statement if one was
running) — an Unauthorized error here means the monitor account's
grants from §1 aren't in place yet, or profiling was never enabled
(db.getProfilingStatus() should show {"was": 1, "slowms": 0}).
// run as mongo_monitor, on the monitored database: the profiler health check's own read (needs the enableProfiler action)
db.getSiblingDB("<your database>").runCommand({ profile: -1 }); // { was: 1, slowms: 0, ok: 1 }
// "not authorized ... profile" means the dbsheriff_profiler role (§1) is not on this account yet: run the accounts step again
// self-managed only — confirms dbsheriff_view_users actually works
// (run as mongo_monitor)
db.adminCommand({ usersInfo: { forAllDBs: true } });
# Atlas Admin API Service Account — confirms the credential and roles
# actually work before wiring it into the registry. Replace {client_id}/
# {client_secret}/{group_id} with the real values.
b64=$(echo -n "{client_id}:{client_secret}" | base64 -w0)
token=$(curl -s --request POST --url https://cloud.mongodb.com/api/oauth/token \
--header "authorization: Basic $b64" \
--header 'content-type: application/x-www-form-urlencoded' \
--data 'grant_type=client_credentials' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -s --request GET \
--url "https://cloud.mongodb.com/api/atlas/v2/groups/{group_id}/databaseUsers" \
--header "Authorization: Bearer $token" \
--header 'Accept: application/vnd.atlas.2023-01-01+json'
A successful response lists every real database user on the project —
if this fails with 403, check the Service Account's API Access List
entry (§1) first; if it fails with 401, check its project roles.
# classification catalog access (run inside the admin container; `<env>` is the environment name you gave the installer, `prod` by default)
sudo docker exec -it dbsheriff-<env>-admin python scripts/classify_scan.py --engines mongodb --server <name>
Expect PENDING candidates for real field names matching
DBSheriff's PII name rules, and — once profiling is on and some traffic has
run — real statement rows in governance.mongodb_statement_audit within
30 seconds.
governance.mongodb_auth_events
stays empty, a failed-login burst is not detected, and an identity's last login is not known. Atlas's activity feed
(GET /groups/{groupId}/events) is project administration, not logins, and is not read. The only login trail Atlas offers is its
database audit log (Atlas Database Auditing, posture code PS09); DBSheriff does not read it in this release.role_table_grants is not collected on Atlas: the monitor account cannot read the privileges of custom roles there, and the
dbsheriff_view_users role that would allow it on a self-managed server cannot be created on Atlas.
PS06 and PS11 are reported as not checked on Atlas, with no fix needed and none possible via any credential. Both are platform-structural: PS06 would count Atlas's own internal infrastructure connections as if they were client traffic (live-proved no real client can bypass Atlas's enforced TLS at all); PS11 would check a self-managed encryption flag Atlas never sets, even though Atlas actually encrypts everything at rest by default. The reason is stored with each result and shown in the evidence pack.
GET /groups/{groupId}/events) is
administrative activity, not a per-connection auth trail — and DBSheriff
does not collect it in this release. The administrative events
DBSheriff records on MongoDB come from comparing the hourly user and
role snapshots (a user created or dropped, a role granted or revoked),
so a change such as network access is not recorded. Don't expect
DBSheriff's admin-events data to answer "who connected and when" the way
a true auth trail would.governance.
mongodb_statement_audit (sourced from system.profile) is the real
substitute; it captures every operation when profiling is set to
{slowms: 0}, not just slow ones.PATCH call returns success
immediately, but the actual role change takes real time to reach the
cluster nodes — Atlas's own rolling-update mechanism. Measured on a live
M10 cluster: a new connection was refused about 35 seconds after the lock
(up to about a minute and a half), and access was back about 85 seconds
after the restore. Until then the locked account can still connect and
run. The lock's audit row says so in its own detail (Atlas accepted the
change and applies it afterwards), and Unlock tells the approver that
access returns after about 85 seconds, so the audit trail no longer reads
as "locked" at the moment the request was made. Use the kill for an
operation that is still running.$limit: 1
first on a multi-document collection:
db.<coll>.aggregate([{$limit:1},{$addFields:{slow:{$function:{...}}}}])
— without it, $addFields+$function runs once per document, not
once total (live-verified: a nominal 6-second delay actually ran 155+
seconds against a 3000-document collection without the $limit: 1).calls_delta (grouped by queryHash, per-poll-cycle) only counts
same-shape repeats. the enrichment step's per-identity aggregate
(user_calls_rate_1m/5s/poll) sums calls across every query template
for that user in the same window, engine-agnostic, so a burst spread
across rotating query texts still counts as one burst.Both speak the MongoDB protocol and are not MongoDB. Everything below was measured on a live Amazon DocumentDB 5.0 cluster (TLS on, one
db.t3.medium instance) in October 2026.
Amazon DocumentDB: what DBSheriff does there
| Capability | On DocumentDB | Why |
|---|---|---|
Running operations ($currentOp, every second) |
Works. A 90-second aggregate was published as a running operation 2 seconds after it started and scored 0.9. | $currentOp is supported. |
| Completed statements, the statement audit, the Query Health scan | Not captured. Only an operation that is still running when a poll reads it is seen; a statement that finishes within a second, and DDL such as dropCollection, leave no trace. The console marks the server completed capture: off. |
DocumentDB has no system.profile: setProfilingLevel answers code 303 (the profiler writes to Amazon CloudWatch Logs through log exports, which DBSheriff does not read) and a capped collection cannot be created. |
Kill (killOp) |
The kill account needs hostManager@admin; killOp itself works (an operation was interrupted 5 seconds after the call). |
|
| Lock and unlock | Works with userAdminAnyDatabase@admin: a new login and an already open session were both refused at once; unlock restored the roles. |
|
| Classification scan | Works (read@\<db>). |
|
| Account Inventory, PS01 and PS05 | Work with the role of the next block (usersInfo across all databases). |
|
| Posture checks PS06 to PS09, PS11 and PS13: PS06 (unencrypted connections observed), PS07 (TLS), PS08 (authentication), PS09 and PS13 (audit log), PS11 (encryption at rest) | Not checked, with the reason beside each: DocumentDB does not support getCmdLineOpts or getParameter, and its serverStatus carries no TLS-handshake counts to compare for PS06. They are never reported as findings; check TLS, authentication, auditing and encryption in the AWS console (cluster parameter group, KMS key, log exports). The same holds for a Cosmos DB server, should one be registered by other means (it is not supported). |
|
role_table_grants (which custom roles reach a classified collection) |
Not collected. | The rolesInfo command needs the viewRole action, which DocumentDB cannot grant. |
| Configuration snapshot (compliance report, MG7) | Edition reads Amazon DocumentDB; authentication, TLS and bind address are not evaluated. | No start-up options (getCmdLineOpts). |
The accounts on DocumentDB. Create them as the cluster's master user, in the admin database. They are the self-managed accounts of §1 with
one difference, the dbsheriff_view_users role: DocumentDB refuses the viewRole action
(Cannot grant privileges for the viewRole action on the cluster resource.), so the guide's role cannot be created there, and without it
mongo_monitor cannot be created either (No role named dbsheriff_view_users@admin). Give the role viewUser alone:
// Amazon DocumentDB — run against the admin database as the master user
db.createRole({
role: "dbsheriff_view_users",
privileges: [ { resource: { cluster: true }, actions: ["viewUser"] } ], // no viewRole: DocumentDB refuses it
roles: []
});
db.createUser({ user: "mongo_monitor", pwd: "<pick a strong password>",
roles: [ { role: "clusterMonitor", db: "admin" }, { role: "read", db: "<your database>" },
{ role: "dbsheriff_view_users", db: "admin" } ] }); // no dbsheriff_profiler: DocumentDB has no profiler, nothing for enableProfiler to act on
db.createUser({ user: "mongo_kill_user", pwd: "<pick a strong password>", roles: [ { role: "hostManager", db: "admin" } ] });
db.createUser({ user: "mongo_lock_user", pwd: "<pick a strong password>", roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] });
db.createUser({ user: "mongo_classify", pwd: "<pick a strong password>", roles: [ { role: "read", db: "<your database>" } ] });
// do NOT run the profiling statements of §1 (setProfilingLevel, the capped system.profile): DocumentDB refuses them with code 303.
// install_prereqs.py / setup_dbsheriff.py report that error for the profiling prerequisite on a DocumentDB host: skip it.
scripts/create_reset_dbuser.py mongodb <cluster endpoint> with view_users selected creates the role with viewUser only when the host is a
*.docdb.amazonaws.com cluster, and also when DocumentDB answers the guide's role with the viewRole refusal above.
The running-operations grant. $currentOp on its own is allowed to every account, but DocumentDB authorises a pipeline with a second stage
after it (DBSheriff's running-time $match) against the admin database, so the monitor above gets Authorization failure (code 13) on that
pipeline. DBSheriff handles it: on that answer it runs $currentOp alone, filters by running time itself, remembers the choice for the server
(so the pool does not fail once a second) and writes one WARNING to the producer log naming the grant that makes the server do the filtering again.
Nothing has to be granted for monitoring to work; if you prefer the server to filter, give mongo_monitor the read role on admin (or
readAnyDatabase) in addition. With the fallback each poll reads every active operation of the node it is connected to.
The CA bundle. DocumentDB's certificate is signed by the Amazon RDS CA. Download the global bundle,
https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem, and register the connection string with the bundle's path inside the
DBSheriff containers:
mongodb://mongo_monitor:<url-encoded password>@<cluster endpoint>.docdb.amazonaws.com:27017/?authSource=admin&tls=true&tlsCAFile=/certs/mongodb-ca.pem&replicaSet=rs0&retryWrites=false
Every DBSheriff service that holds target credentials (the producer pool, the governance collector, the kill and lock paths, the classification
and posture scans) opens the connection, so the file must be mounted into all of them. The compose files mount one bundle per engine at
/certs/<engine>-ca.pem from the host file named by <ENGINE>_SSL_CA_HOST_PATH. For MongoDB set MONGODB_SSL_CA_HOST_PATH in the stack's
.env to the bundle on the host (for Amazon DocumentDB the Amazon RDS global bundle, global-bundle.pem, or a self-managed server's own CA) and
write tlsCAFile=/certs/mongodb-ca.pem in the connection string, as above; then docker compose up -d. MongoDB has the mount and no
MONGODB_SSL_CA variable: the CA travels in the connection string, so there is nothing for a variable to name, and a server set to verify is not
refused by DBSheriff for lack of one: the driver reports the certificate failure. (A deployment made before this release can name the path of
another engine's mount that holds the same bundle, for example tlsCAFile=/certs/mysql-ca.pem.)
The mark in the Monitored Servers list. A server on DocumentDB shows completed capture: off beside its host, with the sentence above as the
tooltip. DBSheriff knows the platform from the host name (a name ending .docdb.amazonaws.com): at once when the server was registered through the
API with that host, and for a server registered by its connection string alone (the console form has no Host field for MongoDB) within about an
hour, when the governance collector next reads the string and records the platform.
Azure Cosmos DB for the MongoDB API is not supported. Cosmos DB rejects the $currentOp aggregation DBSheriff reads running operations with
(Expected type string but found int), and has no killOp, no profiler and no user commands, so a registered server could only fail every
second. The Admin Console refuses a registration whose Host or connection string is a *.mongo.cosmos.azure.com (or its sovereign-cloud forms
.us, .cn, .de) or *.documents.azure.com host (HTTP 422, with a one-sentence explanation); setup_dbsheriff.py, create_reset_dbuser.py
and install_prereqs.py refuse such a host with the same sentence before they connect; and a producer pool leaves such a server out if it was
registered before. The vCore service (*.mongocluster.cosmos.azure.com) was not part of the probe that found these commands refused, so it is not
refused, and it is not a tested platform either. Use a self-managed MongoDB, MongoDB Atlas or Amazon DocumentDB server.
setup_dbsheriff.py, create_reset_dbuser.py and install_prereqs.py connect as your administrator (onboard_target.py does not handle MongoDB: it refuses the engine before it connects), and the administrator password and every new account's password cross that connection, so they verify the server's certificate by default (--ssl-mode verify-full; --ca-file <bundle> names the CA, for self-managed MongoDB tlsCAFile; Atlas is always mongodb+srv over verified TLS). A refused certificate stops the run and the message names the flag; --ssl-mode require encrypts without verifying (an explicit opt-down that prints a warning) and --ssl-mode disable --allow-plaintext sends everything in clear text (a trusted private link only). A generated password is never printed: it is stored in the registry (encrypted), or written to a new mode-600 file (--credentials-file) whose path is shown. Account names must match [A-Za-z][A-Za-z0-9_]*, and every database, schema or warehouse name the scripts discover is quoted as an identifier before it goes into a statement.