Skip to main content

Service Accounts

A service account is a non-human identity that authenticates to the Akuity Platform with a token from an OIDC issuer you trust, such as GitHub Actions or a Kubernetes cluster. The workload presents the short-lived token its platform already gives it, and the Akuity Platform exchanges it for a short-lived Akuity credential. Nothing is stored in the pipeline or the cluster, so there is no secret to leak or rotate.

Compare this with API keys, which are long-lived secrets you have to distribute and protect. Prefer a service account wherever the workload can obtain an OIDC token.

The flow has three parts:

  1. Register the OIDC issuer once, so the platform knows where to get its signing keys.
  2. Create a service account bound to that issuer, with a claim match that says which tokens may use it and which role it holds.
  3. The workload exchanges its OIDC token for an Akuity credential and uses it like a login.
note

Service accounts are rolled out per organization. If the Service Accounts tab is not visible on your organization page, contact Akuity support.

Registering an OIDC issuer​

Organization owners, or members with a custom role that grants it, can manage issuers and service accounts.

  1. Select the organization from the pull down menu and switch to the Service Accounts tab.

  2. In the OIDC Issuers section, click New Issuer.

  3. Enter a Name, for example github-actions.

  4. Enter the Issuer URL. This must be exactly the iss claim of the tokens the issuer produces, for example https://token.actions.githubusercontent.com.

  5. Choose a Key source, which is where the platform reads the issuer's public signing keys from:

    Key sourceUse when
    DiscoveryThe issuer publishes <issuer URL>/.well-known/openid-configuration on a public address. Works for GitHub, GitLab, Okta, Entra ID and most managed Kubernetes clusters.
    JWKS URLThe issuer's key set is published on a public address that is not derived from the issuer URL.
    Static JWKSThe keys are not reachable from the internet, for example a private Kubernetes cluster. Paste the JWKS document.

    Keys fetched from a URL are refreshed automatically, so key rotation on the issuer's side needs no action. A static JWKS must be updated by hand when the issuer rotates its keys.

  6. Optionally set Audience overrides and the Token TTL, then click Create.

After creation the issuer shows its default audience, https://akuity.cloud/api/v1/organizations/<organization ID>. Tokens must carry this value in their aud claim, or one of the audience overrides if you set any. A token may list more audiences than that, as long as one matches.

The token TTL is the lifetime of the Akuity credentials issued for tokens from this issuer, between 1 minute and 24 hours. The default is 1 hour.

Creating a service account​

  1. In the Service Accounts section, click Create Service Account.

  2. Enter a Description, for example ci-deployer.

  3. Select the Issuer the account trusts. Each service account is bound to exactly one issuer.

  4. Fill in the Claim match. Each row pairs a claim name from the OIDC token with a pattern. The pattern is a regular expression that must match the whole claim value. Every row must match for the exchange to succeed.

    The sub claim is required, because it is what identifies the workload: the repository and branch on GitHub, the namespace and service account on Kubernetes. A sub pattern that would accept any value is rejected. Nested claims are addressed with a dotted path, such as kubernetes.io.namespace.

    Some examples:

    ClaimPatternMatches
    subrepo:acme/app:ref:refs/heads/mainPushes to main of the acme/app GitHub repository
    subrepo:acme/app:environment:productionJobs running in the production GitHub environment
    subrepo:acme/.*:ref:refs/tags/v.*Tag builds in any repository of the acme organization
    subsystem:serviceaccount:ci:deployerThe deployer service account in the ci namespace of a Kubernetes cluster
    actorrenovate\[bot\]Only workflows started by that GitHub actor
  5. Assign a Role or one or more custom roles. Organization service accounts hold the Member role and organization custom roles. The Owner role cannot be given to a service account.

  6. Optionally restrict the IP Allowlist to the addresses or CIDR ranges the workload runs from, if your plan includes it. Both the exchange and every request made with the credential must come from an allowed address.

  7. Click Create and note the service account ID shown in the list. The workload needs it, along with the organization ID.

Workspace service accounts​

A service account can also be scoped to a single workspace. Open the workspace settings, select Service Accounts, and create the account there. It binds to one of the organization's issuers in the same way, but holds a workspace role (Admin or Member) or workspace custom roles, and can only reach that workspace, exactly like a workspace API key.

Exchanging a token​

The workload trades its OIDC token for an Akuity credential, then uses the credential as a bearer token. No login or API key is involved at any point.

The simplest way is the Akuity CLI, which reads the OIDC token from a flag, a file, or stdin and prints the credential:

akuity service-account exchange-token \
--organization-id <organization ID> \
--service-account-id <service account ID> \
--subject-token-file token.jwt

Export the result as AKUITY_SERVICE_ACCOUNT_TOKEN and every other akuity command authenticates with it. The same variable is honored by the Terraform provider.

You can also call the exchange endpoint directly. It uses the parameter names of RFC 8693 token exchange but takes a JSON body and requires the service account ID, so it is not a drop-in OAuth token endpoint:

curl -sS -X POST "https://akuity.cloud/api/v1/organizations/<organization ID>/oauth/token" \
-H 'Content-Type: application/json' \
-d '{
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"subject_token": "<OIDC token>",
"service_account_id": "<service account ID>"
}'
{
"access_token": "<credential>",
"issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_type": "Bearer",
"expires_in": 3600
}

Send the credential as Authorization: Bearer <credential> on API requests. It expires after the issuer's token TTL. Exchange a new one for each run rather than storing it.

Examples​

GitHub Actions gives every job an OIDC token when the workflow requests the id-token: write permission. Register https://token.actions.githubusercontent.com as an issuer with the Discovery key source, and bind a service account with a sub pattern such as repo:acme/app:ref:refs/heads/main.

The token's sub claim depends on how the job runs: repo:<owner>/<repo>:ref:refs/heads/<branch> for a branch, repo:<owner>/<repo>:ref:refs/tags/<tag> for a tag, repo:<owner>/<repo>:pull_request for a pull request, and repo:<owner>/<repo>:environment:<name> for a job that targets a GitHub environment. See GitHub's OIDC documentation for every claim available.

name: deploy
on:
push:
branches: [main]

permissions:
id-token: write
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
env:
AKUITY_ORG_ID: <organization ID>
AKUITY_SERVICE_ACCOUNT_ID: <service account ID>
steps:
- name: Install the Akuity CLI
run: |
curl -sSL -o akuity "https://dl.akuity.io/akuity-cli/$(curl -sL https://dl.akuity.io/akuity-cli/stable.txt)/linux/amd64/akuity"
chmod +x akuity && sudo mv akuity /usr/local/bin/akuity

- name: Authenticate with the Akuity Platform
run: |
audience="https://akuity.cloud/api/v1/organizations/$AKUITY_ORG_ID"
token=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$audience" | jq -r .value)
credential=$(echo "$token" | akuity service-account exchange-token \
--organization-id "$AKUITY_ORG_ID" \
--service-account-id "$AKUITY_SERVICE_ACCOUNT_ID" \
--subject-token-file -)
echo "::add-mask::$credential"
echo "AKUITY_SERVICE_ACCOUNT_TOKEN=$credential" >> "$GITHUB_ENV"

- name: Use the CLI
run: akuity argocd instance list --organization-id "$AKUITY_ORG_ID"

The audience query parameter makes GitHub put the issuer's default audience into the token. Without it GitHub uses the repository URL, and the exchange is refused.

Revoking credentials​

Credentials are short-lived, but you can cut them off early:

  • Select Revoke tokens on a service account to invalidate every credential issued to it. New exchanges keep working.
  • Disable the service account to stop both existing credentials and new exchanges.
  • Changing a service account's issuer or claim match, or changing an issuer's URL or keys, also revokes the credentials issued under the old settings. The affected workloads simply exchange a new token on their next run.

Every change to issuers and service accounts is recorded in the organization's audit logs, and actions taken with a credential are attributed to the service account.