> ## Documentation Index
> Fetch the complete documentation index at: https://docs.borghq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Azure DevOps: Service Principal Setup

> Connect Azure DevOps with a customer-owned service principal via workload identity federation — no shared secret

This guide walks through connecting Azure DevOps to Odin using a **service principal you own** in your own Microsoft Entra ID tenant, authenticated via **workload identity federation (WIF)**. It is written for your platform and security teams and is intended to double as supporting evidence for an internal or ISO 27001 review of the integration.

The short version: you keep full control of the identity, the credential, and the permissions. Borg never holds a client secret or certificate for your tenant, and every permission you grant can be reviewed and revoked entirely on your side, at any time.

<Info>
  This is the **production security model**, recommended for enterprise and ISO 27001 / SOC 2 reviews. If you just want the quickest connection and are comfortable with delegated OAuth, see the [standard Azure DevOps integration](/platform/integrations/azure-devops) instead. Both give Mjolnir access to the same repositories.
</Info>

## How it works

The integration authenticates as a **service principal that you create and own**. The connection is **app-only** — there is no signed-in user and no delegated OAuth token.

Rather than a client secret or certificate, the service principal trusts a **federated credential**. At connection time, Borg's GCP-hosted backend mints a short-lived, Google-signed OIDC token (issuer `https://accounts.google.com`) and presents it as the `client_assertion` in an OAuth2 `client_credentials` grant against your Entra tenant. Your tenant validates that token against the federated credential you configured and issues a short-lived Azure DevOps access token in return.

The practical consequence: **Borg holds no client secret and no certificate** for your service principal, ever. There is nothing of that kind to store, rotate, leak, or revoke on our side — the only thing we hold is a short-lived access token, valid until its own expiry (see [Data handling](#data-handling)).

### Authentication vs authorisation

The split of responsibility between the two systems is the basis for why this integration is low-risk and fully customer-controlled:

* **Entra ID only authenticates the identity.** The federated credential lets your tenant confirm that a request really comes from Borg's GCP identity. It says nothing about what that identity is allowed to do.
* **Azure DevOps authorises via its own permission model**, entirely independent of Entra. The permissions you grant the service principal are what actually constrain what the connection can do.

Because authorisation lives entirely in Azure DevOps' own permission system, least privilege is enforced — and can be tightened, audited, or revoked — **entirely on your side**, without needing anything from Borg.

## What you will set up

You will create three things, in this order. Odin shows you the exact federation values to use after the first step, so keep the setup dialog open as you go.

<Steps>
  <Step title="Register a service principal in Entra ID">
    In the [Microsoft Entra admin centre](https://entra.microsoft.com), go to **App registrations → New registration**. Give it a dedicated name (e.g. `borg-odin-integration`), single-tenant, with no redirect URI. Register it.

    From the app's **Overview**, note the **Directory (tenant) ID** and **Application (client) ID**. **Do not** create a client secret or certificate — federation replaces them.

    <Tip>
      A dedicated registration is recommended so its permissions can be scoped and audited independently of any other application.
    </Tip>
  </Step>

  <Step title="Save the connection in Odin">
    In Odin, go to **Management → Integrations**, click **Connect** next to Azure DevOps, and choose **Service principal**. Enter your **Tenant ID**, **Client (application) ID**, and **Azure DevOps organisation** (the `{org}` slug from `dev.azure.com/{org}`), then **Save**.

    Odin then displays the three federation values you need for the next step.
  </Step>

  <Step title="Add a federated credential in Entra">
    Back on the app registration, go to **Certificates & secrets → Federated credentials → Add credential**, and choose the **Other issuer** scenario. Fill in **exactly** the values Odin shows (use the copy buttons — no trailing spaces):

    | Field              | Value                                                                    |
    | ------------------ | ------------------------------------------------------------------------ |
    | Issuer             | `https://accounts.google.com`                                            |
    | Subject identifier | The GCP service account unique numeric ID shown in the Odin setup dialog |
    | Audience           | `api://AzureADTokenExchange`                                             |

    Give the credential a name and save. No client secret or certificate is created — this is what lets your tenant trust Borg's identity without any shared secret.
  </Step>

  <Step title="Grant least-privilege repository access in Azure DevOps">
    Add the service principal as a member of your Azure DevOps organisation, then grant it — on the nominated repository only:

    * **Code (Read)** — clone the repository and read pull requests
    * **Contribute to Pull Requests** — post PR review comments and the status check

    No code-push or branch-write, no work-item access, and no organisation- or project-administration rights are required, and none should be granted.

    <Tip>
      If the service principal does not appear when adding org members, check **Organization settings → Policies** for a service-principal / managed-identity access toggle and enable it.
    </Tip>
  </Step>

  <Step title="Test the connection">
    Back in Odin, click **Test connection**. Odin mints a token and lists the repositories the service principal can see. A non-zero count confirms both the federation and the Azure DevOps permissions are correct.

    * *"Could not mint a token…"* → the federation values do not match; recheck the issuer, subject, and audience for typos or a trailing slash.
    * *"Token minted, but the service principal is not authorised…"* → federation works; the repository permissions from the previous step are missing or have not propagated yet.
  </Step>

  <Step title="Register a Service Hook for PR reviews (optional)">
    Only needed if you want [PR Reviews](/mjolnir/pr-reviews) — skip it for pentest-only use.

    After you save, Odin also shows a **Webhook URL** and an **`X-Mjolnir-Token`** header value. Because a least-privilege service principal cannot register webhooks itself (that would require organisation subscription-admin rights), an Azure DevOps organisation admin registers them by hand:

    1. In the project → **Project settings → Service hooks → + (Create subscription) → Web Hooks**.
    2. Create one subscription triggering on **Pull request created**, and a second on **Pull request updated**.
    3. For each, set the **URL** to the Webhook URL (copy the full value — it includes a query parameter), and paste the `X-Mjolnir-Token: …` string into the **HTTP headers** field.
    4. Use the wizard's **Test** button to confirm Odin returns a `2xx`, then finish.

    You then enable PR reviews per repository from the [Mjolnir Configuration](/mjolnir/configuration) page. (The delegated OAuth model registers these hooks automatically; the service-principal model keeps them manual so the identity stays least-privilege.)
  </Step>
</Steps>

## Revocation

You can revoke Borg's access unilaterally, at any time, without contacting Borg, by either:

* **Removing the federated credential** from the service principal's app registration in Entra ID — Borg can then no longer authenticate as the service principal at all; or
* **Removing the service principal from your Azure DevOps organisation** (or removing its permissions on the nominated repository) — authentication may still succeed, but Azure DevOps will no longer authorise any access.

Either action takes effect immediately. There is no propagation delay to wait for on Borg's side, and no secret of ours to invalidate — because we never held one.

## Data handling

* **Token lifetime** — minted Azure DevOps access tokens are short-lived (approximately one hour) and are re-minted on expiry using the same federation flow. There is no long-lived refresh token, because `client_credentials` grants do not issue one.
* **Storage** — minted access tokens are cached, encrypted, in Google Secret Manager, scoped to your organisation's integration record. No client secret or certificate is ever stored, because none is used.
* **Scope of use** — tokens minted under this connection are used only within the Azure DevOps organisation you nominate, bounded by the least-privilege permissions you grant: cloning the nominated repository for a whitebox penetration test, and reading pull requests, posting review comments, and posting a pull-request status check as part of PR Reviews. No other Azure DevOps organisation is accessed under this connection.

## Permissions reference

| Permission                  | Granted on                   | Why                                                                      |
| --------------------------- | ---------------------------- | ------------------------------------------------------------------------ |
| Code (Read)                 | Nominated repository         | Clone the repository for whitebox pentesting and read pull-request diffs |
| Contribute to Pull Requests | Nominated repository         | Post PR review comments and the `mjolnir-security-review` status check   |
| *(none)*                    | Organisation / project admin | Not required — hooks are registered manually by your admin               |

Everything else — code push, branch write, work-item access, and organisation or project administration — is neither requested nor required.

## Related

* [Azure DevOps integration overview](/platform/integrations/azure-devops) — including the delegated OAuth alternative
* [PR Reviews](/mjolnir/pr-reviews) — what Mjolnir posts on your pull requests
* [Mjolnir Configuration](/mjolnir/configuration) — enabling PR reviews per repository
