Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/e2e_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ jobs:
provider: lxd
test-tox-env: integration-juju3.6
modules: '["test_e2e"]'
# INTEGRATION_TOKEN, OS_PASSWORD are passed through INTEGRATION_TEST_SECRET_ENV_VALUE_<N>
# mapping. See CONTRIBUTING.md for more details.
# INTEGRATION_TOKEN, OS_PASSWORD, GITHUB_APP_INSTALLATION_ID,
# GITHUB_APP_PRIVATE_KEY are passed through
# INTEGRATION_TEST_SECRET_ENV_VALUE_<N> mapping. See CONTRIBUTING.md for more details.
extra-arguments: |
-m=openstack \
--log-format="%(asctime)s %(levelname)s %(message)s" \
Expand All @@ -32,7 +33,8 @@ jobs:
--openstack-http-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_HTTP_PROXY }}" \
--openstack-no-proxy="${{ vars.INTEGRATION_TEST_OPENSTACK_NO_PROXY }}" \
--openstack-flavor-name="${{ vars.INTEGRATION_TEST_OPENSTACK_FLAVOR_NAME }}" \
--dockerhub-mirror="${{ vars.INTEGRATION_TEST_DOCKERHUB_MIRROR }}"
--dockerhub-mirror="${{ vars.INTEGRATION_TEST_DOCKERHUB_MIRROR }}" \
--github-app-client-id="${{ vars.INTEGRATION_TEST_GITHUB_APP_CLIENT_ID }}"
Comment thread
cbartz marked this conversation as resolved.
self-hosted-runner: true
self-hosted-runner-label: pfe-ci

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/integration_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main
secrets: inherit
with:
charmcraft-channel: latest/stable
juju-channel: 3.6/stable
pre-run-script: tests/integration/setup-integration-tests.sh
provider: lxd
Expand Down Expand Up @@ -73,6 +74,7 @@ jobs:
matrix:
base: ["22.04", "24.04"]
with:
charmcraft-channel: latest/stable
juju-channel: 3.6/stable
provider: lxd
test-tox-env: integration-juju3.6
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/test_github_runner_manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ jobs:
- test_debug_ssh
- test_metrics
- test_planner_runner
- test_github_app_auth
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
Expand Down Expand Up @@ -51,10 +50,6 @@ jobs:
${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME_8 }}: ${{ secrets.INTEGRATION_TEST_SECRET_ENV_VALUE_8 }}
${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME_9 }}: ${{ secrets.INTEGRATION_TEST_SECRET_ENV_VALUE_9 }}
${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME_10 }}: ${{ secrets.INTEGRATION_TEST_SECRET_ENV_VALUE_10 }}
# GITHUB_APP_CLIENT_ID, GITHUB_APP_INSTALLATION_ID, GITHUB_APP_PRIVATE_KEY
${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME_11 }}: ${{ secrets.INTEGRATION_TEST_SECRET_ENV_VALUE_11 }}
${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME_12 }}: ${{ secrets.INTEGRATION_TEST_SECRET_ENV_VALUE_12 }}
${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME_13 }}: ${{ secrets.INTEGRATION_TEST_SECRET_ENV_VALUE_13 }}
run: |
tox -e integration -- -v --tb=native -s \
tests/integration/${{ matrix.test-module }}.py \
Expand Down
1 change: 1 addition & 0 deletions .vale/styles/config/vocabularies/local/accept.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
aproxy
Aproxy
GitHub App
backoff
denylist
enqueued
Expand Down
9 changes: 5 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ that can be used for linting and formatting code when you're preparing contribut
To prefer explicit setting arguments passing, use `extra-arguments` of the
[following reusable workflow](https://github.com/canonical/operator-workflows/blob/main/.github/workflows/integration_test_run.yaml)
to pass in non-sensitive values.
For sensitive values (`INTEGRATION_TOKEN`: `--token`, `OS_PASSWORD`: `--openstack-password`), map
them through `INTEGRATION_TEST_SECRET_ENV_NAME_<N>`
environment variable settings under [repository](https://github.com/canonical/github-runner-operator)
> settings > variables > actions.
For sensitive values (`INTEGRATION_TOKEN`, `OS_PASSWORD`, `GITHUB_APP_INSTALLATION_ID`,
`GITHUB_APP_PRIVATE_KEY`, and so on), map them through `INTEGRATION_TEST_SECRET_ENV_NAME_<N>` /
`INTEGRATION_TEST_SECRET_ENV_VALUE_<N>` environment variable settings under
[repository](https://github.com/canonical/github-runner-operator) > settings > variables > actions.
This is to prevent GitHub from leaking secrets when passing them over the CLI calls (unresolved).
See the workflow files for the current slot assignments.

### Building the charm

Expand Down
33 changes: 24 additions & 9 deletions charmcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ description: |
A [Juju](https://juju.is/) [charm](https://juju.is/docs/olm/charmed-operators) managing
[self-hosted runners for GitHub Actions](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners).
Each unit of this charm will start a configurable number of virtual machines to host
self-hosted runners. Each runner performs only one job, after which it unregisters from GitHub to
ensure that each job runs in a clean environment. The charm will periodically check the number of
runners and spawn or destroy them as necessary to maintain the configured number of runners. Both
self-hosted runners. Each runner performs only one job, after which it unregisters from GitHub to
ensure that each job runs in a clean environment. The charm will periodically check the number of
runners and spawn or destroy them as necessary to maintain the configured number of runners. Both
the reconciliation interval and the number of runners to maintain are configurable.

links:
Expand Down Expand Up @@ -77,8 +77,8 @@ config:
This is useful when the charm is deployed in a network that requires a proxy to access the
internet.
Note that you should carefully choose values for the 'aproxy-exclude-addresses' and
'aproxy-redirect-ports' so that the network traffic from the runner to the HTTP proxy is not
captured by aproxy. The simplest way to achieve this is to add the IP address of the HTTP proxy
'aproxy-redirect-ports' so that the network traffic from the runner to the HTTP proxy is not
captured by aproxy. The simplest way to achieve this is to add the IP address of the HTTP proxy
to 'aproxy-exclude-addresses' or exclude the HTTP proxy port from 'aproxy-redirect-ports'.
aproxy-exclude-addresses:
type: string
Expand All @@ -102,8 +102,8 @@ config:
default: ""
description: >-
Additional comma separated labels to attach to self-hosted runners. By default, the labels
"self-hosted", architecture (i.e. "x64", "arm64"), os (i.e. "linux"), os-flavor (i.e.
"jammy") are set. Any labels provided via this configuration will be appended to the default
"self-hosted", architecture (i.e. "x64", "arm64"), os (i.e. "linux"), os-flavor (i.e.
"jammy") are set. Any labels provided via this configuration will be appended to the default
values.
path:
type: string
Expand Down Expand Up @@ -141,6 +141,21 @@ config:
'repo' scope for repository runners and 'repo' + 'admin:org' scope for organization runners.
For fine grained token scopes, see
https://charmhub.io/github-runner/docs/how-to-change-token.
github-app-client-id:
type: string
description: >-
GitHub App Client ID used instead of `token` for GitHub API authentication.
This is the Client ID shown on the GitHub App settings page (e.g. "Iv23liXXXXXX").
The legacy numeric App ID is also accepted.
github-app-installation-id:
type: int
description: >-
GitHub App installation ID used instead of `token` for GitHub API authentication.
github-app-private-key-secret-id:
type: string
description: >-
Juju secret ID containing the PEM-encoded GitHub App private key under the `private-key`
field. Use this together with `github-app-client-id` and `github-app-installation-id`.
virtual-machines:
type: int
default: 0
Expand Down Expand Up @@ -179,8 +194,8 @@ config:
pre-job-script:
type: string
description: >-
Optional script (needs shebang) to execute in the pre-job phase of a spawned runner VM.
This can e.g. be useful for specific infrastructure related configurations
Optional script (needs shebang) to execute in the pre-job phase of a spawned runner VM.
This can e.g. be useful for specific infrastructure related configurations
(e.g. usage of certain proxies or custom routes).
Note that the user executing the script is the ubuntu user (which has sudo rights).
Example script:
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This changelog documents user-relevant changes to the GitHub runner charm.

## 2026-04-07

- Add GitHub App authentication support using Juju secrets for the private key, alongside the existing PAT-based authentication.
Comment thread
cbartz marked this conversation as resolved.

## 2026-04-03

- Fixed charm not entering blocked/waiting status when the image integration has no image available. Previously, some event handlers (`upgrade-charm`, `planner-relation-changed`, `planner-relation-broken`) would start the runner manager service without checking image readiness, causing the service to error with "No runner combinations configured."
Expand Down
11 changes: 7 additions & 4 deletions docs/explanation/security.md
Comment thread
cbartz marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,19 @@ When `allow-external-contributor` is set to `false`, external contributors can s

This approach ensures that all code from external contributors is reviewed by trusted users before execution on self-hosted runners.

## Permission for GitHub app or personal access token
## Permissions for GitHub App or personal access token

The charm interacts with GitHub via RESTful API. This requires a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).
The charm interacts with GitHub using the RESTful API. This requires either a [GitHub App](https://docs.github.com/en/apps) or a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).

It is generally recommended to grant the minimal permissions necessary for security reasons. Use a [fine-grained token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) to control the scope of permission. See [token scopes](https://charmhub.io/github-runner/docs/reference-token-scopes) for more information.
GitHub App authentication is preferred as it provides fine-grained permissions and does not tie access to a personal user account. The App's private key is stored securely in a Juju secret.

When using a personal access token, use a [fine-grained token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) to control the scope of permission. See [authentication and token scopes](https://charmhub.io/github-runner/docs/reference-token-scopes) for more information.

### Good practices

- Use a fine-grained personal access token.
- Prefer GitHub App authentication over personal access tokens.
- Give the minimal permission required.
- When using a personal access token, use a fine-grained token.

## OpenStack project management

Expand Down
40 changes: 36 additions & 4 deletions docs/how-to/change-token.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
# How to change GitHub personal access token
# How to change GitHub authentication

This charm supports changing the [GitHub personal access token (PAT)](https://github.com/settings/tokens) used.
The charm supports two authentication methods: a GitHub App or a personal access token (PAT).
See [Authentication and token scopes](https://charmhub.io/github-runner/docs/reference-token-scopes) for required permissions.

## Changing the token
## Authenticate using a GitHub App

Create a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) with the required permissions and install it on the target organization or repository.

Store the App's PEM-encoded private key in a Juju secret:

```shell
juju add-secret github-app-key private-key="$(cat /path/to/private-key.pem)"
```

Note the secret ID from the output (e.g. `secret:abc123def`), then grant it to the charm and configure the App credentials.

- `<APP_NAME>`: the Juju application name (e.g. `github-runner`)
- `<CLIENT_ID>`: the Client ID shown on the App's settings page (Settings > Developer settings > GitHub Apps)
- `<INSTALLATION_ID>`: the numeric ID in the URL when viewing the App installation on the organization or user account (e.g. `https://github.com/organizations/<ORG>/settings/installations/<INSTALLATION_ID>`)
- `<SECRET_ID>`: the Juju secret ID from the previous step

```shell
juju grant-secret github-app-key <APP_NAME>
juju config <APP_NAME> \
github-app-client-id=<CLIENT_ID> \
github-app-installation-id=<INSTALLATION_ID> \
github-app-private-key-secret-id=<SECRET_ID>
Comment thread
cbartz marked this conversation as resolved.
```

To rotate the private key, update the Juju secret:

```shell
juju update-secret github-app-key private-key="$(cat /path/to/new-private-key.pem)"
```

## Authenticate using a personal access token

Create a new [GitHub Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).

Expand All @@ -16,4 +48,4 @@ By using [`juju config`](https://juju.is/docs/juju/juju-config) to change the [c

```shell
juju config <APP_NAME> token=<TOKEN>
```
```
4 changes: 2 additions & 2 deletions docs/reference/charm-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ would also be directed to the HTTP(s) proxy, unlike when using aproxy.
## GitHub API usage
<!-- vale Canonical.007-Headings-sentence-case = YES -->

The charm requires a GitHub personal access token for the [`token` configuration](https://charmhub.io/github-runner/configure#token). This token is used for:
The charm requires GitHub API credentials — either a [GitHub App](https://charmhub.io/github-runner/docs/reference-token-scopes) or a [personal access token](https://charmhub.io/github-runner/configure#token). These credentials are used for:

- Requesting self-hosted runner registration tokens
- Requesting a list of runner applications
- Requesting a list of self-hosted runners configured in an organization or repository
- Deleting self-hosted runners

Note that the GitHub API uses a [rate-limiting mechanism](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28). When this is reached, the charm may not be able to perform the necessary operations and may go into
BlockedStatus. The charm will automatically recover from this state once the rate limit is reset, but using a different token with a higher rate limit may be a better solution depending on your deployment requirements.
BlockedStatus. The charm will automatically recover from this state once the rate limit is reset.

<!-- vale Canonical.007-Headings-sentence-case = NO -->
## External contributor access control
Expand Down
70 changes: 53 additions & 17 deletions docs/reference/token-scopes.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,55 @@
# Token scopes
# Authentication and token scopes

In order to use the GitHub runner charm, a personal access token with the necessary permissions
is required.
The GitHub runner charm supports two authentication methods for interacting with the GitHub API:
a [GitHub App](https://docs.github.com/en/apps) or a
[personal access token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).

## Fine grained access token scopes
## GitHub App authentication

GitHub App authentication is the recommended approach. It provides fine-grained permissions
and does not tie access to a personal user account.

To configure the charm with a GitHub App, set the following charm configuration options:

- `github-app-client-id`: The App's Client ID (shown on the GitHub App settings page).
- `github-app-installation-id`: The installation ID for the target organization or repository.
- `github-app-private-key-secret-id`: A Juju secret ID containing the App's PEM-encoded private
key under the `private-key` field.

### Required GitHub App permissions

#### Organizational runners

Organization permissions:

- Self-hosted runners: read & write

Repository permissions:

- Actions: read (required if COS integration is enabled and private repositories exist)
- Administration: read

#### Repository runners

Repository permissions:

- Actions: read (required if COS integration is enabled and the repository is private)
- Administration: read & write
- Metadata: read

## Personal access token authentication

### Fine grained access token scopes

**Note**: In addition to having a token with the necessary permissions, the user who owns the
token also must have admin access to the organisation or repository.
token also must have admin access to the organization or repository.

### Organizational runners
#### Organizational runners

The following are the permissions scopes required for the GitHub runners when registering as an
organisational runner.
organizational runner.

Organisation:
Organization:

- Self-hosted runners: read & write

Expand All @@ -22,29 +58,29 @@ Repository:
- Actions: read (required if COS integration is enabled and private repositories exist)
- Administration: read

### Repository runners
#### Repository runners

The following are the permissions scopes required for the GitHub runners when registering as an
The following are the permissions scopes required for the GitHub runners when registering as a
repository runner.

- Actions: read (required if COS integration is enabled and the repository is private)
- Administration: read & write
- Metadata: read

## Personal access token scopes
### Classic personal access token scopes

Depending on whether the charm is used for GitHub organisations or repositories, the following scopes
should be selected when creating a personal access token.
Depending on whether the charm is used for GitHub organizations or repositories, the following
scopes should be selected when creating a personal access token.

### Organizational runners
#### Organizational runners

To use this charm for GitHub organisations, the following scopes should be selected:
To use this charm for GitHub organizations, the following scopes should be selected:

- `repo`
- `admin:org`

### Repository runners
#### Repository runners

To use this charm for GitHub repositories, the following scopes should be selected:

- `repo`
- `repo`
18 changes: 0 additions & 18 deletions github-runner-manager/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,3 @@ def pytest_addoption(parser):
help="Directory to store debug logs.",
default=os.getenv("DEBUG_LOG_DIR", "/tmp/github-runner-manager-test-logs"),
)
parser.addoption(
"--github-app-client-id",
action="store",
help="GitHub App Client ID for App authentication integration tests.",
default=os.getenv("GITHUB_APP_CLIENT_ID"),
)
parser.addoption(
"--github-app-installation-id",
action="store",
help="GitHub App installation ID for App authentication integration tests.",
default=os.getenv("GITHUB_APP_INSTALLATION_ID"),
)
parser.addoption(
"--github-app-private-key",
action="store",
help="GitHub App PEM-encoded private key for App authentication integration tests.",
default=os.getenv("GITHUB_APP_PRIVATE_KEY"),
)
Loading
Loading