Sources

Least-privilege logins

The smallest login each source accepts — the exact grants, why each is needed, and what stays with your administrator.

Fabric needs to read your tables and your database's change log. It never writes to your tables. Give it a dedicated login with only the grants below, and create anything that needs higher privileges (publications, CDC capture, the change log settings) yourself as an administrator, once.

The rules for every source

  • One login for Fabric, used by nothing else, so you can audit and revoke it on its own.
  • Only the tables you sync. Grant read access per table or per schema, not on the whole server.
  • No DDL, no DML on your data. The one write is a one-row heartbeat table on PostgreSQL, below.
  • Allowlist only Fabric's address for this login, and use a strong generated password. Fabric stores it encrypted and never shows it again.

TLS is a per-source setting, not a grant. How the runner encrypts its connection to a database is chosen on each source and needs no extra database privilege; turning on require, verify-ca or verify-full changes nothing about the login below. See TLS and encryption in transit for the modes and how to get your provider's CA.

PostgreSQL

Run as an administrator (a superuser; rds_superuser on Amazon RDS and Aurora; cloudsqlsuperuser on Cloud SQL). Replace app and the table names with yours.

-- 1. The login: replication, but no superuser and no CREATE
CREATE ROLE fabric_cdc LOGIN PASSWORD '<generated>' REPLICATION;
-- Amazon RDS / Aurora: drop REPLICATION above and instead
-- GRANT rds_replication TO fabric_cdc;

GRANT CONNECT ON DATABASE mydb TO fabric_cdc;
GRANT CONNECT ON DATABASE postgres TO fabric_cdc;  -- "Test connection" lists databases from here

-- 2. Read access to exactly the tables you sync
GRANT USAGE ON SCHEMA app TO fabric_cdc;
GRANT SELECT ON app.orders, app.customers TO fabric_cdc;

-- 3. The heartbeat: one row Fabric updates so an idle
--    database's replication slot keeps advancing
CREATE TABLE public.heartbeat (
  id INTEGER PRIMARY KEY,
  last_update TIMESTAMPTZ NOT NULL
);
GRANT USAGE ON SCHEMA public TO fabric_cdc;
GRANT SELECT, INSERT, UPDATE ON public.heartbeat TO fabric_cdc;

-- 4. The publication, named exactly dbz_publication, listing the
--    tables you sync plus the heartbeat
CREATE PUBLICATION dbz_publication
  FOR TABLE app.orders, app.customers, public.heartbeat;

Why each piece is there:

  • REPLICATION (or rds_replication) lets the login create its replication slot and read the change log. It does not grant read access to any table by itself.
  • If the publication is missing, Fabric tries to create it FOR ALL TABLES, which PostgreSQL allows only for a superuser. Creating it yourself keeps the login unprivileged.
  • Logical replication ignores table grants: the change stream carries every table in the publication, whatever SELECT the login has. The publication, not the grants, is what limits which changes leave your database — which is why this guide lists tables rather than using FOR ALL TABLES.

Add a table to the publication before you sync it

Before selecting a new table in Fabric, grant SELECT on it and add it to the publication: ALTER PUBLICATION dbz_publication ADD TABLE app.invoices; Fabric checks this — when you save the sync, when the pipeline starts, and about once a minute while it runs — and stops with the statement to run rather than copy a table whose changes it would never receive: publication dbz_publication is missing 1 synced table(s): app.invoices. Their changes would never reach the sink: the initial snapshot still copies the rows, so the loss is silent. As an administrator on the source, run: ALTER PUBLICATION dbz_publication ADD TABLE app.invoices; Changes made while a table was out of the publication cannot be recovered, which is why the check refuses as early as it does. If limiting the stream matters less than this step, have a superuser create the publication FOR ALL TABLES instead: the login stays unprivileged, but every table's changes reach the runner.

MySQL

CREATE USER 'fabric_cdc'@'<fabric-address>' IDENTIFIED BY '<generated>';

-- Reading the binlog: these two exist only at server level
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'fabric_cdc'@'<fabric-address>';

-- Reading data: only the database (or tables) you sync
GRANT SELECT ON mydb.* TO 'fabric_cdc'@'<fabric-address>';
  • No authentication plugin is pinned: IDENTIFIED BY creates the login with the server's default (caching_sha2_password since MySQL 8.0), which Fabric's reader supports. Pinning IDENTIFIED WITH mysql_native_password fails on MySQL 8.4, where that plugin is disabled by default: ERROR 1524 (HY000): Plugin 'mysql_native_password' is not loaded. Leave it at the default.
  • REPLICATION SLAVE streams the binlog; REPLICATION CLIENT reads the binlog position and file list. MySQL has no database-scoped form of either.
  • The snapshot uses a consistent-snapshot transaction, so no RELOAD or LOCK TABLES privilege is needed.
  • Narrow further with per-table grants: GRANT SELECT ON mydb.orders TO ….

MariaDB

CREATE USER 'fabric_cdc'@'<fabric-address>' IDENTIFIED BY '<generated>';
GRANT REPLICATION SLAVE, REPLICATION CLIENT,
      REPLICATION MASTER ADMIN, SLAVE MONITOR
  ON *.* TO 'fabric_cdc'@'<fabric-address>';
GRANT SELECT ON mydb.* TO 'fabric_cdc'@'<fabric-address>';

MariaDB 10.5 split the replication privileges. Fabric's preflight lists the replicas already attached (to catch a clashing server ID), which needs REPLICATION MASTER ADMIN, and reads replica status, which needs SLAVE MONITOR. None of these grant access to table data.

SQL Server

Enabling CDC on the database and tables needs sysadmin or db_owner; do it once as an administrator (see the SQL Server page). Fabric's own login needs only read access:

CREATE LOGIN fabric_cdc WITH PASSWORD = '<generated>';
USE mydb;
CREATE USER fabric_cdc FOR LOGIN fabric_cdc;
GRANT SELECT ON SCHEMA::cdc TO fabric_cdc;      -- the change tables
GRANT SELECT ON dbo.orders TO fabric_cdc;       -- each table you sync
GRANT SELECT ON dbo.customers TO fabric_cdc;
GO

Without db_owner, Fabric cannot read the CDC cleanup retention and shows a warning instead of checking it. Confirm the retention yourself (EXEC sys.sp_cdc_help_jobs as an administrator); it must outlast your longest expected pause.

IBM Db2

GRANT CONNECT ON DATABASE TO USER fabric_cdc;
GRANT SELECT ON SCHEMA ASN TO USER fabric_cdc;       -- change-data and register tables
GRANT SELECT ON TABLE APP.CUSTOMERS TO USER fabric_cdc;  -- each table you sync

Setting up capture (the ASN control tables and the capture program) is an administrator task; the login above only reads what capture wrote.

MongoDB

// mongosh, as a user with userAdmin on the target database
db.getSiblingDB("myapp").createRole({
  role: "fabricCdc",
  privileges: [{
    resource: { db: "myapp", collection: "" },
    actions: ["find", "changeStream", "listCollections"]
  }],
  roles: []
})
db.getSiblingDB("myapp").createUser({
  user: "fabric_cdc",
  pwd: "<generated>",
  roles: [{ role: "fabricCdc", db: "myapp" }]
})

Three read actions on one database. To limit it to certain collections, repeat the privilege with collection: "orders" and so on instead of the empty string. On Atlas, create the same custom role under Database Access → Custom Roles.

Zoho CRM

Zoho connects with OAuth rather than a password. Authorize with a Zoho user whose profile can see only the modules you want synced; Fabric reads what that user can read. You can revoke the connection at any time from Zoho's Connected Apps settings.

Revoking access

Stop the pipeline in Fabric, then drop the login. On PostgreSQL, also drop the replication slot so the server stops retaining change log for it: SELECT pg_drop_replication_slot('<slot>'); — an abandoned slot makes the log grow until the disk fills.