Skip to main content
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 below produce the same end state.
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.

Run the step

skyhook github setup
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.
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 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

FlagDescription
--cloud <gcp|aws>Skip cloud auto-detection
--github-org <name>GitHub organization (defaults to the git remote in the current directory)
--template-onlyEmit a Terraform/CloudFormation template instead of executing
--dry-runPrint the plan without creating anything
--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.

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.
Run skyhook onboard status first to see whether OIDC is already marked as configured for your organization.

What the CLI creates

On GCP, the CLI creates a full Workload Identity Federation stack in the current gcloud project:
ResourceNamePurpose
Workload Identity Poolgithub-poolContainer for OIDC providers
OIDC Providergithub-providerAccepts tokens from token.actions.githubusercontent.com; attribute mapping google.subject=assertion.sub, attribute.repository=assertion.repository
Service Accountgithub-actions-sa@<project>.iam.gserviceaccount.comImpersonated by GitHub Actions
SA IAM bindingroles/iam.workloadIdentityUser scoped to principalSet://.../attribute.repository/<org>/*Lets any repo under your GitHub org impersonate the SA
Project IAM bindingsroles/artifactregistry.writer, roles/container.developerLets 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
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.

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.
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).

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 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):
# 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
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.
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
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.
# 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
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:
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.
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.

AWS manual setup

# 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:
{
  "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 roleAmazonEC2ContainerRegistryPowerUser (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.
    {
      "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 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

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

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:
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"
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:
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:
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. For BYOK-only clusters, use the BYOK tab on GitHub Actions setup instead — OIDC doesn’t apply there.