> For the complete documentation index, see [llms.txt](https://docs.teleskope.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.teleskope.ai/connectors/azure/azure-sql-managed-identity.md).

# Azure SQL — Managed Identity

Configure Azure SQL scanning using a User Assigned Managed Identity (UAMI) instead of a SQL login and password. Use this path when your security policy prohibits stored credentials, or when Teleskope runs in your Azure subscription with Workload Identity. For the standard SQL login setup, see [Azure SQL](/connectors/azure/azure-sql.md).

## Requirements

* Teleskope scanning services run in your Azure subscription with Workload Identity enabled and a UAMI attached
* Microsoft Entra authentication is enabled on each Azure SQL server (an Entra admin is set on the server)
* You have `az` CLI access to the resource group containing the UAMI, and admin access to the SQL servers

{% hint style="info" %}
This page applies to Azure SQL Database (`*.database.windows.net`). SQL Server running on Azure VMs requires instance-level Entra configuration and is not covered here.
{% endhint %}

## Identify the managed identity

Database users must be bound to the exact identity the Teleskope pods run as. Look it up by its client ID (provided by Teleskope, visible in the pod environment as `AZURE_CLIENT_ID`):

```bash
az identity list -g <resource-group> \
  --query "[?clientId=='<client-id>'].{name:name, principalId:principalId}" -o table
```

Record the identity `name` and `principalId`. If `CREATE USER [<name>] FROM EXTERNAL PROVIDER` later fails with "Principal not found in the directory", create the user by object ID instead:

```sql
CREATE USER [teleskope-identity] FROM EXTERNAL PROVIDER WITH OBJECT_ID = '<principalId>';
```

## Permissions

{% stepper %}
{% step %}

#### Create the identity user in `master`

Entra identities are contained database users; unlike a SQL login, they have no server-level principal. Teleskope's first connection to each server lands in `master` to enumerate databases, so the identity needs a user there:

```sql
-- in master
CREATE USER [<identity-name>] FROM EXTERNAL PROVIDER;
GRANT VIEW ANY DATABASE TO [<identity-name>];
```

`VIEW ANY DATABASE` makes the database enumeration return all databases on the server. This is the managed-identity equivalent of the `GRANT VIEW ANY DATABASE` step in the [standard setup](/connectors/azure/azure-sql.md); it is explicit here because Entra users must be created per database. Do not add `db_datareader` in `master` — no table data is read there.
{% endstep %}

{% step %}

#### Grant read access in each database

For each database to be scanned:

{% tabs %}
{% tab title="Azure SQL Database" %}
Azure SQL Database does not support cross-database `USE`, so the grants run per database. To avoid manual repetition, loop over the server's databases from a client with `az` CLI and `sqlcmd`, authenticated as an Entra admin of the server (`-G` uses Entra authentication):

```bash
SERVER='<server-name>'            # without .database.windows.net
RG='<resource-group>'
IDENTITY='<identity-name>'

# once per server: master user + enumeration grant
sqlcmd -S "$SERVER.database.windows.net" -d master -G -Q "
  IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = '$IDENTITY')
    CREATE USER [$IDENTITY] FROM EXTERNAL PROVIDER;
  GRANT VIEW ANY DATABASE TO [$IDENTITY];"

# every database on the server: read grants
for DB in $(az sql db list --server "$SERVER" -g "$RG" --query "[?name!='master'].name" -o tsv); do
  sqlcmd -S "$SERVER.database.windows.net" -d "$DB" -G -Q "
    IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = '$IDENTITY')
      CREATE USER [$IDENTITY] FROM EXTERNAL PROVIDER;
    GRANT SELECT TO [$IDENTITY];
    GRANT VIEW DATABASE STATE TO [$IDENTITY];"
done
```

The script is idempotent and safe to re-run; wrap it in an outer loop over `az sql server list` to cover every server. To grant a single database manually, run just the inner statements connected to that database:

```sql
CREATE USER [<identity-name>] FROM EXTERNAL PROVIDER;
GRANT SELECT TO [<identity-name>];
GRANT VIEW DATABASE STATE TO [<identity-name>];
```

{% endtab %}

{% tab title="SQL Managed Instance" %}
Managed Instance supports cross-database scripting, so the grants can be applied to all non-system databases in one pass:

```sql
DECLARE @sql NVARCHAR(MAX);
SET @sql = '';

-- Generate the dynamic SQL for each database
SELECT @sql +=
    'USE [' + name + '];
    CREATE USER [<identity-name>] FROM EXTERNAL PROVIDER;
    GRANT SELECT TO [<identity-name>];
    GRANT VIEW DATABASE STATE TO [<identity-name>];' + CHAR(13)
FROM sys.databases
WHERE state = 0 AND name NOT IN ('tempdb', 'model', 'msdb'); -- Exclude system databases

-- Execute the generated SQL
EXEC sp_executesql @sql;
```

{% endtab %}
{% endtabs %}

These are the same read grants as the [standard setup](/connectors/azure/azure-sql.md): `SELECT` for table contents, `VIEW DATABASE STATE` for row-count statistics.
{% endstep %}

{% step %}

#### Verify the identity binding

Confirm the database user is bound to the identity Teleskope runs as. In any database where the user was created:

```sql
SELECT name, CONVERT(uniqueidentifier, sid) AS client_id
FROM sys.database_principals
WHERE name = '<identity-name>';
```

`client_id` must equal the UAMI client ID from the identification step. A different value means the user is bound to another principal; drop it and recreate with `WITH OBJECT_ID`.
{% endstep %}

{% step %}

#### Validate on one server, then roll out

Apply the `master` and per-database grants on a single server, trigger a scan, and confirm its databases and tables appear in the Teleskope catalog. Repeat for the remaining servers.
{% endstep %}
{% endstepper %}
