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

# GitHub OIDC setup

> Automate GitHub Actions OIDC trust with AWS or GCP using the Skyhook CLI, or set it up by hand

GitHub Actions can authenticate to AWS and GCP using short-lived OIDC tokens instead of long-lived access keys. The Skyhook CLI automates the setup: it detects existing OIDC configuration in your cloud account, creates the required resources (or extends them), and prints the GitHub variables your workflows need. If you'd rather do it by hand — or your environment doesn't fit the automated path — the [manual walkthroughs](#manual-setup) below produce the same end state.

<Info>
  OIDC is the recommended cloud authentication path. For the static-credential alternatives (GCP service account keys, AWS IAM access keys, Azure client secret, BYOK) and the non-OIDC pieces of the CI/CD setup — the Deployment GitHub App, protected branches, the verify step — see [GitHub Actions setup](/infrastructure/cicd-setup/github-integration).
</Info>

## Run the step

```bash theme={"system"}
skyhook github setup
```

<Note>
  `skyhook github setup` and `skyhook onboard github` are aliases — same flags, same behavior. The `setup` form is the canonical one; the `onboard github` form exists because the step is also the GitHub stage of the full `skyhook onboard` wizard.
</Note>

The command is interactive and:

1. **Resolves the cloud provider** from your Skyhook-registered clusters. A single supported cloud (GCP or AWS) is selected silently; multi-cloud setups prompt. BYOK (`other`) clusters don't influence detection — if all your clusters are BYOK, the CLI falls back to a generic `gcp`/`aws` prompt. For BYOK-only setups, use the [BYOK tab on GitHub Actions setup](/infrastructure/cicd-setup/github-integration) instead (OIDC doesn't apply there).
2. **Checks tooling and authentication** for `gcloud` / `aws` and `gh`. For GCP, the CLI actively exercises the credential with `gcloud auth print-access-token` so expired sessions are caught here, not mid-setup.
3. **Detects the GitHub org** from `git remote get-url origin` in the current directory, falling back to a prompt.
4. **Scans your cloud account** for an existing GitHub OIDC configuration (read-only; skipped in `--template-only` mode or when the cloud CLI isn't installed).
5. **Creates or extends the setup** based on what was found.
6. **Prints the GitHub variables** to copy into your repository or organization.

### Flags

| Flag                  | Description                                                               |
| --------------------- | ------------------------------------------------------------------------- |
| `--cloud <gcp\|aws>`  | Skip cloud auto-detection                                                 |
| `--github-org <name>` | GitHub organization (defaults to the git remote in the current directory) |
| `--template-only`     | Emit a Terraform/CloudFormation template instead of executing             |
| `--dry-run`           | Print the plan without creating anything                                  |

<Note>
  `--github-org` is the **GitHub** organization. The Skyhook org is set via `skyhook config set org <name>` or the root-level `-o/--org` flag — don't confuse the two.
</Note>

## Prerequisites

1. **Cloud CLI installed and authenticated**
   * GCP: `gcloud` with an active login (not expired)
   * AWS: `aws` with `aws configure` or an active SSO session
2. **Cloud admin permissions** on the target project or account
   * GCP: manage Workload Identity Pools, service accounts, and IAM bindings
   * AWS: `iam:CreateOpenIDConnectProvider`, role management, and CloudFormation deploy
3. **`gh` CLI** (optional) — if installed, makes setting GitHub variables a one-liner
4. **A connected cluster in Skyhook** (recommended) — the CLI auto-detects your cloud provider from registered clusters. Without one, you'll be prompted to pick `gcp` or `aws`.

<Tip>
  Run `skyhook onboard status` first to see whether OIDC is already marked as configured for your organization.
</Tip>

## What the CLI creates

<Tabs>
  <Tab title="GCP">
    On GCP, the CLI creates a full Workload Identity Federation stack in the current `gcloud` project:

    | Resource               | Name                                                                                         | Purpose                                                                                                                                                |
    | ---------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | Workload Identity Pool | `github-pool`                                                                                | Container for OIDC providers                                                                                                                           |
    | OIDC Provider          | `github-provider`                                                                            | Accepts tokens from `token.actions.githubusercontent.com`; attribute mapping `google.subject=assertion.sub, attribute.repository=assertion.repository` |
    | Service Account        | `github-actions-sa@<project>.iam.gserviceaccount.com`                                        | Impersonated by GitHub Actions                                                                                                                         |
    | SA IAM binding         | `roles/iam.workloadIdentityUser` scoped to `principalSet://.../attribute.repository/<org>/*` | Lets any repo under your GitHub org impersonate the SA                                                                                                 |
    | Project IAM bindings   | `roles/artifactregistry.writer`, `roles/container.developer`                                 | Lets the SA push images to Artifact Registry and deploy to GKE                                                                                         |

    Scope is enforced at the **binding** (the `principalSet` pattern), not on the provider. The provider itself has no `attributeCondition`, so the same pool/provider can be extended later by adding a binding for another org rather than reconfiguring the provider.

    After success, the CLI prints:

    ```
    Add these to your GitHub repository variables:

      WIF_PROVIDER: projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider
      WIF_SA:       github-actions-sa@my-project.iam.gserviceaccount.com
    ```

    <Note>
      The CLI labels the service-account value `WIF_SA` in the terminal for width, but the generated workflows expect the GitHub variable name to be `WIF_SERVICE_ACCOUNT`. Use `WIF_SERVICE_ACCOUNT` in GitHub.
    </Note>
  </Tab>

  <Tab title="AWS">
    On AWS, the CLI creates all the resources directly via `aws iam` commands (matching how GCP execute mode creates its resources end-to-end):

    | Resource                  | Details                                                                                           |
    | ------------------------- | ------------------------------------------------------------------------------------------------- |
    | OIDC Identity Provider    | URL `https://token.actions.githubusercontent.com`, audience `sts.amazonaws.com`                   |
    | IAM Role                  | `github-actions-deploy-role` — trust policy allows `repo:<org>/*:*` from the GitHub OIDC provider |
    | Managed policy (attached) | `AmazonEC2ContainerRegistryPowerUser`                                                             |
    | Inline policy             | `EKSDeployAccess` with `eks:AccessKubernetesApi`, `eks:DescribeCluster`, `eks:ListClusters`       |

    After setup, the CLI prints the role ARN for you to copy into GitHub repository variables as `AWS_BUILD_ROLE` and `AWS_DEPLOY_ROLE` (both can point at the same role if you don't want to split build/deploy permissions).

    In `--template-only` mode, the CLI skips execution and prints a CloudFormation template that creates the same resources — useful for environments where you want the setup reviewed in PRs or applied from a central IaC pipeline.

    <Warning>
      **Kubernetes RBAC mapping required.** An IAM role with `eks:*` permissions still needs a cluster-side identity before `kubectl apply` works. Use EKS Access Entries (recommended for clusters created after late 2023) or the legacy `aws-auth` ConfigMap. See the [manual AWS walkthrough](#aws-manual-setup) for the RBAC step.
    </Warning>
  </Tab>
</Tabs>

## Existing OIDC detection

The scan is read-only and produces one of four outcomes.

### Nothing found

```
Checking GCP project for existing Workload Identity setup...
(read-only — no changes will be made)
No existing GitHub OIDC configuration found in GCP project my-project.
```

The CLI proceeds with the full setup.

### Found, scope matches your GitHub org

```
Found GitHub OIDC provider in GCP project my-project
  Pool/Provider: github-pool/github-provider
  Service Account: github-actions-sa@my-project.iam.gserviceaccount.com
  Scope: my-org (org-wide)

OIDC is already configured for 'my-org' — no changes needed.
```

Nothing is modified. The CLI exits successfully.

<Note>
  A scope-match detection doesn't record completion in Skyhook's org settings — on a fresh machine, `skyhook onboard status` may still show this step as incomplete even though OIDC is already configured in the cloud. This is a known gap in the detect-and-exit path (the CLI has no force-mark path today).
</Note>

### Found, scope mismatches

Common when one cloud project serves multiple GitHub orgs, or the existing setup is scoped to a single repository. The CLI offers a menu — see [Handling scope mismatch](#handling-scope-mismatch) below.

### Could not check

Usually a permissions issue.

* **GCP** needs `iam.workloadIdentityPools.list` and `iam.serviceAccounts.getIamPolicy`.
* **AWS** needs `iam:ListOpenIDConnectProviders` and `iam:GetRole`.

The CLI asks whether to proceed anyway (safe — creation is idempotent: already-existing pools, providers, and SAs are reused), skip, or generate a template for an admin to apply.

## Handling scope mismatch

You have GitHub OIDC in this cloud account, but it's scoped to a different GitHub org, a single repository, or a scope pattern the CLI couldn't parse. The CLI analyzes the existing setup's bindings and provider `attributeCondition` to pick the safest options:

1. **Extend automatically (recommended)** — shown when the CLI can safely add an IAM binding to the existing service account, mirroring the existing binding style (`attribute.repository_owner/<org>` vs `attribute.repository/<org>/*`). If the existing provider's `attributeCondition` would reject tokens from your org, the CLI also proposes a relaxed replacement and shows the exact diff before running anything. GCP only — AWS trust-policy edits aren't automated.
2. **Create a new parallel OIDC setup** — always available. Creates an independent pool/provider/SA stack. Safest when the existing setup is complex or you don't want to modify it.
3. **Show me how to extend the existing setup myself** — when the CLI has a concrete extend plan, it prints the exact `gcloud` (or `aws iam`) commands for your specific setup. When auto-extend isn't feasible, it prints a diagnostic and recommends the parallel or manual path instead of speculative commands. Either way, run what's shown, then re-run `skyhook github setup`.
4. **Skip for now** — defer and come back later.

All options require cloud admin permissions on the project — they differ in whether the CLI runs the commands or you do.

When auto-extend isn't feasible (per-repo bindings, subject-scoped bindings, multiple service accounts, or an `attributeCondition` the CLI can't safely parse), the CLI prints a diagnostic of what it found, explains why it can't auto-extend, and recommends the parallel setup or manual path.

### Option 1: Extend automatically

When the existing setup uses org-wide bindings with a simple `attributeCondition` (a single equality or `in [...]` list) — or no condition at all — the CLI can extend it without user intervention. If the condition needs updating (e.g., from `assertion.repository_owner == 'OldOrg'` to `assertion.repository_owner in ['OldOrg', 'your-org']`), the update is part of the plan.

The confirmation shows every operation with its exact gcloud command before running anything.

### Option 2: Manually extend the existing setup

Reuse the existing pool/provider by adding a new IAM binding for your GitHub org. When the CLI has enough information to build an extend plan, it prints the exact commands for your specific setup — substituting project number, pool/provider IDs, and SA email. Here's the shape (your output will have concrete values):

<Tabs>
  <Tab title="GCP">
    ```bash theme={"system"}
    # 1. Fetch the project number (needed for the principalSet URI)
    gcloud projects describe PROJECT_ID --format='value(projectNumber)'

    # 2. Add the IAM binding (substitute PROJECT_NUMBER from step 1)
    gcloud iam service-accounts add-iam-policy-binding EXISTING_SA_EMAIL \
      --role='roles/iam.workloadIdentityUser' \
      --member='principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/EXISTING_POOL_NAME/attribute.repository/your-org/*' \
      --project=PROJECT_ID
    ```

    <Note>
      The `--member` string has to match what the existing provider's `attributeMapping` exposes:

      * If the provider maps `attribute.repository_owner` (common for org-scoped setups), use `attribute.repository_owner/your-org` (no `/*`).
      * If it only maps `attribute.repository` (what Skyhook's own parallel setup creates), use `attribute.repository/your-org/*`.

      The CLI's "Show me how to extend" output picks the right shape for your specific setup automatically.
    </Note>

    **Prerequisites**:

    * `roles/iam.serviceAccountAdmin` on the service account
    * If a condition update is also needed: `iam.workloadIdentityPools.providers.update` on the pool
    * The existing provider's `attributeCondition` (if any) must not exclude your org
  </Tab>

  <Tab title="AWS">
    ```bash theme={"system"}
    # 1. Fetch the current trust policy
    aws iam get-role --role-name EXISTING_ROLE_NAME \
      --query 'Role.AssumeRolePolicyDocument' > trust.json

    # 2. Edit trust.json — add "repo:your-org/*:*" to the
    #    token.actions.githubusercontent.com:sub StringLike condition
    #    alongside the existing entries:
    #
    #    "token.actions.githubusercontent.com:sub": [
    #      "repo:existing-org/*:*",
    #      "repo:your-org/*:*"
    #    ]

    # 3. Apply the updated policy
    aws iam update-assume-role-policy --role-name EXISTING_ROLE_NAME \
      --policy-document file://trust.json
    ```

    **Prerequisites**:

    * `iam:UpdateAssumeRolePolicy` on the role
    * The role's attached permissions must be appropriate for your org's workflows
  </Tab>
</Tabs>

After extending the existing setup manually, rerun `skyhook github setup` — detection will now show the scope covers your org and exit without changes.

## Manual setup

Use this path when you can't or won't run `skyhook github setup` — restricted environments, admins who type commands personally, or IaC-only shops. The resource names below **match what the CLI creates**, so running the CLI later will detect your setup as already-configured instead of creating a parallel stack.

### GCP manual setup

Substitute `<PROJECT_ID>` and `<GITHUB_ORG>` throughout.

```bash theme={"system"}
# 1. Create the Workload Identity pool
gcloud iam workload-identity-pools create github-pool \
  --location=global \
  --display-name="GitHub Actions Pool" \
  --project=<PROJECT_ID>

# 2. Create the OIDC provider on that pool
gcloud iam workload-identity-pools providers create-oidc github-provider \
  --location=global \
  --workload-identity-pool=github-pool \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --project=<PROJECT_ID>

# 3. Create the service account
gcloud iam service-accounts create github-actions-sa \
  --display-name="GitHub Actions Service Account" \
  --project=<PROJECT_ID>

# 4. Fetch the project number (needed for the principalSet URI)
PROJECT_NUMBER=$(gcloud projects describe <PROJECT_ID> --format='value(projectNumber)')

# 5. Allow any repo under your GitHub org to impersonate the SA
gcloud iam service-accounts add-iam-policy-binding \
  github-actions-sa@<PROJECT_ID>.iam.gserviceaccount.com \
  --role=roles/iam.workloadIdentityUser \
  --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.repository/<GITHUB_ORG>/*" \
  --project=<PROJECT_ID>

# 6. Grant the SA permissions the workflows need
for role in roles/artifactregistry.writer roles/container.developer; do
  gcloud projects add-iam-policy-binding <PROJECT_ID> \
    --member="serviceAccount:github-actions-sa@<PROJECT_ID>.iam.gserviceaccount.com" \
    --role="$role"
done
```

Add these as GitHub repository (or organization) variables under **Settings → Secrets and variables → Actions → Variables**:

* `WIF_PROVIDER` = `projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/github-pool/providers/github-provider`
* `WIF_SERVICE_ACCOUNT` = `github-actions-sa@<PROJECT_ID>.iam.gserviceaccount.com`

<Accordion title="Optional hardening: provider-level attributeCondition">
  The CLI-created provider has **no** `attributeCondition` — scope is enforced at the IAM binding via the `principalSet://.../attribute.repository/<org>/*` pattern. This is intentional: the same pool/provider can be extended later by adding a binding for another org, without reconfiguring the provider.

  If you want defense-in-depth, you can add a provider-level condition that rejects tokens outside your org before they ever reach the binding check. It's an extra layer, not a replacement for the binding constraint:

  ```bash theme={"system"}
  gcloud iam workload-identity-pools providers update-oidc github-provider \
    --location=global \
    --workload-identity-pool=github-pool \
    --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
    --attribute-condition="assertion.repository_owner == '<GITHUB_ORG>'" \
    --project=<PROJECT_ID>
  ```

  Per [Google's Workload Identity Federation with deployment pipelines guide](https://cloud.google.com/iam/docs/workload-identity-federation-with-deployment-pipelines).

  <Warning>
    If you add this condition and later want to extend the setup to a second GitHub org, you'll need to either relax the condition (e.g., `in ['OrgA', 'OrgB']`) or reconfigure the provider — which is exactly the case the CLI handles via the "Update provider condition + extend automatically" option under scope-mismatch handling.
  </Warning>
</Accordion>

### AWS manual setup

```bash theme={"system"}
# 1. Create the OIDC identity provider (once per AWS account)
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com
```

Then create an IAM role with a trust policy that allows GitHub OIDC to assume it. Scope the `sub` claim to your org so random repos can't impersonate it:

```json theme={"system"}
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com" },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringLike": {
        "token.actions.githubusercontent.com:sub": "repo:<GITHUB_ORG>/*:*"
      },
      "StringEquals": {
        "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
      }
    }
  }]
}
```

Attach the permissions the workflows actually need:

* **Build role** — `AmazonEC2ContainerRegistryPowerUser` (or narrower `ecr:*` on a single repository) for pushing to ECR.
* **Deploy role** — an inline policy with EKS client permissions. Do **not** attach `AmazonEKSClusterPolicy` — that's the policy for the EKS control plane's own service role, not a client, and a workflow with it attached will still fail on `aws eks update-kubeconfig`.

  ```json theme={"system"}
  {
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": [
        "eks:DescribeCluster",
        "eks:AccessKubernetesApi",
        "eks:ListClusters"
      ],
      "Resource": "*"
    }]
  }
  ```

**Map the role into Kubernetes.** An IAM role with `eks:AccessKubernetesApi` still needs RBAC inside the cluster before it can `kubectl apply`:

* **EKS Access Entries** (recommended, for clusters created after late 2023) — `aws eks create-access-entry` + `aws eks associate-access-policy` with `AmazonEKSClusterAdminPolicy` (or a narrower one)
* **`aws-auth` ConfigMap** (legacy) — add a `mapRoles` entry for the role ARN; see the AWS-keys tab on [GitHub Actions setup](/infrastructure/cicd-setup/github-integration) for the ConfigMap edit pattern

Add the role ARN(s) as GitHub repository variables under **Settings → Secrets and variables → Actions → Variables**:

* `AWS_BUILD_ROLE` — used by the build workflow (ECR)
* `AWS_DEPLOY_ROLE` — used by the deploy workflow (EKS)

Both can point at the same role if you don't want to split permissions.

## Multi-account and multi-project setups

OIDC configuration applies to the **current** cloud account or project only. If your GitHub Actions workflows push images or deploy to multiple AWS accounts or GCP projects, run `skyhook github setup` in each one — switching `gcloud` config or AWS profile between runs.

The CLI prints a reminder after setup with the account or project name so this is hard to miss.

## Template-only mode

```bash theme={"system"}
skyhook github setup --template-only
```

Prints infrastructure-as-code instead of running the cloud CLI:

* **GCP** — Terraform HCL (`google_iam_workload_identity_pool`, `..._provider`, `google_service_account`, bindings)
* **AWS** — CloudFormation YAML (`AWS::IAM::OIDCProvider`, `AWS::IAM::Role` with the trust policy and EKS access policy baked in)

Save the output to a file, review it, and apply with `terraform apply` or `aws cloudformation deploy`. Useful when you want the setup reviewed in PRs, applied from a central IaC pipeline, or when you don't have `gcloud`/`aws` installed locally — the CLI falls back to template mode automatically in that case.

## Dry run

```bash theme={"system"}
skyhook github setup --dry-run
```

Prints the full plan (resources, names, and GitHub org scope) without creating anything. If `--template-only` is also set, `--dry-run` is ignored — template output is already non-destructive.

## After setup

### GitHub variables

Set the variables printed by the CLI at the org level so every repo under the org can use them:

<Tabs>
  <Tab title="GCP">
    ```bash theme={"system"}
    gh variable set WIF_PROVIDER --org your-org \
      --body "projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider"

    gh variable set WIF_SERVICE_ACCOUNT --org your-org \
      --body "github-actions-sa@my-project.iam.gserviceaccount.com"
    ```
  </Tab>

  <Tab title="AWS">
    ```bash theme={"system"}
    gh variable set AWS_BUILD_ROLE --org your-org \
      --body "arn:aws:iam::123456789012:role/github-actions-deploy-role"

    gh variable set AWS_DEPLOY_ROLE --org your-org \
      --body "arn:aws:iam::123456789012:role/github-actions-deploy-role"
    ```
  </Tab>
</Tabs>

Repo-scoped variables work the same way — drop `--org` and add `--repo owner/name`.

### Status persistence

On successful setup (including a successful Workload Identity binding), the CLI records OIDC completion in Skyhook's org settings. `skyhook onboard status` then shows the GitHub integration step as complete, and Skyhook's workflow codegen knows which variable values to inject into generated YAML.

### Generated workflows

Skyhook's generated GitHub Actions workflows already reference `WIF_PROVIDER` / `WIF_SERVICE_ACCOUNT` (GCP) and `AWS_BUILD_ROLE` / `AWS_DEPLOY_ROLE` (AWS) — no workflow edits needed after setup.

## Troubleshooting

### Google Cloud session expired — reauthentication required

Your `gcloud` credentials have expired. The CLI catches this up-front by exercising the token with `gcloud auth print-access-token`, so the error surfaces before any cloud operations run.

When the CLI prompts, choose **Re-authenticate now and continue** — it runs `gcloud auth login` inline, without an account argument (passing an account forces gcloud into a password reauth flow that breaks for passkey and SSO accounts). Or run it manually:

```bash theme={"system"}
gcloud auth login
```

Then rerun `skyhook github setup`.

### Workload Identity binding failed

The pool, provider, and service account were created, but the `roles/iam.workloadIdentityUser` binding on the SA couldn't be added. OIDC setup is **incomplete** — GitHub Actions can't impersonate the service account until the binding exists, and the CLI will not mark the step complete.

Verify you have `roles/iam.serviceAccountAdmin` on the service account (or project-level IAM admin), then rerun:

```bash theme={"system"}
skyhook github setup
```

The step is idempotent. The existing pool, provider, and SA are reused; only the failed binding is retried.

### Could not check for existing OIDC configuration

Detection failed, usually from missing read permissions.

* **GCP** needs `iam.workloadIdentityPools.list` and `iam.serviceAccounts.getIamPolicy`.
* **AWS** needs `iam:ListOpenIDConnectProviders` and `iam:GetRole`.

If you're confident there's no existing setup, choose **Set up OIDC now** — resource creation is idempotent, so re-running is safe even if something does exist. Otherwise, have an admin apply the `--template-only` output instead.

### Multi-cloud: which cloud does the CLI pick?

See [Multi-account and multi-project setups](#multi-account-and-multi-project-setups). For BYOK-only clusters, use the [BYOK tab on GitHub Actions setup](/infrastructure/cicd-setup/github-integration) instead — OIDC doesn't apply there.
