> ## Documentation Index
> Fetch the complete documentation index at: https://conductorone-hunner-patch-1.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up a Grafana connector

> C1 provides identity governance and just-in-time provisioning for Grafana. Integrate your Grafana instance with C1 to run user access reviews (UARs) and enable just-in-time access requests.

## Capabilities

| Resource | Sync | Provision |
| :- | :- | :- |
| Accounts | <Icon icon="square-check" iconType="solid" color="#c937ae" /> | <Icon icon="square-check" iconType="solid" color="#c937ae" /> |
| Organizations | <Icon icon="square-check" iconType="solid" color="#c937ae" /> | <Icon icon="square-check" iconType="solid" color="#c937ae" /> |
| Teams | <Icon icon="square-check" iconType="solid" color="#c937ae" /> | <Icon icon="square-check" iconType="solid" color="#c937ae" /> |
| Roles | <Icon icon="square-check" iconType="solid" color="#c937ae" /> | |
| Service accounts | <Icon icon="square-check" iconType="solid" color="#c937ae" /> | |

Team membership can be granted and revoked. Grafana RBAC roles that a team holds (IRM and OnCall plugin roles such as **Schedules Editor**) are synced as read-only assignments and require Grafana Cloud or Enterprise. Service accounts are synced with their organization role; they are read-only.

[This connector syncs non-human identities](/product/admin/nhi) and displays them on the **Identities overview** dashboard.

<Note>
  **Roles are optional**

  IRM / OnCall RBAC roles are available only on Grafana Cloud and Enterprise. Role sync is disabled by default — enable the **Role** resource type in the connector's settings in C1 when your instance has access-control.
</Note>

The Grafana connector supports both **self-hosted Grafana** instances and **Grafana Cloud**. The required credentials and provisioning behavior differ between the two — see [Gather Grafana credentials](#gather-grafana-credentials) below.

The Grafana connector supports [automatic account provisioning and deprovisioning](/product/admin/account-provisioning).

For self-hosted Grafana, when a new account is created by C1, the account's password is sent to a [vault](/product/admin/vaults).
For Grafana Cloud, account creation is invite-based and no connector-generated password is returned.

<Warning>
  **Grafana Cloud: enabling the basic login form is a prerequisite for creating brand-new users**

  Grafana Cloud instances ship with the basic login form **disabled** by default (users authenticate through grafana.com / SSO). While it is disabled, Grafana rejects instance-level invites for users who do not yet exist in the instance, and account creation fails with:

  ```
  Cannot invite external user when login is disabled.
  ```

  With the service-account token the connector uses, this means:

  * **Users who already exist in the instance** (provisioned earlier via SSO, SCIM, or grafana.com) are added to the organization normally — account provisioning works for them without any change.
  * **Brand-new users** cannot be created until you either:
    * enable [SCIM provisioning](https://grafana.com/docs/grafana/latest/setup-grafana/configure-access/configure-scim-provisioning/) (Grafana's recommended path for automatic user lifecycle in Cloud), so users are provisioned by your identity provider before C1 assigns organization roles; or
    * enable the basic login form on the instance (set `disable_login_form = false`), which permits instance-level invites for external users.

  Managing membership directly through the grafana.com portal is a separate API and credential (a Grafana Cloud Access Policy token) that the connector's instance service-account token cannot use.
</Warning>

<Note>
  **Grafana Cloud: provisioning organization roles for externally synced users**

  In Grafana Cloud, users who sign in through an external identity provider (such as Grafana.com SSO, Okta, Azure AD, or any OAuth/SAML provider) have their organization roles controlled by that provider. By default, Grafana blocks API-level role changes for these users, which prevents C1 from provisioning organization entitlements for them.

  To allow C1 to manage organization roles for these users, enable **Skip org role sync** for the relevant SSO provider in your Grafana instance:

  1. In Grafana, go to **Administration** → **Authentication**.
  2. Select the SSO provider your users log in with.
  3. Enable **Skip org role sync** (equivalent to setting `skip_org_role_sync = true`).

  Once this is enabled, Grafana stops overriding org roles on login and C1 becomes the authoritative source for role assignments. This is a global setting that applies to all users under that provider.

  This step is not required for self-hosted Grafana instances using basic (username/password) authentication.
</Note>

## Account access origin

Starting with connector version 0.2.3, each synced account's profile includes attributes that identify how the user's access originated. They appear in the account's **Profile attributes** in C1 and support access reviews where the origin of access matters:

| Attribute | Type | Present | Meaning |
| :- | :- | :- | :- |
| `is_externally_synced` | Boolean | Only when Grafana returns the flag (Grafana Cloud) | `true` when the user's **organization role** is managed by an external identity provider (role sync), taken verbatim from Grafana's native `isExternallySynced` flag; `false` when the role is managed locally. Omitted entirely when Grafana does not return the flag (see below). |
| `auth_labels` | String | Externally authenticated users only | The external authentication provider(s) associated with the user (for example, `grafana.com` or an OAuth/SAML provider name), joined with `; ` when there is more than one. Absent — not an empty string — for local users. |

`is_externally_synced` surfaces Grafana's native `isExternallySynced` flag verbatim and only when Grafana actually returns it. Whether the flag is returned depends on the endpoint the connector reads, which differs by mode:

* **Grafana Cloud** reads the organization users endpoint (`/api/org/users`), which always returns the flag, so `is_externally_synced` is present and mirrors Grafana's value exactly. It reflects **only** whether the user's organization role is managed by an external identity provider (role sync) — it is **not** derived from `auth_labels`, which is a different concept (how the user authenticated). In Grafana Cloud every user authenticates through grafana.com, so `auth_labels` is effectively always `grafana.com`; an admin whose role is managed locally therefore reports `is_externally_synced: false` even though their `auth_labels` show `grafana.com`.
* **Self-hosted Grafana** reads the global users endpoint (`/api/users`), which does not return the flag. Rather than derive a value from a different concept, the connector omits `is_externally_synced` from the profile entirely. Use `auth_labels` to reason about authentication provenance in this mode.

## Gather Grafana credentials

Configuring the connector requires credentials obtained in your Grafana instance. The credentials you need depend on whether you are connecting to **Grafana Cloud** or a **self-hosted Grafana** instance.

<Tabs>
  <Tab title="Grafana Cloud">
    For Grafana Cloud, the connector authenticates using a **service account token**. Basic username/password authentication is not supported in Cloud mode.

    To create a service account token:

    1. In your Grafana Cloud instance, go to **Administration** → **Users and access** → **Service accounts**.
    2. Click **Add service account**, give it a name, and assign it the **Admin** role.
    3. Make sure the service account's **basic role** is set to at least **Viewer** — not **No basic role**. See the callout below; this must be done in the Grafana UI.
    4. Open the new service account and click **Add service account token**.
    5. Copy and save the generated token — it will not be shown again.

    <Warning>
      **A basic role is required, and no RBAC action substitutes for it**

      The connector calls `GET /api/org` both for the initial credential validation and, in Cloud mode, on every sync as the Organization resource-sync source — so this is not a one-time check, it's needed for every sync. That endpoint is gated by a Grafana **basic role**, not by an RBAC action: a service account set to **No basic role** returns `403` there even when every RBAC action below is granted, and assigning `orgs:read` does not satisfy it. Setting the basic role to **Viewer** does.

      Set this in the Grafana UI (**Administration** → **Users and access** → **Service accounts** → your service account → **Basic role**). It cannot currently be set through the API — `PATCH /api/serviceaccounts/{id}` with `{"role":"Viewer"}` returns `HTTP 500` on Grafana Cloud.
    </Warning>

    Use an **Admin** service-account role, or a credential with equivalent permissions. The connector requires six RBAC actions:

    | Permission | Endpoint | Needed for |
    | :- | :- | :- |
    | `org.users:read` | `GET /api/org/users` | Account sync (Cloud mode reads organization users) |
    | `teams:read` | `GET /api/teams/search` | Team sync |
    | `teams.permissions:read` | `GET /api/teams/{id}/members` | Team membership grants |
    | `teams.roles:read` | `GET /api/access-control/teams/{id}/roles` | The RBAC roles each team holds — Role sync only |
    | `serviceaccounts:read` | `GET /api/serviceaccounts/search` | Service account sync |
    | `roles:read` | `GET /api/access-control/roles` | Role sync only |

    These six actions cover **sync only**. When provisioning is enabled (`BATON_PROVISIONING`), granting and revoking access calls separate write endpoints that need additional RBAC actions. The actions you need depend on which provisioning capability is enabled — **account provisioning** (creating brand-new users) and **entitlement provisioning** (granting/revoking org roles and team membership) call different endpoints and do not require the same permissions.

    **Account provisioning** (`CreateAccount`, `Delete`):

    | Permission | Needed for |
    | :- | :- |
    | `org.users:add` | Inviting a brand-new user into the org (`CreateAccount`, invite-based in Cloud mode, via `POST /api/org/invites`) |
    | `org.users:remove` | Removing a user from the org (`Delete`, via `DELETE /api/org/users/{id}`) — this permission is shared with entitlement provisioning's Revoke below |

    **Entitlement provisioning** (Grant/Revoke on the Organizations and Teams resource types):

    | Permission | Needed for |
    | :- | :- |
    | `org.users:write` | Updating an existing member's org role (Grant, via `PATCH /api/org/users/{id}`) |
    | `org.users:remove` | Removing a user from the org (Revoke, via `DELETE /api/org/users/{id}`) — shared with account provisioning's `Delete` above |
    | `teams.permissions:write` | Adding or removing a team member (Grant/Revoke, via `POST`/`DELETE /api/teams/{id}/members[/{userId}]`) |

    `org.users:add` is reached only by `CreateAccount`'s invite path. A customer who enables entitlement provisioning without account provisioning does not need it: in Cloud mode, Grant only ever updates the role of a user who is already an org member (via `PATCH /api/org/users/{id}`), or fails outright if the user isn't already a member — it never calls the invite endpoint.

    `org.users:remove`, by contrast, is needed independently by **both** capabilities: account provisioning's `Delete` and entitlement provisioning's Revoke both call the same `DELETE /api/org/users/{id}` endpoint. A customer who enables only one of the two capabilities still needs this permission for that capability alone.

    Team sync always reads team membership, so `teams.permissions:read` is required unconditionally. `teams.roles:read` is only needed when Role sync is enabled — the connector only fetches each team's RBAC roles in that case — same as `roles:read`. A narrower token that previously synced only users/orgs may still fail List after this upgrade, since `teams:read` and `teams.permissions:read` are newly required for team sync regardless of Role sync.

    <Note>
      **With Role sync enabled, `roles:read` and `teams.roles:read` come as a package**

      No built-in Grafana role grants one of these actions without the other: `fixed:roles:reader` and `fixed:roles:writer` each grant both together, and the basic roles that grant `roles:read` (`basic:admin` and `basic:grafana_admin`) also grant `teams.roles:read`. `basic:viewer` and `basic:editor` grant neither. This is expected — when Role sync is disabled, the connector needs neither action, so there is nothing to isolate; when Role sync is enabled, plan on granting both together (or use a **custom role** if you need to diverge from Grafana's built-in roles for some other reason).
    </Note>

    You will need:

    * Your Grafana Cloud instance URL (e.g., `https://your-org.grafana.net`)
    * The service account token generated above

    **Done.** Next, move on to the connector configuration instructions.
  </Tab>

  <Tab title="Self-hosted Grafana">
    For self-hosted Grafana, the connector authenticates using the username and password of a Grafana admin account.

    You will need:

    * The username and password for a Grafana account with **admin-level permissions**
    * Your Grafana instance URL

    On Grafana Enterprise that account also needs `teams:read` (`GET /api/teams/search`) and `teams.permissions:read` (team membership, from `GET /api/teams/{id}/members`) unconditionally, plus `roles:read` and `teams.roles:read` together when Role sync is enabled — the connector only fetches each team's RBAC roles in that case. Self-hosted reads users from the global users endpoint rather than `GET /api/org/users`, so `org.users:read` — required in Cloud mode for user sync — does not apply to self-hosted user sync here (it still applies to organization-membership grants below). See the permission table in the **Grafana Cloud** tab above for the full endpoint-by-endpoint mapping, and note that no built-in Grafana role grants `roles:read` without also granting `teams.roles:read` (or vice versa) — that's expected when Role sync is enabled; a custom role is only needed if you must diverge from that pairing. On OSS, leave Role sync disabled — the access-control API is not present, and the connector skips that step instead of failing.

    Service account sync is unconditional on both Cloud and self-hosted, so `serviceaccounts:read` (`GET /api/serviceaccounts/search`) is also required here, same as in the Cloud tab.

    Beyond team, role, and service-account permissions, self-hosted org and user sync call instance-admin-scoped endpoints:

    | Endpoint | Needed for |
    | :- | :- |
    | `GET /api/orgs` | Self-hosted organization sync (lists every org on the instance) |
    | `GET /api/orgs/{id}/users` | Organization membership grants |
    | `GET /api/users` | Global user sync (self-hosted reads all users from this endpoint, not `GET /api/org/users`) |

    These three endpoints are part of Grafana's legacy admin API and are gated by the account's **Grafana Admin** (server admin) flag on **OSS**, where RBAC is not available. On **Grafana Enterprise**, RBAC exposes granular actions that can gate these same endpoints instead of relying on the server-admin flag: `orgs:read` for `GET /api/orgs`, `users:read` (scope `global.users:*`) for `GET /api/users`, and `org.users:read` for `GET /api/orgs/{id}/users`.

    When provisioning is enabled (`BATON_PROVISIONING`), self-hosted also calls write endpoints. Like the read endpoints above, these are part of Grafana's legacy admin API and are gated by the account's **Grafana Admin** (server admin) flag.

    **Account provisioning** (`CreateAccount`, `Delete`):

    | Endpoint | Needed for |
    | :- | :- |
    | `POST /api/admin/users` | Creating a new user (`CreateAccount`) |
    | `DELETE /api/admin/users/{id}` | Deleting a user (`Delete`) |

    `CreateAccount`'s already-exists handling looks up the existing account by scanning the first page of the global users list (`GET /api/users`, already required for user sync above), not by ID — so no additional permission beyond user sync is needed for that path.

    **Entitlement provisioning** (Grant/Revoke on organization and team membership):

    | Endpoint | Needed for |
    | :- | :- |
    | `GET /api/users/{id}` | Looking up a user's details when granting org membership (Grant) |
    | `GET /api/users/{id}/orgs` | Checking a user's existing org memberships before Grant/Revoke |
    | `POST /api/orgs/{id}/users` | Adding a user to an organization with a role (Grant) |
    | `DELETE /api/orgs/{id}/users/{userId}` | Removing a user from an organization — needed for both Grant (when changing an existing member's role, which removes then re-adds them) and Revoke |
    | `POST /api/teams/{id}/members` | Adding a user to a team (Grant) |
    | `DELETE /api/teams/{id}/members/{userId}` | Removing a user from a team (Revoke) |

    On Grafana Enterprise, team membership Grant/Revoke is additionally gated by `teams.permissions:write` — the same RBAC action documented for Cloud's team provisioning above. On OSS, these two endpoints are instead gated by the account's **org Admin** role — a lower, per-organization privilege than the instance-wide **Grafana Admin** server-admin flag that gates the rest of this table.

    **Done.** Next, move on to the connector configuration instructions.
  </Tab>
</Tabs>

## Configure the Grafana connector

<Warning>
  To complete this task, you'll need:

  * The **Connector Administrator** or **Super Administrator** role in C1
  * Access to the set of Grafana credentials gathered by following the instructions above
</Warning>

<Tabs>
  <Tab title="Cloud-hosted">
    **Follow these instructions to use a built-in, no-code connector hosted by C1.**

    <Steps>
      <Step>
        In C1, navigate to **Apps** > **Connectors** and click **Add connector**.
      </Step>

      <Step>
        Search for **Grafana** and click **Add**.
      </Step>

      <Step>
        Choose where to add the connector: **Create a new app**, or **Add to an existing app** (then select the app).

        If you're creating a new app, choose whether to link it to an application discovered from your identity provider: select **Yes** and pick the IdP application, or **No** to continue with just the connector.
      </Step>

      <Step>
        Set the connector's **Name** and, optionally, a **Description**.
      </Step>

      <Step>
        Click the pencil icon next to **Owners** to choose who can configure and manage this connector.
      </Step>

      <Step>
        Click **Add**. The connector is created and its configuration page opens.
      </Step>

      <Step>
        Find the **Settings** area of the page and click **Edit**.
      </Step>

      <Step>
        Paste your Grafana instance URL into the **Instance URL** field.
      </Step>

      <Step>
        Enter your credentials based on your Grafana deployment type:

        * **Grafana Cloud**: Select "API Key" as the auth method and paste your service account token into the **API Token** field.

        * **Self-hosted Grafana**: Select "Basic Authentication" as the auth method and paste the admin account's username and password into the **Username** and **Password** fields.
      </Step>

      <Step>
        Click **Save**.
      </Step>

      <Step>
        The connector's label changes to **Syncing**, followed by **Connected**. You can view the logs to ensure that information is syncing.
      </Step>
    </Steps>

    **Done.** Your Grafana connector is now pulling access data into C1.
  </Tab>

  <Tab title="Self-hosted">
    **Follow these instructions to use the Grafana connector, hosted and run in your own environment.**

    When running in service mode on Kubernetes, a self-hosted connector maintains an ongoing connection with C1, automatically syncing and uploading data at regular intervals. This data is immediately available in the C1 UI for access reviews and access requests.

    ### Resources

    * [GitHub repository](https://github.com/conductorone/baton-grafana): Access the source code, report issues, or contribute to the project.

    ### Step 1: Set up a new Grafana connector

    <Steps>
      <Step>
        In C1, navigate to **Apps** > **Connectors** and click **Add connector**.
      </Step>

      <Step>
        Search for **Baton** and click **Add**.
      </Step>

      <Step>
        Choose where to add the connector: **Create a new app**, or **Add to an existing app** (then select the app).

        If you're creating a new app, choose whether to link it to an application discovered from your identity provider: select **Yes** and pick the IdP application, or **No** to continue with just the connector.
      </Step>

      <Step>
        Set the connector's **Name** and, optionally, a **Description**.
      </Step>

      <Step>
        Click the pencil icon next to **Owners** to choose who can configure and manage this connector.
      </Step>

      <Step>
        Click **Add**. The connector is created and its configuration page opens.
      </Step>

      <Step>
        In the **Settings** area of the page, click **Edit**.
      </Step>

      <Step>
        Click **Rotate** to generate a new Client ID and Secret.

        Carefully copy and save these credentials. We'll use them in Step 2.
      </Step>
    </Steps>

    ### Step 2: Create Kubernetes configuration files

    Create two Kubernetes manifest files for your Grafana connector deployment. Use the secrets configuration that matches your Grafana deployment type.

    #### Secrets configuration — Grafana Cloud

    ```yaml expandable theme={null}
    # baton-grafana-secrets.yaml
    apiVersion: v1
    kind: Secret
    metadata:
      name: baton-grafana-secrets
    type: Opaque
    stringData:
      # C1 credentials
      BATON_CLIENT_ID: <C1 client ID>
      BATON_CLIENT_SECRET: <C1 client secret>

      # Grafana Cloud credentials
      BATON_HOSTNAME: <Grafana Cloud instance URL>       # e.g. https://your-org.grafana.net
      BATON_API_TOKEN: <service account token>

      # Optional: Include if you want C1 to provision access using this connector
      BATON_PROVISIONING: true
    ```

    #### Secrets configuration — Self-hosted Grafana

    ```yaml expandable theme={null}
    # baton-grafana-secrets.yaml
    apiVersion: v1
    kind: Secret
    metadata:
      name: baton-grafana-secrets
    type: Opaque
    stringData:
      # C1 credentials
      BATON_CLIENT_ID: <C1 client ID>
      BATON_CLIENT_SECRET: <C1 client secret>

      # Self-hosted Grafana credentials
      BATON_HOSTNAME: <Grafana instance URL>
      BATON_USERNAME: <Grafana account username>
      BATON_PASSWORD: <Grafana account password>

      # Optional: Include if you want C1 to provision access using this connector
      BATON_PROVISIONING: true
    ```

    See the connector's README or run `--help` to see all available configuration flags and environment variables.

    #### Deployment configuration

    ```yaml expandable theme={null}
    # baton-grafana.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: baton-grafana
      labels:
        app: baton-grafana
    spec:
      selector:
        matchLabels:
          app: baton-grafana
      template:
        metadata:
          labels:
            app: baton-grafana
            baton: true
            baton-app: grafana
        spec:
          containers:
          - name: baton-grafana
            image: public.ecr.aws/conductorone/baton-grafana:latest
            imagePullPolicy: IfNotPresent
            env:
            - name: BATON_HOST_ID
              value: baton-grafana
            envFrom:
            - secretRef:
                name: baton-grafana-secrets
    ```

    ### Step 3: Deploy the connector

    <Steps>
      <Step>
        Create a namespace in which to run C1 connectors (if desired), then apply the secret config and deployment config files.
      </Step>

      <Step>
        Check that the connector data uploaded correctly. In C1, click **Apps**. On the **Managed apps** tab, locate and click the name of the application you added the Grafana connector to. Grafana data should be found on the **Entitlements** and **Accounts** tabs.
      </Step>
    </Steps>

    **Done.** Your Grafana connector is now pulling access data into C1.
  </Tab>
</Tabs>
