Airflow administrator guide: managing connections¶
Airflow connections that authenticate with a client_id/client_secret
(Databricks, Fabric, Power BI, Synapse, Data Factory, a private git repo,
or any generic OAuth-protected API) need that secret rotated before it
expires. The Airflow Connections Pipeline is a self-contained Azure
DevOps pipeline package that does this automatically, on a schedule, for
every connection you list, without ever handing out Airflow’s own
application secret.
Overview¶
One pipeline, one schedule, a list of connections. On every scheduled run, the pipeline loops over the list and, per entry:
Rotates the target app registration’s client secret: the app that owns the thing Airflow is connecting to (a Databricks service principal, a Fabric service principal, a generic API’s app registration, and so on). Name the target by
targetAppNameto have the pipeline create the app registration and service principal when one is missing (the recommended way to give every connection its own dedicated SP), or bytargetAppIdto rotate an existing one. A freshly created SP still needs its resource access (workspace roles, ARM RBAC) granted once, manually.Ensures the service connection’s SP holds the Airflow app’s “Admin” app role, self-granting it via Microsoft Graph when missing.
Mints an access token for the Airflow application itself, without ever using the Airflow app’s own client secret (see “One service connection” below).
Pushes the new
client_id/client_secretinto the named Airflow connection via the Airflow REST API.
Because every entry rotates on the same schedule, pick a cron cadence
shorter than the smallest validityDays across all your connections.
Getting the pipeline¶
This is a self-contained Azure DevOps pipeline package, with no dependency on the rest of the platform repo. Get a working copy one of two ways:
Download the pipeline package (zip)— the template, the scripts, and an exampleconnections.yamlto copy and edit by hand.Generate a starting connections.yaml — fill in a form and get a ready-to-drop-in file for the connections you actually need, instead of hand-editing the example.
Either way, unzip the package into its own Azure DevOps repo (or a folder of an existing one) and point a new pipeline at it — see “Setting up your pipeline” below.
One service connection¶
Each entry names a single Azure DevOps service connection
(serviceConnectionName, typically the same one for every entry) whose
service principal does everything:
Job |
What the SP needs |
|---|---|
Finds/creates target apps and rotates their secrets |
Owner of those app registrations. To let the pipeline create
per-connection apps ( |
Authenticates to the Airflow REST API |
The “Admin” app role on the Airflow application — self-granted by
the pipeline when the SP holds |
You will never need, and should never be given, the Airflow application’s
own client secret. Instead, the service connection’s SP gets the Airflow
app’s “Admin” app role. When the pipeline requests a token for the
Airflow app as the resource/audience, Microsoft Entra ID includes that
role in a roles claim regardless of which client asked for the token.
Airflow’s auth layer only checks the claim, not who’s holding the app’s
secret.
If the SP can’t self-grant the role (missing
AppRoleAssignment.ReadWrite.All), the pipeline fails with a clear
error and two resolution paths: someone with rights over the Airflow app
registration runs Grant-AirflowAdminRole.ps1 once, or the service
connection’s SP is granted that Graph permission (admin consent required)
so the pipeline becomes self-sufficient on every future run. Either way,
a freshly granted role can take a short while to propagate — re-run if
the very next step still gets a 403.
Setting up your pipeline in Azure DevOps¶
Everything below happens in your own organization’s Azure DevOps, not the vendor’s. No prior Azure DevOps experience is assumed: every step names the exact menu to click.
1. Get the files into a repo¶
In your Azure DevOps project (left sidebar), click Repos.
If the project doesn’t have a repo yet, Azure DevOps offers to create one on that same screen — accept the default, or give it a name like
airflow-connections. If you’d rather keep this pipeline in an existing repo instead of a new one, that’s fine too; any subfolder works.Add the pipeline’s files to that repo, keeping the folder structure (
templates/,scripts/, and yourconnections.yaml) intact:No git experience needed: on the Repos → Files screen, click ⋯ (top right) → Upload file(s), and repeat per folder (
templates/rotate-connection-template.yaml, everything underscripts/, and yourconnections.yaml).If you already use git: clone the repo, copy the unzipped package’s contents in,
git add/git commit/git push.
2. Fill in your connections.yaml¶
Start from
examples/connections.yamlin the downloaded package, or use the generator to produce one directly.For each connection you need, add an entry to the
connectionslist — see the connection type reference below. UsetargetAppNameto have the pipeline create a dedicated app registration + SP per connection, ortargetAppIdto pin an existing one.connectionIdis the Airflowconn_idand must be unique within the file — letters, digits, underscores, and hyphens only.Pick a
schedules:cron cadence shorter than the smallestvalidityDaysamong your connections, so every secret gets rotated before it expires.
3. Create the service connection¶
This is the one identity the pipeline runs as (see “One service
connection” above) — its name must match the serviceConnectionName
variable in your YAML file exactly.
Note
The Airflow application itself belongs to the platform and lives in the platform’s own tenant, not yours. Self-granting it the “Admin” role (below) only works once your organization has completed Entra ID onboarding — that’s what consents the platform’s Airflow application into your tenant, which is what your service connection’s SP needs to be able to assign it a role at all. If your Airflow instance is instead secured through the platform’s shared Keycloak realm, this rotation mechanism’s Airflow step doesn’t apply — talk to the platform team about rotating that connection’s secret.
Bottom-left corner of Azure DevOps, click Project Settings.
Under Pipelines, click Service connections.
Click New service connection.
Choose Azure Resource Manager, then click Next.
Choose Service principal (automatic) — Azure DevOps creates the app registration for you, so there’s no separate Entra ID step for this part.
Pick your Subscription (any subscription in your own tenant — this only matters for creating the service principal itself; which one you pick doesn’t affect its permissions, since everything this pipeline needs is granted at the Microsoft Entra ID level, not the subscription level). Leave Resource group empty (scope: Subscription).
Under Service connection name, enter exactly the same name as
serviceConnectionNamein your YAML file (e.g.customer-connections).Uncheck Grant access permission to all pipelines. This service connection ends up holding sensitive rights (ownership of your target apps, the Graph permissions below) — don’t leave every pipeline in the project free to use it. Click Save. The first time you run the pipeline you create in the next section, Azure DevOps will show a banner asking to authorize that specific pipeline to use this connection — approve it there.
The service connection now exists, but it only has the base subscription role Azure DevOps gave it. It still needs two Microsoft Graph application permissions before the pipeline can do its job. Grant these once, as a Microsoft Entra ID administrator:
Open portal.azure.com, then Microsoft Entra ID → App registrations.
Find the app registration Azure DevOps just created — back on the service connection you made (Project Settings → Service connections → your connection → Manage Service Principal) opens it directly in the Entra portal.
Click API permissions → Add a permission → Microsoft Graph → Application permissions.
Search for and check both:
Application.ReadWrite.OwnedBy— needed if any connection usestargetAppName(lets the pipeline create that connection’s app registration).AppRoleAssignment.ReadWrite.All— needed so the pipeline can self-grant itself the Airflow “Admin” role (see “Granting the Admin role” above).
Click Add permissions.
Click Grant admin consent for <your organization>, then Yes to confirm. The permissions row should now show a green checkmark under Status.
If you don’t use targetAppName anywhere in your file, you can skip
Application.ReadWrite.OwnedBy. If you’d rather not grant
AppRoleAssignment.ReadWrite.All at all, skip it too — the pipeline
will instead print manual-grant instructions the first time it runs (see
“Granting the Admin role” above, option (a)).
4. Create the pipeline¶
Left sidebar, click Pipelines → New pipeline.
Where is your code? → Azure Repos Git → select the repo you pushed the files into.
Configure your pipeline → Existing Azure Pipelines YAML file.
Branch:
main(or whichever branch you pushed to). Path: pick yourconnections.yamlfile (e.g./customer-connections.yaml).Click Continue, review the YAML preview, then click the dropdown next to Run and choose Save (not Save and run — see the next step first).
Optional: rename the pipeline to something recognizable — open it, click ⋯ (top right) → Rename/move, e.g. “Rotate Airflow connections”.
5. Run it once, then trust the schedule¶
Open the pipeline and click Run pipeline → Run.
Watch each step. If the admin-role step fails, it prints the exact two fix options — see “Granting the Admin role” above and Troubleshooting below.
Once every step is green, your connections are live in Airflow — check the Airflow UI’s Admin → Connections page for the ``connectionId``(s) you configured.
Nothing else to do: the
schedules:block in your YAML already arms recurring runs. Confirm the next run time under the pipeline’s Scheduled runs (clock icon, top right of the pipeline’s page).
Connection type reference¶
The pipeline template dispatches on connType. Types with a dedicated
script take typed fields directly (no hand-written extraJson); any
other connType falls through to the generic
Set-AirflowConnection.ps1 step with hostName/extraJson.
Login/password are always the rotated client_id/client_secret,
and the tenant ID is derived at runtime from the service connection’s
Azure context, never spelled out in YAML.
Type |
|
Typed fields |
Notes |
|---|---|---|---|
HTTP (generic OAuth-protected API) |
|
|
Airflow’s built-in |
Databricks |
|
|
Matches |
Fabric ( |
|
|
Extra |
Fabric Capacity ( |
|
— |
Extra |
Power BI |
|
— |
Extra |
Synapse ( |
|
|
Extras |
Data Factory ( |
|
|
Extras |
Git (private DAGs repo) |
|
|
Same dedicated-app-per-connection model — use |
Fabric Capacity access (one-time)¶
The fabric_capacity connection drives suspend/resume/scale through
the ARM management plane, so its SP needs a role on the capacity
resource, not Contributor. Create a minimal custom role once
(Fabric Capacity (Smooth) Operator) granting
Microsoft.Fabric/capacities/{read,write},
capacities/suspend/action, capacities/resume/action, and
capacities/skus/read / skus/read, then assign it to the
pipeline-created SP for that connection (run the pipeline once first so
the SP exists). Leave out write if you only ever suspend/resume, not
scale.
Troubleshooting¶
Symptom |
Likely cause |
|---|---|
Can’t reach the Airflow API (after retries) |
|
Admin-role step fails with manual-resolution steps |
The service connection’s SP can’t self-grant the Airflow “Admin”
role — grant it manually or give the SP
|
401/403 from the Airflow API even though the admin-role step passed |
A freshly granted role can take a short while to propagate —
re-run. If it persists, confirm the resolved Airflow app is the
right one (pin it explicitly with |
“does not exist” rotating the target secret |
With |
Can’t create the target app |
The service connection’s SP lacks app-creation rights — either
have an admin create the app manually and pin it via
|
Pipeline succeeded but the target system returns 401/403 to DAGs |
A freshly created per-connection SP has credentials but no access yet — grant it the resource-side access once (workspace role, ARM RBAC, etc.). |