Skip to main content

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:

ParameterRequiredDescriptionExample
connection_uriYesMongoDB connection stringmongodb://admin:pass@host:27017/admin
databaseYesTarget database for grantsmyapp
auth_databaseNoAuth source database (default: admin)admin
max_ttlNoMaximum lease duration (default: 24h)72h
default_ttlNoDefault lease duration (default: 1h)4h
max_connectionsNoMax 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

RoleMongoDB RoleUse Case
readread on target databaseReporting, analytics, dashboards
readWritereadWrite on target databaseApplication backends
dbAdmindbAdmin on target databaseIndex 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 RoleMongoDB Command
readroles: [{ role: "read", db: "myapp" }]
readWriteroles: [{ role: "readWrite", db: "myapp" }]
dbAdminroles: [{ 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 Validate method refuses to drop users that don't have the arcan_ 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 admin database (or configured auth_database) for centralized auth management.
  • SCRAM-SHA-256 is 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.