Database Administration
CBOM Secure stores everything in MongoDB. This page is what is stored where, how it is secured, how it grows, and the operations a database administrator will be asked to perform.
Backup and restore procedures are on Backup and Recovery; replica set construction is in High Availability Runbooks. This page covers the database itself.
Two databases, not one
| Database | Holds |
|---|---|
CBOMData | The product: inventory, hosts, tasks, templates, policies, configuration, dashboards, users |
CBOMAudit | The evidence: the audit trail, material histories, deployment histories |
Both must be backed up, and both must be restored together. CBOMAudit is the one that gets forgotten, because nothing in the console suggests there is a second database. Restoring only CBOMData gives a platform that works perfectly and has lost every history timeline, every audit record and every deployment history — and nothing reports the loss, because each of those reads simply returns nothing.
The separation is deliberate: the audit trail has a different retention policy, a different access model and a different growth rate from the operational data, and it must not be editable by anything that can edit the product's own state.
The collections that matter
In CBOMData:
| Collection | Holds | Growth |
|---|---|---|
CryptoMaterial | The deduplicated inventory — the product's primary data | With the estate |
CMDeposit | Raw sensor results awaiting deduplication | Transient; should drain |
CMCollectRuns | One row per collection call, with the ingestion counters | With scan frequency |
DiscoveryLogs | Sensor log records | With scan frequency; TTL applies |
HostsInfo | Targets and their encrypted credentials | With the estate |
TaskDeployment, DeployManagement | Tasks and templates | Slowly |
Configs | The configuration schema and the form that edits it | Two documents |
Admin | Users, roles, groups and the permission map | Slowly |
Widgets, Layouts, DashboardTemplates | Dashboards | Slowly |
AnalyticRules | Algorithm rankings and scoring inputs | Fixed |
CMTags, CMTagScopes | Tag definitions and named scopes | Slowly |
SensorSchema | Registered sensor schemas, seeded at startup | Fixed |
Counters | The atomic id allocator | A few documents |
In CBOMAudit:
| Collection | Holds |
|---|---|
AuditLog | Who did what — one flat document per audited action |
CMHistories | Per-material change timeline |
TaskDeploymentHistories | One document per task, holding its runs |
CMDeposit should be close to empty between scans. It is a staging collection that the deduplication pass consumes. Rows that persist mean the pass is not running, or is failing — and the materials they represent are not in the inventory yet. A growing CMDeposit is the earliest sign that ingestion has stalled, and it is worth a monitor.
Security
Authentication on, always. An exposed MongoDB with no authentication is the single worst configuration mistake available here — the inventory names every weak key in the estate, which makes it a map for an attacker rather than merely a data loss.
Bind to the platform interface, not to everything. The deployment's host port binding defaults to loopback deliberately. Changing it to 0.0.0.0 publishes the database to every network the host is on, with only the root password in front of it. If the application tier is on another host, bind to the specific interface it reaches and firewall the port to that host alone.
Give the application its own user, with readWrite on the two databases and nothing more. It does not need dbAdmin, clusterAdmin or root. Keep the root account for administration and do not use it from the application.
use admin
db.createUser({
user: "cbomapp",
pwd: "<strong password>",
roles: [
{ role: "readWrite", db: "CBOMData" },
{ role: "readWrite", db: "CBOMAudit" }
]
})
Encrypt at rest. Use the storage engine's encryption, or full-disk encryption on the volume. Target credentials are separately encrypted by the platform before they are stored, but the inventory itself is not, and the inventory is sensitive on its own account.
Encrypt in transit between the application tier and the database wherever they are on different hosts.
Indexes
Indexes are created by the platform at startup — the application ensures what it needs and does not require an administrator to build them. There are unique constraints on task and template names, compound indexes behind the notification engine and the deployment histories, and two time-to-live indexes.
Do not drop an index you did not create. A missing unique index does not produce an error; it produces duplicate task names and a second document that quietly shadows the first.
Do add indexes for your own reporting. If you query the inventory directly for reports, index for those queries — but add them to a secondary first and watch the effect on write latency before promoting the practice.
Retention and growth
Three collections expire their own data through TTL indexes:
| Collection | Controlled by |
|---|---|
AuditLog | The audit retention setting |
NotificationTriggerEvents | The notification trigger retention setting |
NotificationOutbox | The notification outbox retention setting |
The audit TTL is on a date field, not on the timestamp string. The record carries both an ISO-8601 string for reading and a real date for expiry. This matters if you ever rebuild the index by hand: MongoDB accepts expireAfterSeconds on a string field and then expires nothing at all, silently, forever. Point it at the date field.
Everything else grows until something removes it. The ones that grow fastest are DiscoveryLogs and CMCollectRuns, both driven by scan frequency rather than estate size — a nightly scan of a small estate generates more of these than a monthly scan of a large one.
Capacity planning. Watch the working set rather than the total size: query latency degrades when the working set stops fitting in memory, long before storage fills. Size from the Sizing guide and monitor the pressure signal, not the disk.
Routine operations
Health check:
db.adminCommand({ ping: 1 })
db.stats()
db.CryptoMaterial.estimatedDocumentCount()
Find what is growing:
db.getCollectionNames().map(c => ({
name: c,
size: db[c].stats().size,
count: db[c].estimatedDocumentCount()
})).sort((a, b) => b.size - a.size)
Reclaim space. MongoDB does not return disk to the filesystem when documents expire. On a replica set, resynchronise one secondary at a time from scratch and step the primary down last — that compacts without an outage. On a single node, compact takes a lock; schedule it.
Check for a stalled pipeline:
db.CMDeposit.estimatedDocumentCount() // should be near zero at rest
db.CMCollectRuns.find().sort({collectedAt:-1}).limit(5)
Seeding and migration
A fresh install is seeded from the files in the deployment's initial-data directory by a one-shot container that runs the seeding tool. That covers the configuration schema, the dashboard widgets, the standard policies, the menu structure, the algorithm rankings and the initial administrative records.
The seed is a first-boot operation and a no-op afterwards. Once a document exists, the bootstrap leaves it alone — which is correct, since it must not overwrite a customer's configuration, and is also why upgrading an existing database needs explicit migration rather than a restart. See Upgrade and Migration.
Troubleshooting
The platform starts and reports no database. Authentication, binding or firewall. Test from the application host with the application's own credentials, not from the database host as root — that tests a different thing and usually succeeds.
Everything works but history pages are empty. CBOMAudit was not restored, or the application user has no rights on it. This is the most common consequence of a partial restore.
Writes slow down over time. Either the working set has outgrown memory, or an index was added that is not earning its write cost. Check working-set pressure first.
CMDeposit keeps growing. The deduplication pass is not completing. Check the collector worker and the broker before the database.
Duplicate task or template names appear. A unique index is missing. Deduplicate the documents by hand, then let the application recreate the index on restart.
A TTL index exists and nothing expires. It is on a string field. Drop it and recreate it on the date field.
Disk is full but document counts are falling. Expired documents have freed space inside the files that has not been returned to the filesystem. Compact, or resynchronise members in turn.