Connection governance¶
Register every external connection edge, gate it deny-by-default, broker credentials only after a permit, and sign off a reviewable YAML config — with a live security posture score showing how safe your outbound perimeter is and what to fix next.
Audience: Organisation administrators and IT security reviewers.
When to reach for this¶
-
Inventory every edge
One registry of outbound and inbound edges: integrations, webhooks, platform events, and warehouse query.
-
Gate at a single chokepoint
Enforce allow / deny policy in one place and audit every decision (permit / deny + reason).
-
Sign off the config
IT signs off a human-readable YAML artifact (SHA-256 hash) before it counts as enforced.
-
See your posture
A live 0–100 security posture score with ranked risks and the next action to take.
The catalog starts empty
An empty catalog scores 0 (Exposed). The perimeter is only governed once you seed connections and set policy — until then, nothing is registered at the gate.
How the gate works¶
Every outbound or inbound request passes through one deny-by-default chokepoint. A permit lets the connection proceed (for an HTTP call, the broker first fetches its credentials from Key Vault); a deny blocks it. Either way, the decision and its reason are written to the audit log.
flowchart LR
REQ["Outbound / inbound request<br/>integration · webhook · event · warehouse query"] --> GATE
GATE{{"Governance gate<br/>state allowed? · RBAC · egress allowlist<br/>schedule window · rate limit"}}
GATE -->|permit| BROKER["Credential Broker<br/>resolves secret from Key Vault"]
GATE -->|deny| BLOCK["Request blocked"]
BROKER --> TARGET["Connection proceeds to the edge"]
GATE -.writes.-> AUDIT[("Decision log<br/>permit / deny + reason")]
classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
class GATE accent
Figure 1 — The governance gate. Requests hit one deny-by-default chokepoint; a permit brokers credentials from Key Vault, a deny blocks the request, and every decision is logged with its reason.
Why teams reach for this
Credentials are only resolved after a permit — never before, never in plaintext on disk — and every decision at the gate is recorded, so you get one auditable answer to "what is Datahub allowed to connect to, and did this request get through?"
Setup — what an admin does once¶
| Role | Grants |
|---|---|
connectiongovernance.read |
View the catalog, audit log, and posture score. |
connectiongovernance.write |
Configure and draft connection edges. |
connectiongovernance.admin |
Apply the YAML config (enforce it), and approve another admin's connection. |
- Grant the roles above via role groups.
- Open Administration → Connection governance.
- Configure with the wizard: network posture → catalog (seed from integrations) → auth / credentials → policy → review & apply.
- Apply as an admin so an enforced config artifact is stored with a version hash IT can sign off.
Allowing a connection that leaves Datahub¶
Allow one connection from the admin page¶
On Connection governance, choose Allow a connection. Outbound takes a URL, method, path, how it signs in (None is a named choice, with a box to say it is intended) and a name. Inbound takes an IP address or range and a name. After saving, the page says the connection is Waiting for approval by another admin, or, if you are the only admin, that you may approve it in your tasks. The list shows each connection as Waiting for approval, Allowed, Refused or Removed, links a waiting one to its review, and Remove archives a connection (Show removed lists them again). The review shows what is being allowed: direction, host or source, method, path and sign-in. The YAML wizard stays as the bulk import.
A connection that reaches outside Datahub, inbound or outbound, is allowed by a second admin's approval, never by the person who submitted it. Through the API it is created denied, and saving it as allowed is refused:
- Submit it for review:
POST /connectiongovernancemodule/connections/by-key/{key}/submit-for-review(connectiongovernance.write). It stays denied while the review is open. The response'sreviewsays it iswaiting, names its task (review_task_id), and setssubmitter_may_decidewhen you are the only admin and may approve it yourself. - Another
connectiongovernance.adminapproves it on their task. The review waits in the team inbox of a business role that grantsconnectiongovernance.admin: the narrowest one, and Administrator when no other does. Approving allows the connection exactly as it was reviewed, and adds it to the enforced config in the same step, so it doesn't show as config drift (see Config sign-off & enforcement). Rejecting leaves it denied, with the reviewer's reason in the audit log.
While a connection is allowed or under review, where it connects to can't be changed: its system, capability, integration, operation, sign-in method, zone, direction, transport, and allowed hosts and sources. To change an allowed one, deny it first, then submit the change for review. While a review is open, wait for its decision: once rejected, change it and submit again. Its secret reference and the roles that may use it stay editable, and denying it is always possible.
Denying a connection stops it until the change is approved. For webhook-ingress, the switch for every inbound webhook, that would pause all of them, so add a source to its list through the YAML import instead (below).
Request approval from the list¶
A row that is not allowed and not under review (never sent, withdrawn or refused) has a Request approval action, unless it is internal or removed. It asks you to confirm, says what approval allows, and sends the connection for review (step 1 above). The message afterwards says where the review went: to another admin's tasks or, if you are the only admin, to your own. A refused request says why: the connection is already waiting, is internal, allowed or removed, or its settings are not complete yet. In the last case, fix its settings and request approval again.
webhook-ingress is the organisation's switch for inbound calls, and it starts denied. While it is not allowed, every inbound call is refused, even from a source that is allowed. The connection list and the inbound tab of Allow a connection say so. The notice above the list has its own Request approval, and while the switch waits for a decision, the notice says so and links to its review. The review is named Inbound calls for the organisation: approving it switches inbound calls on for everyone, and a caller still needs a source with its own approval.
A connection that stays inside Datahub (zone internal) is allowed by saving it allowed, as before. Because nobody reviews it, its allowed hosts and allowed sources may only name Datahub's own address (the host of the address your deployment is set up with). Any other host, a wildcard such as *.example.com, or an IP address or range is refused, whether you save the connection, edit it or apply it through the YAML import, and the message names the entries and the list each is in. Remove them, or save the connection as denied with the zone DMZ or External and request approval. If your deployment has no address of its own set, nothing can be checked against it, so every entry is refused until whoever hosts Datahub sets it. An internal connection saved before this rule still loads, and you can deny it, edit it while it is denied, or move it to DMZ or External. An edit that keeps it allowed is refused, even one that does not touch its lists, and the message says the entry was already there: remove the outside entries first, or deny it. When only one person holds connectiongovernance.admin and they also administer tasks, they can approve their own.
The wizard applies as an admin import
The configuration wizard saves through the YAML import, which an admin applies and IT signs off. That import is the one documented exception: it stores the configuration as it was applied, without starting a review.
Binding warehouse_query to an integration¶
The warehouse_query edge governs SQL against one integration. Databricks, Microsoft Fabric, Azure SQL and PostgreSQL can all serve it — but only once there is something to query: a warehouse (Databricks, Fabric) or a database (Azure SQL, PostgreSQL) configured on the integration. The wizard's Integration picker lists only integrations that are active and ready to query in that sense.
Why a configured integration can be missing from the picker
A Databricks integration without a warehouse ID is a valid integration — the warehouse is optional, and other features work without it. It simply has nothing to run a query against yet, so no other picker in Datahub can select it either. Configure its warehouse (or, for Azure SQL and PostgreSQL, its database) under Integrations, and it appears.
If no integration qualifies, the picker shows a warning with a link to Integrations rather than an empty list.
Datahub also refuses to save a warehouse_query edge linked to an integration that cannot serve it — one with no warehouse or database, one that is archived or inactive, one that is not a warehouse or database integration, or one that no longer exists — and the message says which. That holds however you save: the wizard, a YAML import, or the API. An edge that is already linked to an integration that later stopped qualifying is not frozen: you can still change its other settings, such as tightening its policy.
The review diff does not run this check yet
A hand-edited YAML import can show a clean diff on the review step and then be refused when you apply it. Nothing is half-applied — fix the link and apply again.
Configured the warehouse after seeding? You do not have to re-link the edge by hand. Running Seed from integrations again links an unlinked warehouse_query edge when exactly one integration is ready to query. It never replaces an integration you chose, never picks one when two or more qualify, and never changes the edge's policy.
flowchart LR
SEED["Seed from integrations"] --> LINKED{"warehouse_query edge linked?"}
LINKED -- "yes" --> KEEP["Keep your choice"]
LINKED -- "no" --> READY{"Integrations ready to query"}
READY -- "exactly one" --> BIND(["Link it"])
READY -- "none" --> NONE["Stay unlinked · picker shows the warning"]
READY -- "two or more" --> PICK["Stay unlinked · you pick"]
classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
class BIND accent
Figure 2 — How a seed decides the warehouse_query link. A link you chose is never replaced; an unlinked edge is linked only when exactly one integration is ready to query, so a warehouse configured after the first seed is picked up by the next one.
What the admin lander shows¶
| Surface | What you see |
|---|---|
| Security posture | 0–100 score with band (Exposed / At risk / Hardening / Hardened), five dimension bars, and ranked risks with next actions. |
| Operational KPIs | Connection count, permit / deny totals, decisions today, 30-day audit volume. |
| Tabs | Connections catalog, Audit log, Policy violations. |
| Configure / Export | Wizard apply path and YAML download for review. |
Config sign-off & enforcement¶
Sign-off is the enforcement act. Configuring in the wizard produces a draft; applying it (admin role) stores an immutable config artifact stamped with a SHA-256 version hash. IT reviews and signs off that hash — only then is the config treated as enforced. The Config integrity posture dimension keeps checking that the enforced artifact still matches the live export hash; if they diverge, re-apply and re-export.
Two changes keep the enforced config in step on their own, so they don't show as drift:
- Approving a connection's review adds that connection to the enforced config, or replaces its entry. Entries are matched by system and direction.
- Removing a connection drops it from the enforced config.
Each one stores a new artifact with a new version hash in the same action, and the Audit Log records it as connection applied. Anything else still drifts until an admin re-applies: a direct edit, a restore, or a connection that is still under review or was rejected. When no config was ever applied, an approval doesn't create the first one; that stays the admin's apply.
stateDiagram-v2
direction LR
[*] --> Draft: Configure in wizard
Draft --> Enforced: Admin applies · immutable artifact + SHA-256 hash
Enforced --> Enforced: IT signs off the version hash
Enforced --> Drift: Live export hash diverges
Drift --> Enforced: Admin re-applies / re-exports
Enforced --> Draft: Reconfigure in wizard
classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
class Enforced accent
Figure 3 — Config sign-off lifecycle. A wizard draft becomes an immutable, hash-stamped artifact when an admin applies it; IT signs off the SHA-256 hash, and any drift between the live and enforced hash sends it back for re-apply.
How the security posture score works¶
The score is config-first — it measures how well edges are registered, gated, controlled, credentialed, and signed off. Recent deny volume only affects confidence (low / medium / high), not the headline. A strict org that denies a lot of bad traffic is not punished for that.
| Dimension | What "good" looks like |
|---|---|
| Perimeter coverage | Edges linked to the gate (capability_ref or inbound identity) |
| Enforcement hardness | Harder network posture + strict unmatched enforcement |
| Policy discipline | Deny-by-default; allowed edges have allowlists / windows / rate limits / RBAC |
| Credential hygiene | External edges link to an Integration (Key Vault–backed secrets) or a vault path; auth-none is penalized. Linked Databricks/warehouse edges are healthy. |
| Config integrity | An enforced YAML artifact exists and matches the live export hash |
flowchart LR
D1["Perimeter coverage"] --> SCORE
D2["Enforcement hardness"] --> SCORE
D3["Policy discipline"] --> SCORE
D4["Credential hygiene"] --> SCORE
D5["Config integrity"] --> SCORE
SCORE(["Security posture score<br/>0-100 · config-first"]) --> BAND["Band<br/>Exposed · At risk · Hardening · Hardened"]
DEC[("Recent gate decisions")] -.informs.-> CONF["Confidence<br/>low · medium · high"]
CONF -.qualifies.-> SCORE
classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
class SCORE accent
Figure 4 — How the posture score is composed. Five equal dimensions form the config-first 0–100 headline and its band; recent gate-decision volume only tunes the confidence level, so denying a lot of traffic never lowers the score.
Config-first, on purpose
Empty catalog → score 0 (Exposed) with a clear action to seed connections. Confidence rises as real traffic hits the gate — expect low confidence on day one.
The score reports; the gate enforces
The posture score is observational — it does not change gate allow / deny by itself. Note too that mTLS credential resolution is not fully enforced yet.
Risk notifications and history¶
When a new security risk arises (or a policy violation is detected), the platform:
- Creates a notification visible in the bell icon.
- Assigns a task to the admin with severity and affected connections.
- Shows the active count as a badge on the Administration menu button and the Connection governance menu item.
The card never goes blank
When all risks are resolved, the card shows a history of recently fixed risks (last 30 days) instead of going blank — so you always have visibility into what was addressed.
Custom API connections¶
When you connect to an external API (SaaS tool, partner endpoint, webhook receiver) that isn't one of the pre-built integration types, use Generic HTTP integrations as your secret registry.
External edges must link to an integration
The backend enforces that any external edge with authentication must link to an integration (integration_ref). This keeps every secret inside the Key Vault-backed Integrations pipeline — audit trail, rotation support, and zero plaintext on disk. When you pick one, credential_source locks to platform-store and integration_ref is set for you.
Setup¶
- Create a Generic HTTP integration in Administration → Integrations → Add integration → Generic HTTP.
- Give it a name that identifies the target system (e.g. "Acme Partner API").
- Choose auth type: API Key (static token) or OAuth2 Client Credentials (auto-rotating token).
- Enter credentials — they are stored in Key Vault automatically.
- Create or edit a connection edge in Connection governance → Configure.
- Set zone to External, direction to Outbound, transport to HTTP-out.
- In the auth panel, select the Generic HTTP integration you just created.
credential_sourceis locked to platform-store andintegration_refis set automatically.- Apply the configuration (admin role required).
- When the gate permits traffic on that edge, the Credential Broker resolves the secret from Key Vault at execution time — you never type a vault path manually.
How the broker resolves credentials¶
| Auth type | What the broker does |
|---|---|
| API Key | Reads the credentials_secret_ref from the linked integration and injects it as the configured header (default Authorization: Bearer <key>). |
| OAuth2 | Resolves client_id + client_secret from Key Vault, then executes a client-credentials grant against the token_url to obtain a fresh access token. |
Migration from vault-ref¶
If you previously configured edges with vault-ref (manual vault path), those edges still work but are deprecated. To migrate:
- Create a Generic HTTP integration with the same credentials.
- Edit the connection edge — select the new integration.
- Remove the old vault-ref value (the wizard clears it automatically when you select an integration).
A vault-ref edge that still reads its secret from your own HashiCorp Vault needs an integration bound to it, and reads only from the path Datahub reserves for that integration: datahub/integrations/<integration id>/ in a KV v2 mount (the id is the last part of the integration's address in Admin → Integrations, in lowercase). The mount is a single name of letters, digits, _ or - that starts with a letter or digit, such as the default secret; a nested mount such as team/kv is refused. Store the secret there, for example at datahub/integrations/<integration id>/api-key, and set that path on the edge. Datahub refuses any other path before it contacts your Vault, so you can also limit the policy of the role Datahub signs in with to <mount>/data/datahub/integrations/* (for the default mount, secret/data/datahub/integrations/*).
The security posture score gives full credit to edges with platform-store + integration_ref. Orphaned platform-store edges (no integration_ref) are flagged as a risk.
Limitations¶
| Limitation | Detail |
|---|---|
| Score is observational | It surfaces risk; it does not change gate allow / deny by itself. |
| No score history | No historical score trend or per-connection risk score in the first release. |
| Partial enforcement | mTLS credential resolution is not fully enforced yet. The requires-approval policy state is retired: older connections that hold it are denied, and nothing writes it any more. |
Audit & compliance¶
| What's recorded | Where |
|---|---|
Every gate evaluation (permit / deny + reason) |
The gate decision log. |
| The applied config (immutable artifact, SHA-256 version hash) | The config artifact store. |
| Module mutations | The platform Audit Log, under Connection governance. |
Troubleshooting¶
| Symptom | Likely cause | What to try |
|---|---|---|
| Score stuck at Exposed / empty catalog | No descriptors yet | Configure → seed from integrations or add edges |
| High "ungated" risk | Outbound edges missing capability_ref |
Link each outbound edge to a capability in the wizard |
| Config integrity low | No apply yet, or live drift | Admin: Review & apply YAML; re-export after changes |
| Config integrity drops right after an approval | The approval stands, but the enforced config couldn't take it: the stored YAML no longer reads, or the connection doesn't validate as a config entry | The Audit Log has a connection apply skipped entry that names which; re-apply the YAML |
Webhooks refused with policy_state although their source is allowed |
The organisation's switch for inbound calls (webhook-ingress) is not approved |
Choose Request approval on the notice above the connection list; another admin approves it in their tasks |
| Confidence low | Few recent gate decisions | Expected on day one; rises as traffic hits the gate |
| "External edges with authentication require integration_ref" error | Edge is external + auth but no integration linked | Edit the edge → select a Generic HTTP integration in the auth panel |
| Access denied on the page | Missing role / stale JWT | Ensure role group includes connectiongovernance.read; refresh session or re-login |
| Warehouse picker says none of your integrations are ready to query | No Databricks/Fabric integration has a warehouse, and no Azure SQL/PostgreSQL integration has a database, configured | Configure one under Integrations, then reopen Configure |
| "warehouse_query cannot bind …" when applying | The linked integration has no warehouse or database, is archived or inactive, is not a warehouse or database integration, or no longer exists | The message names which — fix the integration, or link a different one |
warehouse_query edge still unlinked after configuring a warehouse |
The edge was seeded before any integration was ready | Run Seed from integrations again; it links the edge when exactly one integration now qualifies |
See also¶
-
The end-user view of which external apps sign in to Datahub.
-
Govern which MCP tools external clients may call, for whom, and audit every decision.
-
The map of every admin surface, including where Connection governance sits.
-
Govern the models, keys, and guardrails behind HERC and the agent system.
-
Network isolation & data residency
Where your data lives and the network isolation around the deployment.