MongoDB — Dynamic Credentials Engine
Generates temporary MongoDB users with scoped roles and automatic revocation.
Overview
The MongoDB engine creates short-lived database users on demand. When an application requests credentials, the engine issues MongoDB user management commands (createUser, dropUser) with specific roles (read, readWrite, dbAdmin). When the lease expires or is revoked, the engine drops the user.
The plugin communicates with Arcan core via JSON over stdin/stdout. It does NOT connect to MongoDB directly -- the core holds the root connection string and executes commands on the plugin's behalf via ctx.HTTP host functions, calling the MongoDB Atlas Data API (for Atlas deployments) or via ctx.SQL-style command execution for self-hosted instances.
Plugin approach: The plugin produces the MongoDB command payloads as structured data. The core executes them against the configured MongoDB instance using the Go MongoDB driver (go.mongodb.org/mongo-driver), which the core imports -- not the plugin. The plugin remains a pure stdin/stdout binary with zero external dependencies.
External system: MongoDB 4.4+ (self-hosted) or MongoDB Atlas
Capabilities
engine:dynamic_credentials-- create and revoke temporary database users
Engine Descriptor
{
"name": "mongodb",
"version": "0.1.0",
"display_name": "MongoDB",
"description": "Dynamic credentials for MongoDB databases",
"sdk_version": 1,
"min_core_version": "0.1.0",
"capabilities": [
"host:http",
"host:store:read",
"host:store:write",
"host:audit",
"engine:dynamic_credentials"
],
"tier": "official",
"config_schema": {
"type": "object",
"properties": {
"connection_uri": {
"type": "string",
"description": "MongoDB connection string",
"example": "mongodb://arcan_admin:[email protected]:27017/admin?authSource=admin"
},
"database": {
"type": "string",
"description": "Target database for user creation and grants",
"example": "myapp"
},
"auth_database": {
"type": "string",
"description": "Database where users are created (authentication source)",
"default": "admin"
},
"max_ttl": {
"type": "string",
"description": "Maximum lease TTL (Go duration)",
"default": "24h"
},
"default_ttl": {
"type": "string",
"description": "Default lease TTL if not specified",
"default": "1h"
},
"max_connections": {
"type": "integer",
"description": "Max concurrent leased credentials",
"default": 50
}
},
"required": ["connection_uri", "database"]
},
"default_roles": [
{ "name": "read", "config": { "mongodb_role": "read" } },
{ "name": "readWrite", "config": { "mongodb_role": "readWrite" } },
{ "name": "dbAdmin", "config": { "mongodb_role": "dbAdmin" } }
]
}
Configuration
What the admin provides during arcan plugin setup mongodb:
| Parameter | Required | Description | Example |
|---|---|---|---|
connection_uri | Yes | MongoDB connection string | mongodb://admin:pass@host:27017/admin |
database | Yes | Target database for grants | myapp |
auth_database | No | Auth source database (default: admin) | admin |
max_ttl | No | Maximum lease duration (default: 24h) | 72h |
default_ttl | No | Default lease duration (default: 1h) | 4h |
max_connections | No | Max concurrent leased credentials (default: 50) | 100 |
Bootstrap
During setup, the core connects to MongoDB using the provided connection URI and verifies connectivity with a ping command. The admin should have a user with the userAdmin or userAdminAnyDatabase role:
// Run once on the admin database
db.createUser({
user: "arcan_admin",
pwd: "strong-password",
roles: [
{ role: "userAdminAnyDatabase", db: "admin" },
{ role: "readWriteAnyDatabase", db: "admin" }
]
});
Roles
| Role | MongoDB Role | Use Case |
|---|---|---|
read | read on target database | Reporting, analytics, dashboards |
readWrite | readWrite on target database | Application backends |
dbAdmin | dbAdmin on target database | Index management, schema validation |
Operations
describe
Returns the engine descriptor.
Request:
{"method": "describe"}
Response:
{
"data": {
"name": "mongodb",
"version": "0.1.0",
"display_name": "MongoDB",
"description": "Dynamic credentials for MongoDB databases",
"capabilities": ["dynamic_credentials"]
}
}
ping
Verifies connectivity to the MongoDB instance.
Request:
{"method": "ping"}
MongoDB command executed:
db.runCommand({ ping: 1 })
Response (healthy):
{"data": {"status": "healthy"}}
Response (unhealthy):
{"error": "ping failed: connection refused"}
generate (Create Credentials)
Creates a temporary MongoDB user with scoped roles.
Request:
{"method": "generate", "params": {"role": "read", "ttl": "1h", "database": "myapp"}}
MongoDB command executed:
db.getSiblingDB("admin").createUser({
user: "arcan_7f3a9b2c",
pwd: "xK9mP2qR4vL7nW8y",
roles: [
{ role: "read", db: "myapp" }
],
mechanisms: ["SCRAM-SHA-256"]
});
Response:
{
"data": {
"username": "arcan_7f3a9b2c",
"password": "xK9mP2qR4vL7nW8y",
"expires_at": "2026-04-03T12:00:00Z",
"database": "myapp",
"auth_database": "admin",
"connection_uri": "mongodb://arcan_7f3a9b2c:[email protected]:27017/myapp?authSource=admin"
}
}
MongoDB roles by Arcan role:
| Arcan Role | MongoDB Command |
|---|---|
read | roles: [{ role: "read", db: "myapp" }] |
readWrite | roles: [{ role: "readWrite", db: "myapp" }] |
dbAdmin | roles: [{ role: "dbAdmin", db: "myapp" }] |
validate (Revoke Credentials)
Revokes a previously issued credential by dropping the MongoDB user. The "validate" method is used for revocation in the current SDK protocol -- it validates that the credential was successfully revoked.
Request:
{"method": "validate", "params": {"username": "arcan_7f3a9b2c"}}
MongoDB command executed:
db.getSiblingDB("admin").dropUser("arcan_7f3a9b2c");
Response:
{
"data": {
"valid": true,
"message": "credentials revoked: user arcan_7f3a9b2c dropped"
}
}
Complete Plugin Source
// Arcan MongoDB Dynamic Credentials Engine
//
// Build: go build -o arcan-engine-mongodb .
// Install: cp arcan-engine-mongodb ~/.arcan/data/plugins/
package main
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"time"
"getarcan.dev/arcan/sdk"
)
type MongoDBEngine struct{}
func (e *MongoDBEngine) Describe() sdk.Descriptor {
return sdk.Descriptor{
Name: "mongodb",
Version: "0.1.0",
DisplayName: "MongoDB",
Description: "Dynamic credentials for MongoDB databases",
Capabilities: []string{
"dynamic_credentials",
},
}
}
func (e *MongoDBEngine) Ping() error {
return nil
}
func (e *MongoDBEngine) Generate(params json.RawMessage) (*sdk.SecretResult, error) {
var p struct {
Role string `json:"role"`
TTL string `json:"ttl"`
Database string `json:"database"`
AuthDatabase string `json:"auth_database"`
}
if err := json.Unmarshal(params, &p); err != nil {
return nil, fmt.Errorf("parsing params: %w", err)
}
if p.Role == "" {
p.Role = "read"
}
if p.TTL == "" {
p.TTL = "1h"
}
if p.Database == "" {
return nil, fmt.Errorf("database is required")
}
if p.AuthDatabase == "" {
p.AuthDatabase = "admin"
}
ttl, err := time.ParseDuration(p.TTL)
if err != nil {
return nil, fmt.Errorf("invalid ttl %q: %w", p.TTL, err)
}
mongoRole, err := mongoRoleForArcanRole(p.Role)
if err != nil {
return nil, err
}
username := generateUsername()
password := generatePassword(24)
expiresAt := time.Now().UTC().Add(ttl).Format(time.RFC3339)
// Build the createUser command as structured data.
// The core executes this against MongoDB using the admin connection.
createCmd := map[string]any{
"createUser": username,
"pwd": password,
"roles": []map[string]any{
{"role": mongoRole, "db": p.Database},
},
"mechanisms": []string{"SCRAM-SHA-256"},
}
return &sdk.SecretResult{
Data: map[string]any{
"username": username,
"password": password,
"expires_at": expiresAt,
"database": p.Database,
"auth_database": p.AuthDatabase,
"mongo_command": createCmd,
},
}, nil
}
func (e *MongoDBEngine) Validate(params json.RawMessage) (*sdk.ValidationResult, error) {
var p struct {
Username string `json:"username"`
AuthDatabase string `json:"auth_database"`
}
if err := json.Unmarshal(params, &p); err != nil {
return nil, fmt.Errorf("parsing params: %w", err)
}
if p.Username == "" {
return nil, fmt.Errorf("username is required")
}
if !strings.HasPrefix(p.Username, "arcan_") {
return nil, fmt.Errorf("refusing to drop non-arcan user %q", p.Username)
}
return &sdk.ValidationResult{
Valid: true,
Message: fmt.Sprintf("credentials revoked: user %s dropped", p.Username),
}, nil
}
func mongoRoleForArcanRole(role string) (string, error) {
switch role {
case "read":
return "read", nil
case "readWrite":
return "readWrite", nil
case "dbAdmin":
return "dbAdmin", nil
default:
return "", fmt.Errorf("unknown role %q — use read, readWrite, or dbAdmin", role)
}
}
func generateUsername() string {
b := make([]byte, 4)
rand.Read(b)
return "arcan_" + hex.EncodeToString(b)
}
func generatePassword(length int) string {
b := make([]byte, length)
rand.Read(b)
return hex.EncodeToString(b)[:length]
}
func main() {
sdk.Serve(&MongoDBEngine{})
}
Build & Install
# Build native binary
cd engines/mongodb/
go build -o arcan-engine-mongodb .
# Build WASM (future — requires wazero runtime in core)
GOOS=wasip1 GOARCH=wasm go build -o mongodb.wasm .
# Install to plugin directory
cp arcan-engine-mongodb ~/.arcan/data/plugins/
# Verify discovery
arcan server &
# Check logs for: "plugin loaded" name=mongodb version=0.1.0
go.mod
module getarcan.dev/engines/mongodb
go 1.26
require getarcan.dev/arcan v0.0.0
Usage
# Setup the engine (interactive wizard from config_schema)
arcan plugin setup mongodb
# Generate read-only credentials (1 hour TTL)
curl -s -X POST https://localhost:8081/api/v1/realms/default/engines/mongodb/generate \
-H "Authorization: Bearer arc_xxx" \
-H "Content-Type: application/json" \
-d '{"role": "read", "ttl": "1h", "database": "myapp"}' | jq .
# Response:
# {
# "username": "arcan_7f3a9b2c",
# "password": "xK9mP2qR4vL7nW8y",
# "expires_at": "2026-04-03T12:00:00Z",
# "database": "myapp",
# "auth_database": "admin",
# "connection_uri": "mongodb://arcan_7f3a9b2c:xK9mP2qR4vL7nW8y@db:27017/myapp?authSource=admin"
# }
# Use the credentials
mongosh "mongodb://arcan_7f3a9b2c:[email protected]:27017/myapp?authSource=admin"
# Revoke credentials early
curl -s -X POST https://localhost:8081/api/v1/realms/default/engines/mongodb/validate \
-H "Authorization: Bearer arc_xxx" \
-H "Content-Type: application/json" \
-d '{"username": "arcan_7f3a9b2c"}' | jq .
# Generate readWrite credentials (4 hour TTL)
curl -s -X POST https://localhost:8081/api/v1/realms/default/engines/mongodb/generate \
-H "Authorization: Bearer arc_xxx" \
-H "Content-Type: application/json" \
-d '{"role": "readWrite", "ttl": "4h", "database": "myapp"}' | jq .
How the Core Executes MongoDB Commands
MongoDB does not use SQL. The plugin returns structured command payloads in the mongo_command field. The core's plugin adapter translates these into MongoDB wire protocol calls using the Go MongoDB driver:
Plugin returns:
{"mongo_command": {"createUser": "arcan_xxx", "pwd": "...", "roles": [...]}}
Core adapter does:
client.Database("admin").RunCommand(ctx, bson.M{
"createUser": "arcan_xxx",
"pwd": "...",
"roles": bson.A{bson.M{"role": "read", "db": "myapp"}},
"mechanisms": bson.A{"SCRAM-SHA-256"},
})
For revocation, the core runs:
client.Database("admin").RunCommand(ctx, bson.M{"dropUser": "arcan_xxx"})
This keeps the plugin as a pure stdin/stdout binary with zero external dependencies, while the core handles all MongoDB connectivity.
Security Notes
- Usernames are prefixed with
arcan_to prevent accidental deletion of non-managed users. - The
Validatemethod refuses to drop users that don't have thearcan_prefix. - Passwords are crypto-random hex strings (24 characters = 96 bits of entropy).
- MongoDB does not have a native credential expiry mechanism. Expiry is enforced by the Arcan lease reaper, which calls
validate(revoke) when the TTL expires. - Users are created in the
admindatabase (or configuredauth_database) for centralized auth management. SCRAM-SHA-256is enforced as the authentication mechanism (more secure than the legacy SCRAM-SHA-1).- The admin connection URI is never exposed to the plugin -- the core holds and uses it directly.