> For the complete documentation index, see [llms.txt](https://docs.p0.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.p0.dev/integrations/resource-integrations/github.md).

# GitHub

Install P0 for a GitHub organization so its members can request just-in-time roles on its repositories and memberships of its teams.

This page documents how to install P0 for a GitHub organization so its members can request just-in-time roles on its repositories and memberships of its teams. A member requests a role on one repository, or a membership of one team. When the request is approved, P0 makes them a direct collaborator with that role, or adds them to the team, and when access ends, P0 puts back what they had before.

{% hint style="info" %}
The GitHub integration is in beta. Functionality and configuration may change.
{% endhint %}

## How it works

* **You own the GitHub App.** P0 acts on your organization through a GitHub App that you create and install, and only on the repositories you select for it.
* **Only members of the organization get access.** P0 grants roles and team memberships only to members of the organization the GitHub App is installed on. P0 doesn't invite anyone to the organization, so add people to it before they request access.
* **The private key stays in your cloud.** You store the app's private key in AWS Secrets Manager or Google Secret Manager, under a name you choose. P0 never reads the key. The connector does.
* **The connector runs in your cloud.** You deploy P0's connector on AWS Lambda or Google Cloud Run, in the same cloud as the key, from a shell script or Terraform that the installer generates. P0 invokes the connector to read and change roles and team memberships, and the connector calls GitHub as your app.
* **P0 changes direct roles and a person's own team membership only.** P0 counts only a person's direct role on a repository, and never changes access that comes from a team or from the organization's base permission. On a team, P0 counts and changes only the person's own membership, not one they get through a child team.

For what happens when access ends, see [Requesting GitHub access](/integrations/resource-integrations/github/requesting-access.md#when-your-access-ends).

## Before you begin

You deploy the connector and store the GitHub App's private key in the same cloud: on AWS Lambda with AWS Secrets Manager, or on Google Cloud Run with Google Secret Manager. You need:

* **An owner of the GitHub organization**, to create and install the GitHub App. Only organization owners can install it.
* **A verified domain for the organization.** P0 matches each requestor to their GitHub account by email. Each requestor needs a verified email address in GitHub, on one of the organization's [verified domains](https://docs.github.com/en/organizations/managing-organization-settings/verifying-or-approving-a-domain-for-your-organization), that matches the email they sign in to P0 with.
* **P0's IAM management where the connector runs.** P0 invokes the connector through it:
  * On AWS, AWS IAM management for the account the connector runs in. See [AWS](/integrations/resource-integrations/aws.md#setting-up-aws-iam-management).
  * On Google Cloud, Google Cloud IAM management for the project the connector runs in. See [Google Cloud](/integrations/resource-integrations/google-cloud.md#install-iam-management-on-each-project).
* **P0's secret manager integration where the key is**, if you install from the P0 app. The installer lists only the accounts or projects where it's installed:
  * On AWS, the [AWS Secrets Manager](/integrations/resource-integrations/aws-secrets-manager.md) integration for the account that holds the key. That account needs AWS IAM management installed first, as in [AWS](/integrations/resource-integrations/aws.md#setting-up-aws-iam-management).
  * On Google Cloud, Google Secret Manager for the project that holds the key. See [Google Secret Manager](/integrations/resource-integrations/google-secret-manager.md#setting-up-google-secret-manager).
* **Bash, or** [**Terraform**](https://developer.hashicorp.com/terraform/install) **1.4 or later**, to deploy the connector:
  * On AWS, run either where Docker and the AWS CLI are installed, with the AWS CLI signed in to the connector's account. Both copy P0's connector image from Docker Hub into Amazon ECR.
  * On Google Cloud, run the script where the gcloud CLI is installed and signed in, or apply the Terraform with version 6.0 or later of the `hashicorp/google` provider. Either needs permission to enable APIs and to manage Cloud Run, service accounts and IAM in the connector's project, and IAM on the private key's secret.

On AWS, the connector and the private key's secret must be in commercial regions. The GitHub integration doesn't support AWS GovCloud, China, ISO or European Sovereign Cloud regions.

## Install GitHub

Each organization gets its own GitHub App, private key and connector, so repeat these steps for every GitHub organization whose repositories and teams you want P0 to manage.

1. Open the P0 dashboard at [p0.app](https://p0.app) and log in to your organization.
2. In the sidebar, click **Integrations**. Under **Resources**, click **GitHub**.
3. Select the **Organization** component, and click **Add Organization**.

The installer then takes you through three pages: the form for a new organization in step 1, the organization's setup page in steps 2 to 5, and its configure page in step 6.

### Step 1: Add the organization

The form is titled **Installing a new Organization**. Where the private key is stored, the secret's name and where the connector runs can't change once you add the organization, so decide them now. You store the key in the secret in step 3.

1. In **Organization login**, enter your organization's login, as in `github.com/<login>`.
2. Under **Secret manager provider**, choose where the private key will be stored: **AWS Secrets Manager** or **Google Secret Manager**.
3. Under **Connector hosting**, choose where the connector runs, in the same cloud as the key: **AWS Lambda** with AWS Secrets Manager, or **Google Cloud Run** with Google Secret Manager.
4. Fill in the fields for that cloud:

| Field                                                                 | AWS                                                                                                                | Google Cloud                                                                                                         |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| **Installation**, under **Secret manager provider**                   | The account the secret is in. The list shows the accounts where P0's AWS Secrets Manager integration is installed. | The project the secret is in. The list shows the projects where P0's Google Secret Manager integration is installed. |
| **Secrets region**                                                    | The secret's region, such as `us-west-2`                                                                           | Not asked for                                                                                                        |
| **Private key secret**                                                | The name you'll store the key under. Enter the name, not the ARN.                                                  | The name you'll store the key under. Enter the name, not the resource name `projects/<project>/secrets/<name>`.      |
| **AWS account ID** or **GCP project ID**, under **Connector hosting** | The account the connector runs in. The list shows the accounts where P0's AWS IAM management is installed.         | The project the connector runs in. The list shows the projects where P0's Google Cloud IAM management is installed.  |
| **Connector name**                                                    | The Lambda function's name, 2 to 64 characters                                                                     | The Cloud Run service's name, 2 to 49 characters                                                                     |
| **Connector region**                                                  | An AWS region, such as `us-west-2`                                                                                 | A Google Cloud region, such as `us-central1`                                                                         |

5. Click **Next**. P0 saves the organization and opens its setup page, titled **Configuring Organization**, followed by the organization's login.

Each list of accounts or projects selects its first entry for you, so check both before you click **Next**. If a list is empty, install the integration it names first. See [#before-you-begin](#before-you-begin "mention").

A connector name uses lowercase letters, digits and single hyphens, and starts with a letter and ends with a letter or digit. Each organization's connector needs its own name.

The connector reads the secret by its name, in the account and region, or the project, you chose under **Secret manager provider**, so the secret can be in a different account or project from the connector. You can create the secret before or after you add the organization.

On AWS, the secret's name can't end in a hyphen and six letters or digits, such as `-AbCdEf`, because Secrets Manager reads that as the suffix of a full ARN. A secret in another account must be encrypted with a customer-managed KMS key, and both the secret's resource policy and the key's policy must allow the connector's role. In step 5, the connector's shell script won't run until you set the key's ARN in `KMS_KEY_ARN`, and its Terraform won't apply until you set it in `kms_key_arn`.

On Google Cloud, the secret's name has letters, digits, hyphens and underscores only.

If P0 can't save the organization, it shows why at the top of the form, under **Error configuring GitHub**. See [#when-you-add-the-organization](#when-you-add-the-organization "mention") for each message. Fix it, then click **Next** again.

### Step 2: Create the GitHub App

On the setup page, click **Create the GitHub App**. GitHub opens a new GitHub App form for your organization, filled in with these settings:

| Setting                                 | Value                                                                                                                                                                                                                 |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| GitHub App name                         | `P0 repo access <org>`, cut to GitHub's limit of 34 characters. App names are unique across GitHub, so GitHub may say this one is taken. If it does, enter another. Any name works, and you can rename the app later. |
| Webhook                                 | Inactive, with no events                                                                                                                                                                                              |
| Repository permissions                  | **Administration**: Read and write. **Metadata**: Read-only.                                                                                                                                                          |
| Organization permissions                | **Members**: Read and write. **Custom repository roles**: Read-only.                                                                                                                                                  |
| Where can this GitHub App be installed? | Only on this account                                                                                                                                                                                                  |

Review the settings, then click **Create GitHub App**.

The app needs each permission for these jobs:

| Permission                          | What P0 uses it for                                                                                                                                                                                                               |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Administration (read and write)     | Give a requestor their role on a repository, and put back their previous role when access ends. For force-merge access, list a repository's protected branches, and add a requestor to a branch's bypass list or take them off it |
| Metadata (read-only)                | List the repositories the app is installed on, and read a requestor's current role                                                                                                                                                |
| Members (read and write)            | Match a requestor to their GitHub account, check that they're a member of the organization, read the organization's base permission, list its teams, and add a requestor to a team or take them off it                            |
| Custom repository roles (read-only) | List the organization's custom repository roles                                                                                                                                                                                   |

#### Leave out what you don't need

You can leave out a permission if you won't use the accesses that need it. P0 still installs the organization. Only a request that needs the missing permission fails, with a message that names it, such as `members=write`. GitHub gives every app **Metadata** (read-only), so it can't be left out.

| Permission                          | Needed for                              | Without it                                                                                                                                                                                                                 |
| ----------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Administration (read and write)     | Repository roles and force-merge access | Role and force-merge requests fail when P0 grants them, after approval. With no Administration permission at all, the request form also lists no branches for force-merge.                                                 |
| Members (read-only)                 | Every access                            | Every request fails, because P0 can't match the requestor to their GitHub account or check that they're a member of the organization.                                                                                      |
| Members (read and write)            | Team memberships                        | Team requests fail when P0 grants them, after approval. Read-only is enough for repository roles and force-merge access.                                                                                                   |
| Custom repository roles (read-only) | Custom repository roles                 | P0 lists only GitHub's built-in roles, and a request for a custom role fails. Built-in roles still work. If your organization isn't on GitHub Enterprise Cloud, it has no custom roles, so you don't need this permission. |

For example, to offer only repository roles, give the app **Administration** (read and write) and **Members** (read-only). To offer only team memberships, give it **Members** (read and write) and leave out **Administration**.

{% hint style="warning" %}
**Administration (read and write) is close to admin on every repository the app is installed on.** Anyone who holds the app's private key can change who has access to those repositories, and administer them. The repositories you select in step 4 are what limit it, so select only the repositories P0 should manage.

**Members (read and write) covers the whole organization.** Anyone who holds the app's private key can change who is on any of its teams, and so any access that comes from a team, and members' roles in the organization. The repository selection doesn't limit it.
{% endhint %}

To add a permission later, open the app in your organization's GitHub Apps, change the permission, and have an owner of the organization accept the change on the installation. You don't need to change anything in P0. For example, if you created the app with **Members**: Read-only, team requests fail until you change **Members** to Read and write.

### Step 3: Save the private key

1. Once you create the app, GitHub opens its settings page. Under **Private keys**, click **Generate a private key**. GitHub downloads a `.pem` file.
2. Store the file's contents in the secret you named in step 1, in the account and region, or the project, you chose there. The setup page shows the secret's name, where it goes, and the commands that store it. Then delete the download. For example, from the command line:

{% tabs %}
{% tab title="AWS Secrets Manager" %}

```bash
aws secretsmanager create-secret \
  --name <secret-name> \
  --secret-string "file://<downloaded-key>.pem" \
  --region <region>
```

For a secret in another account, also pass `--kms-key-id <kms-key-arn>` with the customer-managed key from step 1. If the secret already exists, store the key in it as a new version with `aws secretsmanager put-secret-value` instead.
{% endtab %}

{% tab title="Google Secret Manager" %}

```bash
gcloud secrets create <secret-name> \
  --replication-policy automatic \
  --data-file "<downloaded-key>.pem" \
  --project <project-id>
```

If the secret already exists, store the key in it as a new version with `gcloud secrets versions add` instead.
{% endtab %}
{% endtabs %}

### Step 4: Install the app on your organization

On the setup page, this is the step titled **Install the app on**, followed by your organization's login. An owner of the organization installs the app:

1. Click the step's button to open your organization's GitHub Apps, and open your app's settings page.
2. Click **Install App**, then click **Install** next to your organization.
3. Under **Repository access**, choose **Only select repositories**, and select the repositories P0 should manage.
4. Click **Install**.

Requestors can ask for roles only on the repositories you select. You can change the selection later in the app's installation settings. If you remove a repository while someone holds a role on it through P0, P0 can't remove that role when their access ends. See [Requesting GitHub access](/integrations/resource-integrations/github/requesting-access.md#if-removing-access-fails). The selection doesn't limit teams: requestors can ask for a membership of any team in the organization.

### Step 5: Deploy the connector

The setup page has the connector's shell script and its Terraform, on the **Shell** and **Terraform** tabs at the bottom. Both are filled in with the values from step 1, pin P0's connector image by digest, and create the same resources. Deploy each organization's connector with one or the other, because the script won't change a connector that the Terraform deployed.

{% tabs %}
{% tab title="AWS Lambda" %}
To deploy with the shell script:

1. Copy the script from the **Shell** tab, and save it as a file, such as `deploy-connector.sh`.
2. If a customer-managed KMS key encrypts the secret, set `KMS_KEY_ARN` near the top of the script to that key's ARN.
3. Run it with `bash deploy-connector.sh`. If the AWS CLI is signed in to another account, the script stops before it changes anything.
4. Click **Next**.

To deploy with Terraform:

1. Copy the Terraform from the **Terraform** tab.
2. Add the Terraform to your configuration. Before you apply it:
   * The Terraform has its own `required_providers` block for the `docker` provider. If your configuration already has one, move `docker` into yours and delete the snippet's block.
   * Add the credential settings your other AWS providers use, such as `profile` or `assume_role`, to the `aws` provider that the Terraform declares for the connector's account and region.
   * If a customer-managed KMS key encrypts the secret, set `kms_key_arn` to that key's ARN.
3. Apply it.
4. Click **Next**.
   {% endtab %}

{% tab title="Google Cloud Run" %}
Store the private key first, as in [#step-3-save-the-private-key](#step-3-save-the-private-key "mention"). The script and the Terraform give the connector read access to the secret, and fail if it doesn't exist.

To deploy with the shell script:

1. Copy the script from the **Shell** tab, and save it as a file, such as `deploy-connector.sh`.
2. Run it with `bash deploy-connector.sh`. If the gcloud CLI isn't signed in, the script stops before it changes anything.
3. Click **Next**.

To deploy with Terraform:

1. Copy the Terraform from the **Terraform** tab.
2. Add the Terraform to your configuration. It has its own `required_providers` block for the `google` provider. If your configuration already has one, move `google` into yours and delete the snippet's block.
3. Apply it.
4. Click **Next**.

P0 looks up the connector's Cloud Run service when you click **Next**, so deploy the connector first. If the setup page shows `Install P0's Google Cloud integration first. P0 invokes the connector as that integration's service account.` in place of the script and the Terraform, install the [Google Cloud](/integrations/resource-integrations/google-cloud.md) integration, then open the organization's setup page again.
{% endtab %}
{% endtabs %}

### Step 6: Validate

P0 opens the organization's configure page. It lists the steps again, with the first four done, and keeps the connector's Terraform and shell script under **Show the connector's Terraform** and **Show the connector's shell script**. The fields from step 1 show below the steps, but you can't change them.

1. In **GitHub App ID**, enter the app's App ID. It's on the app's settings page, under **About**. To find it, click the link to your organization's GitHub Apps in the field's description, and click **Edit** beside the app.
2. Click **Finish**. P0 validates the install, and checks these in order:
   1. P0 can invoke the connector, and the connector can read the private key.
   2. GitHub accepts the private key for the app ID.
   3. The app is installed on the organization.
   4. The installation isn't suspended.

P0 stops at the first check that fails. When every check passes, P0 installs the organization. P0 doesn't check the app's permissions here. A request that needs a permission the app doesn't have fails with a message that names it. See [#leave-out-what-you-dont-need](#leave-out-what-you-dont-need "mention").

If a check fails, P0 shows what to fix at the top of the page, under **Error configuring GitHub**. See [#when-p0-validates-the-install](#when-p0-validates-the-install "mention") for each message. Fix it, then click **Finish** again. If P0 or the connector is denied access right after you deploy the connector, wait a few seconds and click **Finish** again.

{% hint style="success" %}
Your organization's members can now request roles on its repositories and memberships of its teams. See [Requesting GitHub access](/integrations/resource-integrations/github/requesting-access.md).
{% endhint %}

## What the connector's deployment creates

The shell script and the Terraform create the same resources. They give the connector read access to the private key's secret and no other secret. P0 gets only what it needs to invoke the connector.

{% tabs %}
{% tab title="AWS Lambda" %}

| Resource                                               | What it's for                                                                                                                                                                                                                                            |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| An ECR repository                                      | Holds the copy of P0's connector image that Lambda runs. Its tags can't be overwritten, so pushing to the repository can't replace the image the connector runs.                                                                                         |
| A CloudWatch log group, `/aws/lambda/<connector-name>` | The connector's logs, kept for 30 days. Change `LOG_RETENTION_DAYS` in the script, or `log_retention_days` in the Terraform, to keep them for longer or shorter.                                                                                         |
| An IAM role for the connector                          | `secretsmanager:GetSecretValue` on the private key's secret, and on no other secret. `kms:Decrypt` on the secret's KMS key, through Secrets Manager only, if you set the key's ARN. `logs:CreateLogStream` and `logs:PutLogEvents` on its own log group. |
| The Lambda function                                    | Runs the connector. It calls `api.github.com` over the internet, so it needs no VPC.                                                                                                                                                                     |
| A policy on P0's role in the account                   | `lambda:InvokeFunction` on the connector's function, and nothing else.                                                                                                                                                                                   |
| {% endtab %}                                           |                                                                                                                                                                                                                                                          |

{% tab title="Google Cloud Run" %}

| Resource                            | What it's for                                                                                                                                                                    |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Project services                    | Turns on the IAM, Cloud Run and Secret Manager APIs in the connector's project.                                                                                                  |
| A service account for the connector | `roles/secretmanager.secretAccessor` and `roles/secretmanager.viewer` on the private key's secret only, not on its project.                                                      |
| The Cloud Run service               | Runs the connector, and accepts calls only from P0's service account. It calls `api.github.com` over the internet, so it needs no VPC connector.                                 |
| Bindings for P0's service account   | `roles/run.invoker`, to invoke the connector, and `roles/run.viewer`, to look up the service's URL each time P0 validates the install. Both are on the connector's service only. |
| {% endtab %}                        |                                                                                                                                                                                  |
| {% endtabs %}                       |                                                                                                                                                                                  |

{% hint style="warning" %}
**Keep the connector's read access to that one secret.** P0 names the secret in each call to the connector, so any other secret the connector can read, whether through a wildcard or through a grant on the whole account or project, is one a call could point it at.
{% endhint %}

## Install with Terraform

Instead of the installer, you can install GitHub with the [`p0_github`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/github) resource of the P0 Terraform provider (see [Manage integrations as code with the P0 Terraform provider](/getting-started/get-started-with-the-p0-terraform-provider.md)). You still create the GitHub App, store its private key and install the app on your organization:

1. Open `https://github.com/organizations/<org>/settings/apps/new` and create the app with the settings in [#step-2-create-the-github-app](#step-2-create-the-github-app "mention").
2. Store its private key in the secret that the resource names, as in [#step-3-save-the-private-key](#step-3-save-the-private-key "mention").
3. Install the app on your organization, as in [#step-4-install-the-app-on-your-organization](#step-4-install-the-app-on-your-organization "mention").

The resource takes the place of the installer's other steps. Copy the connector's Terraform from the resource's example in the [provider's docs](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/github), or, for Google Cloud, from [`gcp.tf`](https://github.com/p0-security/terraform-provider-p0/blob/main/examples/resources/p0_github/gcp.tf) beside it, and put the resource in the same configuration. `gcp.tf` reads P0's service account from your `p0_gcp` resource's `service_account_email`. If you manage `p0_gcp` in another configuration, pass its `service_account_email` into this one, such as through a variable, and use that instead. Pin the connector image to a published tag and its digest, as in [#change-the-install-later](#change-the-install-later "mention"). Applying the resource has P0 validate the install through the connector. If a check fails, the apply fails with P0's message.

The `p0_github` resource needs version `0.73.0` or later of the P0 Terraform provider.

For the organization `my-github-org`, the resource looks like this:

{% tabs %}
{% tab title="AWS Lambda" %}

```terraform
resource "p0_github" "my_github_org" {
  org    = "my-github-org"
  app_id = "123456"

  vault = {
    type           = "aws-sm"
    account_id     = local.p0_github_my_github_org.secrets_account_id
    secrets_region = local.p0_github_my_github_org.secrets_region
  }
  private_key_secret_name = local.p0_github_my_github_org.private_key_secret_name

  hosting = {
    type             = "aws"
    account_id       = local.p0_github_my_github_org.account_id
    connector_name   = aws_lambda_function.p0_github_my_github_org.function_name
    connector_region = local.p0_github_my_github_org.connector_region
  }

  depends_on = [
    aws_iam_role_policy.p0_github_my_github_org_invoke,
    aws_iam_role_policy.p0_github_my_github_org_private_key,
  ]
}
```

{% endtab %}

{% tab title="Google Cloud Run" %}

```terraform
resource "p0_github" "my_github_org" {
  org    = "my-github-org"
  app_id = "123456"

  vault = {
    type       = "gcp-sm"
    project_id = local.p0_github_my_github_org.secrets_project_id
  }
  private_key_secret_name = local.p0_github_my_github_org.private_key_secret_id

  hosting = {
    type             = "gcp"
    project_id       = google_cloud_run_v2_service.p0_github_my_github_org.project
    connector_name   = google_cloud_run_v2_service.p0_github_my_github_org.name
    connector_region = google_cloud_run_v2_service.p0_github_my_github_org.location
  }

  depends_on = [
    google_cloud_run_v2_service_iam_member.p0_github_my_github_org_invoker,
    google_cloud_run_v2_service_iam_member.p0_github_my_github_org_viewer,
    google_secret_manager_secret_iam_member.p0_github_my_github_org_accessor,
    google_secret_manager_secret_iam_member.p0_github_my_github_org_viewer,
  ]
}
```

You don't set the service's URL. P0 looks it up, and `hosting.connector_service_uri` shows it.
{% endtab %}
{% endtabs %}

The `depends_on` list holds back the validation until P0 can invoke the connector and the connector can read the key. IAM changes can take a few seconds to take effect, so if the first validation is denied access, apply again.

If you already added the organization in the P0 app, the apply fails with `The GitHub organization <org> is already added`. Import it into the resource instead:

```bash
terraform import p0_github.my_github_org my-github-org
```

Changing `app_id` updates the installation in place, and P0 validates it again. Changing `org`, `vault`, `hosting` or `private_key_secret_name` replaces the installation, so change them only when no one holds a role or a team membership through P0 in the organization. Check for active grants on the **Activity** tab of [Access Management](/access-management/access-management.md). Otherwise, a removal that comes due while the organization isn't installed fails, and needs **Retry revoke** once it's installed again.

## Force-merge access

{% hint style="info" %}
Force-merge access is in beta. Functionality and configuration may change.
{% endhint %}

Force-merge access lets a person merge pull requests into one protected branch without the branch's required reviews, for a bounded time. P0 adds them to the people allowed to bypass the branch's required pull request reviews, and takes them off that list when access ends. If they were already on the list before the grant, P0 leaves them on it.

It uses the GitHub App's **Administration** (read and write) permission, which the installer's app settings include, so there's no new permission to accept. If you left that permission out, see [#leave-out-what-you-dont-need](#leave-out-what-you-dont-need "mention").

The branch needs:

* **A classic branch protection rule for its exact name**, such as `main`. P0 refuses a branch whose rule is a pattern, such as `release/*`, because a change to that rule would cover every branch the pattern matches. To offer force-merge on one of those branches, add a rule for its exact name. GitHub applies a rule for an exact name before a pattern rule.
* **Required reviews in that rule.** Turn on **Require a pull request before merging**, with at least one required approval.

A grant doesn't cover:

* **Rulesets**, on the repository or the organization. A ruleset that requires reviews still blocks the merge.
* **The rule's other settings**, such as required status checks and signed commits. The grant skips required reviews only, so a failing check still blocks the merge.
* **People outside the organization.** P0 grants force-merge access only to members, as for the other accesses.

To request it, see [Requesting GitHub access](/integrations/resource-integrations/github/requesting-access.md#requesting-force-merge-access).

### Force-merge errors

Requestors and approvers see these messages on a force-merge request.

| Message                                                                                                                                                                                                                                                                                                                                              | What to do                                                                                                                                                                                                                                                               |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `<repo>'s <branch> branch isn't protected, or doesn't exist. Force-merge access needs a branch protection rule for exactly <branch> that requires pull request reviews.`                                                                                                                                                                             | Add a classic branch protection rule for the branch's exact name, with required reviews, or request a branch that has one.                                                                                                                                               |
| `<repo>'s <branch> branch doesn't require pull request reviews, so there are none to bypass. Turn on "Require a pull request before merging" in the branch's protection rule.`                                                                                                                                                                       | There are no reviews to skip. Turn on required reviews in the branch's rule, or merge without requesting access.                                                                                                                                                         |
| `<repo>'s <branch> branch is protected by a wildcard rule, which other branches can share, so P0 won't change who can bypass it. Ask a repo admin to add a branch protection rule for exactly <branch>.`                                                                                                                                             | Add a classic branch protection rule for the branch's exact name, with required reviews.                                                                                                                                                                                 |
| `GitHub refused to change who can bypass reviews on <repo>'s <branch> branch (<GitHub's message>). Check that <login> has write access to <repo>, and that the bypass list has room: GitHub allows 100 users, teams and apps in all.`                                                                                                                | Follow GitHub's message. Give the person write access to the repository, or make room on the branch's bypass list.                                                                                                                                                       |
| `<login> isn't a member of <org>, so P0 can't let them bypass reviews on <repo>'s <branch> branch. Add them to the org first.`                                                                                                                                                                                                                       | An owner of the organization adds them, then they request again.                                                                                                                                                                                                         |
| `GitHub didn't keep P0's change to who can bypass reviews on <repo>'s <branch> branch, so <login> may not be able to force-merge there. Something else may be changing the branch's protection rule at the same time. Retry, or check the rule's bypass list in <repo>'s branch settings.` When access ends, it says `may still be able to` instead. | Something else changed the branch's protection rule while P0 was changing it. Check the rule's bypass list in GitHub. Then request again, or click **Retry revoke** if access was ending.                                                                                |
| `<repo> has no <branch> branch any more, but its protection rule for <branch> is still there, so P0 couldn't take <login> off the rule's bypass list. Remove them from the rule in <repo>'s branch settings.`                                                                                                                                        | The branch was deleted while the person had access. If the rule isn't needed any more, delete it in the repository's branch settings, then click **Retry revoke**. To keep the rule, remove the person from its bypass list instead. The request then stays **Errored**. |

## Access policies

In the [Configuring access policies](/policy-studio/policy-studio/configure-your-first-access-policy.md#resource) section of an access policy, GitHub is the service `github`. Its access types are `role`, for a role on a repository, `team`, for a membership of a team, and `force-merge`, for force-merge access to a branch. See [#force-merge-access](#force-merge-access "mention").

### Available filters

A rule can filter on these properties of a request, each with the key `name`:

| Filter         | Access                   | What it matches                                                                                                                                                                                    | Example                  |
| -------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `organization` | All                      | The organization's login, as you entered it in P0                                                                                                                                                  | `my-github-org`          |
| `repository`   | `role` and `force-merge` | The repository, as `<org>/<repo>`, with the repository's name as GitHub spells it                                                                                                                  | `my-github-org/api`      |
| `role`         | `role`                   | A built-in role's name in access policies, listed in [Requesting GitHub access](/integrations/resource-integrations/github/requesting-access.md#roles), or a custom role's name as GitHub shows it | `push`                   |
| `team`         | `team`                   | The team, as `<org>/<team>`, with the team's slug                                                                                                                                                  | `my-github-org/sre`      |
| `team-role`    | `team`                   | The team role, `member` or `maintainer`                                                                                                                                                            | `maintainer`             |
| `branch`       | `force-merge`            | The branch, as `<org>/<repo>/<branch>`                                                                                                                                                             | `my-github-org/api/main` |

P0 records a built-in role by its name in access policies, whichever name the requestor used, so a rule for Write has to match `push`, not `write`. Patterns are unanchored, case-sensitive regular expressions. Use `^` and `$` to match a whole name.

Each access type has only the filters marked for it above. A rule that keeps only some values of a filter doesn't match a request whose access type doesn't have that filter. For example, a rule that keeps only some teams doesn't match a `role` or `force-merge` request.

### Example access policy

These policies send requests for Read, Triage or Write on the `api` and `web` repositories, requests to maintain any team, and requests to force-merge into `main` on any of the organization's repositories to your Security Reviewers. They deny Admin on every repository:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: github
    accessType: role
    filters:
      repository:
        effect: keep
        key: name
        pattern: ^my-github-org/(api|web)$
      role:
        effect: keep
        key: name
        pattern: ^(pull|triage|push)$
  approval:
    - type: p0
- requestor:
    type: any
  resource:
    type: integration
    service: github
    accessType: role
    filters:
      role:
        effect: keep
        key: name
        pattern: ^admin$
  approval:
    - type: deny
- requestor:
    type: any
  resource:
    type: integration
    service: github
    accessType: team
    filters:
      team-role:
        effect: keep
        key: name
        pattern: ^maintainer$
  approval:
    - type: p0
- requestor:
    type: any
  resource:
    type: integration
    service: github
    accessType: force-merge
    filters:
      branch:
        effect: keep
        key: name
        pattern: ^my-github-org/[^/]+/main$
  approval:
    - type: p0
```

## Error messages

P0 shows these messages when you add an organization, when it validates the install, and on requests, where requestors and approvers see them. Once you've fixed the cause, retry what failed.

### When you add the organization

P0 checks the form when you click **Next** in [#step-1-add-the-organization](#step-1-add-the-organization "mention"), and doesn't save the organization if a check fails. The message shows at the top of the form, under **Error configuring GitHub**. Fix the field it names, then click **Next** again.

| Message                                                                                                                                                                                                                      | What to do                                                                                                                                                                                                                                                                             |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `The GitHub organization <org> is already added. To change its GitHub App ID, open <org> from the list of organizations. To change anything else, remove <org> and add it again.`                                            | Open the organization from the list instead. To change anything other than its GitHub App ID, see [#change-the-install-later](#change-the-install-later "mention").                                                                                                                    |
| `The secret manager provider and the connector hosting need to be in the same cloud. Pick AWS Secrets Manager with AWS Lambda, or Google Secret Manager with Google Cloud Run.`                                              | Pick the same cloud under **Secret manager provider** and **Connector hosting**.                                                                                                                                                                                                       |
| `Enter the name of the secret that holds, or will hold, the GitHub App's private key.`                                                                                                                                       | Fill in **Private key secret**.                                                                                                                                                                                                                                                        |
| `That doesn't look like a secret name. Enter the name of the secret that holds the key, not its ARN or the key itself.`                                                                                                      | Enter the secret's name, not its ARN or, on Google Cloud, its resource name `projects/<project>/secrets/<name>`. Never paste the key itself into the form. This message can show with either cloud selected, because the form checks the name before it knows where the key is stored. |
| `That doesn't look like a secret name. Enter the name of the secret that holds the key, not its resource name or the key itself.`                                                                                            | On Google Cloud, enter the secret's name, not its resource name `projects/<project>/secrets/<name>`. Use letters, digits, hyphens and underscores only. Never paste the key itself into the form.                                                                                      |
| `Secrets Manager can't find a secret by its name when the name ends in a hyphen and six characters, like -AbCdEf. Use a secret whose name doesn't end that way.`                                                             | Choose a secret name that doesn't end in a hyphen and six letters or digits.                                                                                                                                                                                                           |
| `Enter the organization's GitHub login, as in github.com/<login>`                                                                                                                                                            | Enter the login from your organization's URL on GitHub.                                                                                                                                                                                                                                |
| `Use 2 to 64 characters for the connector name: lowercase letters, digits and single hyphens, starting with a letter and ending with a letter or digit.`                                                                     | Choose a **Connector name** that follows the rule.                                                                                                                                                                                                                                     |
| `Use 2 to 49 characters for the connector name: lowercase letters, digits and single hyphens, starting with a letter and ending with a letter or digit.`                                                                     | On Google Cloud Run, choose a **Connector name** that follows the rule.                                                                                                                                                                                                                |
| `Enter the AWS region the connector runs in, such as us-west-2.`, or a message like it for another AWS account or region field                                                                                               | Enter that field as the message says.                                                                                                                                                                                                                                                  |
| `Enter the ID of the Google Cloud project the connector runs in.`, `Enter the ID of the Google Cloud project the private key's secret is in.` or `Enter the Google Cloud region the connector runs in, such as us-central1.` | Enter that project ID or region as the message says.                                                                                                                                                                                                                                   |
| `The GitHub integration doesn't support AWS GovCloud, China, ISO or European Sovereign Cloud regions. Pick a commercial AWS region, such as us-west-2.`                                                                      | Use a commercial AWS region for the secret and the connector.                                                                                                                                                                                                                          |

### When P0 validates the install

P0 validates the install when you click **Finish** on the organization's configure page in [#step-6-validate](#step-6-validate "mention"), and when you click **Update** after a change to the install, as in [#change-the-install-later](#change-the-install-later "mention"). A failed check shows at the top of the page, under **Error configuring GitHub**. Fix the cause, then click **Finish** or **Update** again.

| Message                                                                                                                                                                                                                          | What to do                                                                                                                                                                                                                                                                                                                             |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Enter the GitHub App ID. It's on the app's settings page, under About.`                                                                                                                                                         | Fill in **GitHub App ID**.                                                                                                                                                                                                                                                                                                             |
| `The GitHub App ID is a number. Find it on the app's settings page.`                                                                                                                                                             | Enter the App ID, not the app's name or its Client ID.                                                                                                                                                                                                                                                                                 |
| `The connector isn't allowed to read the GitHub App private key secret <secret>. Grant the connector's identity secretsmanager:GetSecretValue on that secret, and kms:Decrypt on its KMS key if it uses a customer-managed key.` | Deploy the connector again from the setup page, as in [#step-5-deploy-the-connector](#step-5-deploy-the-connector "mention"). If a customer-managed KMS key encrypts the secret, set its ARN too, and for a secret in another account, set its policies as in [#step-1-add-the-organization](#step-1-add-the-organization "mention").  |
| `The connector isn't allowed to read the GitHub App private key secret <secret>. Grant the connector's identity secretmanager.versions.access and secretmanager.versions.get on that secret.`                                    | Check that the secret still exists, as in [#step-3-save-the-private-key](#step-3-save-the-private-key "mention"). Google Cloud reports a deleted secret as one the connector isn't allowed to read. Then deploy the connector again from the setup page, as in [#step-5-deploy-the-connector](#step-5-deploy-the-connector "mention"). |
| `The connector couldn't find the GitHub App private key secret <secret>. Check the private key secret in the P0 install, and that the secret still exists.`                                                                      | Store the key under the secret's name and in the place that the setup page shows, as in [#step-3-save-the-private-key](#step-3-save-the-private-key "mention"). If you named the wrong secret in step 1, remove the organization and add it again.                                                                                     |
| `GitHub rejected the credentials for GitHub App <app-id>. Check that the app ID and the private key in your secret manager belong to the same app, and that the key hasn't been revoked.`                                        | Check that **GitHub App ID** is the App ID on the app's settings page, and that the secret holds one of the app's current private keys.                                                                                                                                                                                                |
| `The private key for GitHub App <app-id> isn't a valid PEM private key. Store the .pem file GitHub generated for the app.`                                                                                                       | Store the contents of the downloaded `.pem` file in the secret as a new version, as in [#step-3-save-the-private-key](#step-3-save-the-private-key "mention").                                                                                                                                                                         |
| A message that starts `Error in service` and ends `(account=<account-id> region=<region>)`, such as an access-denied error from AWS Lambda                                                                                       | P0 couldn't invoke the connector. Deploy the connector from the setup page, as in [#step-5-deploy-the-connector](#step-5-deploy-the-connector "mention"), and check that **Connector name** and **Connector region** match the function it deployed. Right after you apply it, wait a few seconds and retry.                           |
| `P0 invoked the Lambda function <arn>, but it didn't answer as the GitHub connector. Check that the function runs the connector's image, and check its logs, then retry.`                                                        | Check that the function at `<arn>` is the one you deployed in [#step-5-deploy-the-connector](#step-5-deploy-the-connector "mention"), and deploy it again. If it is, check the function's logs.                                                                                                                                        |
| `P0 can't find the Cloud Run service <name> in project <project>. If the connector is still deploying, wait for it to finish, then retry.`                                                                                       | Deploy the connector as in [#step-5-deploy-the-connector](#step-5-deploy-the-connector "mention"), and check that **GCP project ID**, **Connector name** and **Connector region** match the service it deployed. Right after you deploy it, wait a few seconds and retry.                                                              |
| `The Cloud Run service <name> in project <project> has no URL. Check that the connector deployed, then retry.`                                                                                                                   | Deploy the connector again from the setup page, as in [#step-5-deploy-the-connector](#step-5-deploy-the-connector "mention"), and check the service's logs in Cloud Run.                                                                                                                                                               |
| `P0 can't read the Cloud Run service <name> in project <project>. Check that P0's service account has roles/run.viewer on the service, then retry. A new grant can take a minute to take effect.`                                | Wait a minute and retry. If it still fails, deploy the connector again from the setup page, which gives P0's service account `roles/run.viewer` on the service. Also check that **GCP project ID**, **Connector name** and **Connector region** match the service you deployed.                                                        |
| `The connector at <url> refused P0's call. Check that its INVOKER_SA_EMAIL is P0's service account <email>, then retry.`                                                                                                         | Deploy the connector again from the setup page, which sets `INVOKER_SA_EMAIL` to P0's service account. If you deploy with Terraform, check that `p0_service_account_email` is that account.                                                                                                                                            |
| `P0 couldn't get an answer from the connector at <url>. Check that P0's service account <email> has roles/run.invoker on that Cloud Run service, and that the service is running (see its Cloud Run logs), then retry.`          | Deploy the connector again from the setup page, and check the service's logs in Cloud Run.                                                                                                                                                                                                                                             |
| `The connector at <url> answered with an error. Check its Cloud Run logs and that it runs the current connector image, then retry.`                                                                                              | Check the service's logs in Cloud Run, and deploy the connector again from the setup page.                                                                                                                                                                                                                                             |
| `GitHub App <app-id> isn't installed on <org>. An owner of <org> needs to install it, then retry.`                                                                                                                               | Install the app as in [#step-4-install-the-app-on-your-organization](#step-4-install-the-app-on-your-organization "mention"). If it's installed, check that the organization you added in P0 is the one you installed it on.                                                                                                           |
| `GitHub App <app-id>'s installation on <org> is suspended. An owner of <org> needs to unsuspend it, then retry.`                                                                                                                 | An owner of the organization unsuspends the installation, in the organization's GitHub App settings.                                                                                                                                                                                                                                   |

### On requests

Requestors and approvers see these messages on a request. If the connector can't read or can't find the private key's secret, a request shows `P0 couldn't read the GitHub App's private key. Ask your P0 admin to check the GitHub install.` To see which problem it is, validate the install again: open the organization's page in P0 and click **Update**. A revoke that failed has a **Retry revoke** button on the request's page. For a role or a team membership that P0 couldn't remove when access ended, see [Requesting GitHub access](/integrations/resource-integrations/github/requesting-access.md#if-removing-access-fails).

| Message                                                                                                                                                                                                                                   | What to do                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GitHub rejected the GitHub App credentials for <org>. Check that the app ID and the private key in the connector's secret manager belong to the same app, that the key hasn't been revoked, and that the connector's clock is accurate.` | Check that **GitHub App ID** is the App ID on the app's settings page, and that the secret holds one of the app's current private keys.                                                                                                                                                                                                                                                                                                                                                                                                         |
| `The GitHub App isn't installed on <org>. Install it on the org, or check the org name in the P0 install, then retry.`                                                                                                                    | Install the app as in [#step-4-install-the-app-on-your-organization](#step-4-install-the-app-on-your-organization "mention"). If it's installed, check that the organization you added in P0 is the one you installed it on.                                                                                                                                                                                                                                                                                                                    |
| `The GitHub App's installation on <org> is suspended. Ask an owner of <org> to unsuspend it in the org's GitHub App settings, then retry.`                                                                                                | An owner of the organization unsuspends the installation.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `The GitHub App for <org> is missing <permission>. Grant it in the app's settings and accept the update on the installation.`, or the same message with `a permission this call needs` in place of a permission's name                    | In the app's settings, give it the permission at the level in [#step-2-create-the-github-app](#step-2-create-the-github-app "mention"). An owner of the organization then accepts the new permissions on the installation. The message names a permission as GitHub's API does, such as `organization_custom_roles=read` for Custom repository roles (read-only). If you left the permission out on purpose, the request is for an access you chose not to offer. See [#leave-out-what-you-dont-need](#leave-out-what-you-dont-need "mention"). |
| `<login> isn't a member of <org>, so P0 can't give them a role on <repo>. GitHub sent <login> an invitation to <repo>. Cancel it in the repo's collaborator settings, then add <login> to <org> and request again.`                       | P0 refused the grant and left the invitation GitHub sent. Cancel the invitation in the repository's collaborator settings, so they can't accept it. Add them to the organization, then they request access again.                                                                                                                                                                                                                                                                                                                               |
| `<login> isn't a member of <org>, so P0 can't add them to <team>. GitHub left their membership of <team> pending instead, and P0 couldn't remove it. Remove <login> from <team> in the team's settings.`                                  | P0 refused the grant. Remove them from the team in the team's settings. They need to join the organization before they request access again.                                                                                                                                                                                                                                                                                                                                                                                                    |
| `GitHub refused to change who is on <team> (<GitHub's message>). If the team is synced with an identity provider group, change its members in the identity provider.`                                                                     | GitHub doesn't let anyone change the members of a team that's synced with an identity provider group. Change the team's members in the identity provider instead, or request a team that isn't synced. If the team isn't synced, GitHub's message says what's wrong.                                                                                                                                                                                                                                                                            |
| `The team with id <team-id> doesn't exist in <org> any more, so P0 couldn't add <login> to it. Request access to a team that does.`                                                                                                       | The team was deleted after the request was made. Request a team that exists.                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |

## Change the install later

* **Use another GitHub App.** Install the new app on the organization, as in [#step-4-install-the-app-on-your-organization](#step-4-install-the-app-on-your-organization "mention"), and store its private key in the same secret as a new version, as in [#step-3-save-the-private-key](#step-3-save-the-private-key "mention"). Then, on the organization's page in P0, change **GitHub App ID**, and click **Update**. Until you click **Update**, P0 can't change roles or team memberships in the organization. P0 validates the install again, as in [#step-6-validate](#step-6-validate "mention"). The GitHub App ID is the only setting you can change in place.
* **Rotate the private key.** Generate a new private key on the app's settings page, and store it in the same secret as a new version, such as with `aws secretsmanager put-secret-value` or `gcloud secrets versions add`. The connector uses the new version from its next call, so you can then delete the old key in GitHub. Nothing changes in P0 or in the connector's Terraform.
* **Update the connector.** To move to a new connector image, change the image's pin and deploy the connector again: run the script again, or apply the Terraform again. You can also copy the current script from the organization's page in P0, under **Show the connector's shell script**. P0 publishes the connector's tags on Docker Hub.
  * On AWS, the pin is `IMAGE_TAG` and `IMAGE_DIGEST` in the script, and `image_tag` and `image_digest` in the Terraform. To find a tag's digest, run `docker buildx imagetools inspect p0security/p0-connector-github:<tag>` and copy the `Digest` it prints. If the apply stops with `The connector image's tag doesn't resolve to the digest pinned here`, the tag on Docker Hub and the digest you pinned are different images, and Terraform changes nothing.
  * On Google Cloud Run, the pin is `IMAGE` in the script and `image` in the Terraform's `locals` block. Each is the full image reference, `docker.io/p0security/p0-connector-github-gcloud@sha256:<digest>`. To find a tag's digest, run `docker buildx imagetools inspect p0security/p0-connector-github-gcloud:<tag>`.
* **Use a different secret, or move the connector.** Where the private key is stored, the secret's name and where the connector runs are fixed once you add an organization. To change any of them, remove the organization from the integration, then add it again and follow [#install-github](#install-github "mention") with the new values. Do this only when no one holds a role or a team membership through P0 in the organization. Check for active grants on the **Activity** tab of [Access Management](/access-management/access-management.md). Otherwise, a removal that comes due while the organization isn't installed fails, and needs **Retry revoke** once it's installed again.

## Limitations

* P0 grants roles and team memberships only to members of the organization the GitHub App is installed on, and never invites anyone to it. Outside collaborators can't be granted a role.
* Requestors can ask for roles only on repositories in the GitHub App's repository selection.
* Custom repository roles need GitHub Enterprise Cloud.
* A role request changes direct collaborator roles only. Team membership and the organization's base permission stay as they are.
* A team request changes the person's own membership of the team only. Membership of a child team isn't counted or changed.
* P0 can't change the members of a team that's synced with an identity provider group.
* When a `maintainer` grant ends, P0 puts back the person's own membership only. Members they added to the team, maintainers they promoted and teams they nested under it stay.
* P0 can't take back a role from an account that GitHub hides, such as an account flagged as spam. The account can keep its role until an admin removes it by hand. See [Requesting GitHub access](/integrations/resource-integrations/github/requesting-access.md#github-hides-the-persons-account).
* The connector runs on AWS Lambda or Google Cloud Run, in the same cloud as the private key: AWS Secrets Manager or Google Secret Manager.
* Each organization needs its own GitHub App and its own connector.
* On AWS, the connector and the private key's secret must be in commercial regions, not in GovCloud, China, ISO or European Sovereign Cloud regions.
