Enterprise Databricks controls
Identity propagation, Unity Catalog enforcement, approvals, audit lineage, durable state, cost controls, secret handling, and deployment hardening.
Fabric Harness adds runtime controls around Databricks calls. Those controls are defense in depth: Unity Catalog and Databricks resource permissions remain authoritative.
Identity modes
| Mode | Use | Fabric API |
|---|---|---|
| App service principal | Databricks Apps and unattended production workloads | appServicePrincipalFromEnv() |
| OAuth M2M | External services acting as one application | databricksIdentity({ kind: 'service-principal' }) |
| On-behalf-of user | Preserve the signed-in user's grants | onBehalfOfFromHeaders() |
| Personal access token | Local development or controlled single-user testing | kind: 'pat' |
Token providers are resolved at call time and refresh before expiry. Identity labels, not tokens, are placed in actors, submissions, lineage, and cost records.
For Databricks Apps ingress, use databricksAppsOidcAuthenticator() as the Node server
authenticate hook. It validates workspace JWT signatures, issuer, audience, expiry, and signing-key
rotation before mapping the forwarded user email to both the Fabric actor and Unity Catalog
principal. See Authentication and RBAC.
When Databricks Apps forwards an OBO access token instead of an OIDC identity token, use the narrow Apps authorization entrypoint. It validates the token against the workspace current-user API, returns only safe identity and lifetime metadata, assigns an opaque tenant per user, and caches by a token digest bounded by token expiry:
import { createDatabricksAppUserAuthenticator } from
'@fabric-harness/databricks/app-user-authorization';
import { startDevServer } from '@fabric-harness/node';
const authenticate = createDatabricksAppUserAuthenticator({
host: process.env.DATABRICKS_HOST!,
});
await startDevServer({ authenticate });Missing forwarded authorization falls through so another configured authenticator may run. Invalid
or unauthorized forwarded tokens fail closed. The raw token is never returned, logged, or used as a
cache key. Applications that need authenticated user identity but do not pass an OBO token to
downstream Databricks APIs can opt into trustForwardedUserIdentity. It maps the integrity-protected
x-forwarded-user, x-forwarded-email, and x-forwarded-preferred-username ingress headers to a
natural-person principal. Generated Databricks App bundles enable this trust mode because they are
reachable only through Apps ingress; a forwarded email or preferred username is required so an M2M
caller with only x-forwarded-user is not reclassified as a person.
trustForwardedUserIdentity, trustForwardedServicePrincipal,
appPrincipalId, and appAuthorizedCallerIds are explicit ingress-trust controls; enable them only
when the server is exclusively reachable through Databricks Apps. See the runnable
examples/with-databricks-simple/app-auth.ts path for deterministic output and failure behavior.
Enforcement flow
Text alternative and Mermaid source
Diagram flow: Tool request leads to Definition and invocation policy; A leads to Allowed?; E leads No Reject and audit; E leads Yes Approval required?; H leads Yes Durable approval wait; WAIT leads to Decision; DEC leads Deny or expire DENY; DEC leads Approve Call Databricks; H leads No CALL; CALL leads to Unity Catalog and resource ACLs; UC leads Denied DENY; UC leads Allowed Redacted result.
flowchart LR
R[Tool request] --> A[Definition and invocation policy]
A --> E{Allowed?}
E -->|No| DENY[Reject and audit]
E -->|Yes| H{Approval required?}
H -->|Yes| WAIT[Durable approval wait]
WAIT --> DEC{Decision}
DEC -->|Deny or expire| DENY
DEC -->|Approve| CALL[Call Databricks]
H -->|No| CALL
CALL --> UC[Unity Catalog and resource ACLs]
UC -->|Denied| DENY
UC -->|Allowed| RESULT[Redacted result]
RESULT --> LINEAGE[Lineage, telemetry, and cost]
classDef decision fill:#fef3c7,stroke:#d97706,color:#422006
classDef allow fill:#dcfce7,stroke:#16a34a,color:#052e16
classDef deny fill:#fee2e2,stroke:#dc2626,color:#450a0a
class E,H,DEC decision
class CALL,UC,RESULT,LINEAGE allow
class DENY denyControls to configure
- Use a least-privilege service principal or a user OBO token for each request.
- Restrict outbound network policy to the workspace host and any explicitly required services.
- Add catalog allowlists as defense in depth; do not treat them as a substitute for UC grants.
- Route write and execute tools to a data-steward approval audience.
- Persist sessions, submissions, and streams in Lakebase for restart recovery.
- Send governed tool lineage to an audit sink, MLflow, OpenTelemetry, or Lakebase telemetry tables.
- Apply estimated and actual-cost tenant budgets, then reconcile against system-table usage.
- Keep secrets in Databricks App resources, environment-backed secret references, or an external secret manager. Never put credentials in prompts, tool inputs, or lineage labels.
- Build with provenance and SBOM output, inspect the v2 manifest, and require an API token on production artifact routes.
Lakebase credential exchange
lakebaseClient() does not use a workspace OAuth token as the Postgres password. It exchanges the
workspace token for a database credential using the endpoint resource name, refreshes that credential
early with jitter, single-flights concurrent refreshes, and supplies it to the connection pool.
const app = databricksApp();
const server = await startDevServer({
host: '0.0.0.0',
port: Number(process.env.DATABRICKS_APP_PORT ?? 8080),
...(await app.serverOptions()),
});serverOptions() injects the Lakebase session, submission, and conversation-stream stores into the
shared Node server. See Lakebase for configuration.
Schema ownership in automated deployments
A production deployment usually has two database principals:
- the release principal used by CI to deploy resources and apply schema changes;
- the App principal used by the running Databricks App to read and write durable state.
Keep one release principal as the stable owner of the Harness tables. Apply new migrations with that principal before rolling out an App version, then grant the App principal only the DML and sequence permissions it needs. Do not transfer table ownership to every new deployment identity.
-- Run as the stable schema owner. Quote UUID-shaped service-principal names.
GRANT USAGE ON SCHEMA public TO "<app-service-principal-client-id>";
GRANT SELECT, INSERT, UPDATE, DELETE
ON TABLE fh_submission_telemetry, fh_lineage
TO "<app-service-principal-client-id>";
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public
TO "<app-service-principal-client-id>";At startup, Fabric resolves existing telemetry tables through PostgreSQL's relation catalog. This is
important because the App principal's first search_path schema can differ from the schema that owns
the shared tables. Fabric skips owner-only ALTER TABLE and CREATE INDEX statements when the
resolved objects already exist. If a required object is genuinely absent, migration still fails
rather than pretending the schema is current.
Submission telemetry sinks are failure-isolated: an MLflow or Lakebase observer error is logged but cannot terminate the App process or change an agent result. Treat missing telemetry as an operations failure anyway. The protected certification gate must prove that lifecycle events and lineage rows were actually written before promoting the release.
Databricks connectors and sandboxes
Choose between the Databricks SQL sandbox, Unity Catalog Volume connectors, workspace sources, attachment storage, and general-purpose compute sandboxes.
Workspace compatibility
Evidence-based Databricks cloud, region, authentication, API, App, Lakebase, and runtime compatibility.