Azure Synapse Analytics — user setup guide

What to create on your Synapse workspace's dedicated SQL pool (every fact below was verified on a real DW100c pool) so DBSheriff can monitor it. Scope: dedicated SQL pool only — serverless SQL pool has no dm_exec_requests-equivalent DMV at all and is not monitored by this engine.

Every fact and T-SQL statement in this guide was run against a live, Terraform-provisioned Synapse workspace during the 2026-09-15 engine buildout — this is not written from Microsoft's documentation alone, and in several places it directly contradicts general claims about how Azure-managed SQL engines behave (see §5).

0. What must be enabled on the server

Item Required? What it unlocks Without it How
The accounts of §1 with the grants of §2 required everything: statements, sessions, kill, lock, posture, classification DBSheriff cannot connect or cannot act §2
VIEW DATABASE STATE for monitor/posture required cross-user visibility into sys.dm_pdw_exec_requests/sys.dm_pdw_exec_sessions/sys.dm_pdw_lock_waits the producer sees only its own connection §2
Workspace SQL administrator credentials for kill/lock required KILL '<session_id>' and DENY CONNECT/session termination — there is no narrower grant for either on this engine (see §2 for what was tried and ruled out) kill/lock cannot act on another user's session at all §2
An Azure service principal with the built-in Reader role on the workspace resource required for PS09/PS11/PS67/PS68 auditing policy, TDE status, public network access, Entra-only auth — none of this is T-SQL-readable at all on a dedicated pool those 4 posture codes report NOT_CHECKED §2
SQL Auditing (azurerm_synapse_sql_pool_extended_auditing_policy or the Portal equivalent) recommended durable statement/auth trail via sys.fn_get_audit_file — see §5's parked-audit-trail caveat before relying on this no completed-statement source beyond the live DMVs' 10,000-row cap Portal/Terraform, not T-SQL
Transparent Data Encryption recommended PS11 passes posture flags it — disabled by default, confirmed live on a freshly-created pool (not on-by-default the way some general claims about Azure managed databases suggest) Portal/CLI, not T-SQL
publicNetworkAccess = Disabled + a private endpoint recommended PS67 passes posture flags the workspace as internet-reachable Portal/CLI/ARM
Microsoft Entra-only authentication recommended PS68 passes posture flags that a leaked SQL login/password alone is enough to connect Portal/CLI — needs a Microsoft Entra admin configured on the workspace first

The floor. A VIEW DATABASE STATE monitor account gives you live + completed statement monitoring. Kill and lock genuinely need the workspace's own SQL administrator credentials — there is no way around this on the current version of this engine (confirmed live, see §2). Nothing here needs a pool restart; every grant is a plain GRANT, applied live.

1. The accounts

Account What it does Grants
synapse_monitor reads sys.dm_pdw_exec_requests/exec_sessions/lock_waits (live + completed statements, lock waits); also the account behind Account Inventory's identity discovery VIEW DATABASE STATE, SELECT ON SCHEMA::dbo (or whichever schemas hold monitored tables), VIEW DEFINITION ON DATABASE::<pool database> (added 2026-09-19 — see §2)
Kill / lock terminates sessions (KILL '<session_id>'), blocks new connections (DENY CONNECT) the workspace SQL administrator login itself — no narrower grant exists (see §2)
synapse_posture (an Azure AD app registration / service principal, not a SQL login) ARM-API reads for PS09/PS11/PS67/PS68 built-in Reader role, scoped to the workspace resource only
synapse_classify reads column metadata and samples values for classification SELECT on the schemas to scan
A test/victim account (optional, for your own validation only) — whatever your own test scenario needs
-- connect to master first for CREATE LOGIN -- Synapse requires this,
-- unlike a plain database-scoped CREATE USER (live-verified 2026-09-15:
-- "User must be in the master database" if attempted from the pool database)
CREATE LOGIN synapse_monitor WITH PASSWORD = '<pick a strong password>';

-- then connect to the pool database itself for CREATE USER / GRANT
CREATE USER synapse_monitor FOR LOGIN synapse_monitor;
GRANT VIEW DATABASE STATE TO synapse_monitor;
GRANT SELECT ON SCHEMA::dbo TO synapse_monitor;
GRANT VIEW DEFINITION ON DATABASE::<pool database> TO synapse_monitor;   -- Account Inventory, see §2

CREATE LOGIN synapse_classify WITH PASSWORD = '<pick a strong password>';
-- (then, per schema to scan, connected to the pool database)
CREATE USER synapse_classify FOR LOGIN synapse_classify;
GRANT SELECT ON SCHEMA::<schema> TO synapse_classify;

For the Azure service principal (posture/ARM):

az ad sp create-for-rbac --name "dbsheriff-posture-<workspace>" \
  --role "Reader" \
  --scopes "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Synapse/workspaces/<workspace>"

Keep the result as the pool's posture credential, posture_service_account_key (sent when you register the pool, below), a JSON string with these key names: tenant_id (the tenant that az prints), client_id (its appId), client_secret (its password), plus subscription_id, resource_group, workspace_name, and sql_pool_name. All seven are required; az's own names (tenant, appId, password) are not accepted.

Register the pool

The Admin Console's Monitored Servers form has no Azure Synapse choice yet, so the pool is registered through the Admin Console's API, the call the form makes for every other engine: POST /api/monitored-servers, signed in as an approver. Send these fields:

Field Value
server_name the name you want to see in DBSheriff
db_engine synapse
host the workspace's SQL endpoint (<workspace>.sql.azuresynapse.net)
port 1433 (send it: the API's own default is 5432)
dbname the dedicated pool's database
db_user, password synapse_monitor and its password
kill_db_user, kill_password, lock_db_user, lock_password the workspace SQL administrator login and its password (§2 says why)
classification_db_user, classification_password synapse_classify and its password
posture_service_account_key the JSON string above

Synapse must be among the engines your licence covers and in the installer's engine list (the producers of an engine start only then), and the producer picks the new server up at its next registry reload (five minutes by default).

2. Grant reference (why each one is needed — and what was ruled out)

3. What DBSheriff reads

Producer (no setup needed beyond §1-2 for the monitor account)

sys.dm_pdw_exec_requests joined to sys.dm_pdw_exec_sessions on session_id is a single unified view carrying both currently-running rows (status = 'Running') and completed rows (status IN ('Completed', 'Failed', 'Cancelled')) — the full query text is available directly (command/command2), with no separate text-resolution call needed (better than plain SQL Server, which needs sys.dm_exec_sql_text). These DMVs are capped at 10,000 rows each and reset entirely when the pool is paused or scaled (Microsoft's own documented behavior) — there is no cumulative counter to diff, so completed-statement capture is a plain time-window lookback, not an offset/cursor.

sys.dm_pdw_lock_waits carries no direct blocking_session_id column (the same shape gap Redshift's pg_locks has) — DBSheriff pairs a waiting session against a granted one on the same object_name. Not fully live-verified this pass: a deliberate two-session concurrent-UPDATE test did not reproduce a captured wait row in the time available (the second statement completed immediately rather than blocking) — the query is implemented against the DMV's real, live-confirmed columns, but its end-to-end behavior under a genuine lock conflict needs a follow-up check.

Classification scan (synapse_classify, §1)

sys.tables joined to sys.schemas/sys.columns/sys.types, with sys.extended_properties for any existing column comments and sys.partitions for a row-count estimate — one connection per registered pool, unqualified (a dedicated SQL pool has no cross-database references at all, the same limitation Azure SQL Database has, so there's nothing to enumerate beyond the one pool the connection is already scoped to). t.is_ms_shipped = 0 and excluding the sys/INFORMATION_SCHEMA/ dbsheriff schemas keeps DBSheriff's own plumbing and system objects out of the candidate list, same as the SQL Server engine.

Two real, engine-specific findings from building this (2026-09-15): - The sys.partitions row-count estimate is unreliable on this engine specifically — live-verified returning 1,000 for a table that actually has 25 rows (a dedicated pool's distributed columnstore rounds row-group estimates up in a way plain SQL Server's rowstore partitions don't). It's carried through as an approval-queue hint only, exactly like every other engine's own row-estimate caveat — never used for scoring, and not worth fixing with a real COUNT(*) per table, which would be far too expensive against a real customer's data. - TABLESAMPLE SYSTEM is rejected outright for value-sampling ("The SYSTEM option for TABLESAMPLE is not supported", error 105058) — unlike plain SQL Server, where it works and only the empty-table case falls back. Value-sampling here always uses a plain TOP N, so (like MySQL's/Oracle's/HANA's own accepted first-N-rows tradeoff) a very large table's sample may skew toward whatever the columnstore happens to return first rather than a truly random slice.

4. Verification

-- cross-user visibility actually works (run as synapse_monitor, not the admin login)
SELECT r.request_id, r.session_id, r.status, s.login_name, s.app_name,
       LEFT(COALESCE(r.command2, r.command), 100) AS statement
  FROM sys.dm_pdw_exec_requests r
  JOIN sys.dm_pdw_exec_sessions s ON r.session_id = s.session_id
 ORDER BY r.submit_time DESC;

-- QUOTED_IDENTIFIER must be ON for this session -- Synapse rejects the
-- sqlcmd default (OFF) outright with "Setting QUOTED_IDENTIFIER to 'OFF'
-- is not supported"; confirm your driver/ODBC connection sets it:
SELECT @@options & 256;  -- non-zero means QUOTED_IDENTIFIER is ON

Expected producer log line on first successful poll: [<server>][poll] nothing new (or a captured statement if one was running) — no permission error, which would mean VIEW DATABASE STATE is missing.

-- classification catalog access (run as synapse_classify)
SELECT s.name, t.name, c.name FROM sys.tables t
  JOIN sys.schemas s ON s.schema_id = t.schema_id
  JOIN sys.columns c ON c.object_id = t.object_id
 WHERE t.is_ms_shipped = 0;

Then run sudo docker exec -it dbsheriff-<env>-admin python scripts/classify_scan.py --engines synapse --server <name> (<env> is the environment name you gave the installer, prod by default) — expect PENDING candidates for any real column names matching DBSheriff's PII name rules, with the two-evidence-source ones (e.g. a ssn column whose sampled values pass the SSN validator) auto-approved.

5. Known limitations