# Using Octelium in GitHub Actions

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/guide/service/devops/github-action>.

Your *User*, especially `WORKLOAD` *User* used by non-human entities such as GitHub Action workflows, can access HTTP-based *Services* (e.g. HTTP/gRPC APIs, Kubernetes clusters) through the clientless BeyondCorp mode via OAuth2 client credentials (read more [here](https://octelium.com/docs/octelium/latest/management/core/credential.md#oauth2-client-credentials)) and directly issued access tokens used in bearer authentication (read more [here](https://octelium.com/docs/octelium/latest/management/core/credential.md#access-tokens)). However, in many cases, you might want to access non-HTTP *Services*, such as databases, from within your GitHub Action workflows. Octelium enables you to connect to your *Cluster* and access any arbitrary *Service* from within your workflows via the official `octelium/github-action` GitHub Action.

By default, the Action authenticates to your *Cluster* in a "secretless" way using GitHub's own OIDC issued identity token assertions (read more [here](https://octelium.com/docs/octelium/latest/management/core/identity-providers.md#oidc-assertion)), which means that no secret at all needs to be stored in your repository. To do so, we first need to create an `oidcIdentityToken` *IdentityProvider* as follows:

```yaml
kind: IdentityProvider
metadata:
  name: github-actions
spec:
  oidcIdentityToken:
    issuerURL: https://token.actions.githubusercontent.com
    audience: https://example.com
```

Now your *User* can set an identifier for the `github-actions` *IdentityProvider* as follows (read more about *User* identities [here](https://octelium.com/docs/octelium/latest/management/core/user.md#authentication)):

```yaml
kind: User
metadata:
  name: github-action-workflows
spec:
  type: WORKLOAD
  authentication:
    identities:
    - identityProvider: github-actions
      identifier: repo:<ORG_NAME>/<REPO_NAME>:ref:refs/heads/<BRANCH_NAME>
  authorization:
    policies: ["policy-1", "policy-2"]
```

Your GitHub Action needs to add the permission `id-token: write` to generate an OIDC identity token. Your workflow should now look as follows:

```yaml
name: integration-tests
on: push

permissions:
  id-token: write

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Connect to Octelium
        uses: octelium/github-action@v2
        with:
          domain: example.com
          audience: https://example.com

      - name: Access an Octelium Service
        run: curl http://demo-nginx

      - name: Disconnect
        if: always()
        uses: octelium/github-action@v2
        with:
          domain: example.com
          command: logout
```

Note that the `audience` input has to match the `audience` field of the `github-actions` *IdentityProvider*, and that the Action does not return until the tunnel is actually up, which means that the steps that come after it can immediately access the *Cluster* *Services*.

> **Note:**
>
> You do not need to identify which *IdentityProvider* the Action authenticates against, even if your *Cluster* has more than one, since the *Cluster* itself infers the *IdentityProvider* that matches the assertion.

The Action deliberately rejects input combinations that would otherwise silently do nothing. In other words, the `audience` input cannot be combined with the `auth-token` input since an authentication token does not use an assertion at all, and it also cannot be combined with an explicit `assertion` input whose value already carries its own options (e.g. `github-actions:audience=https://example.com`).

## Authentication Tokens

You can also authenticate using an authentication token [*Credential*](https://octelium.com/docs/octelium/latest/reference/resources.md#credential) (read more about issuing authentication tokens [here](https://octelium.com/docs/octelium/latest/management/core/credential.md#authentication-tokens)) which is typically stored as a GitHub repository secret. Here is an example:

```yaml
jobs:
  example:
    runs-on: ubuntu-latest
    steps:
      - uses: octelium/github-action@v2
        with:
          domain: example.com
          auth-token: ${{ secrets.OCTELIUM_AUTH_TOKEN }}
```

The authentication token is only passed to the `octelium` client via an environment variable and it is never passed as a command-line argument. Note, however, that GitHub does not provide repository secrets to workflows that are triggered by a `pull_request` event from forked repositories, which is one more reason to prefer the secretless assertion-based authentication described above.

## Scopes

You can restrict what the *Session* created by the workflow is actually able to access via the `scopes` input, which works in a similar spirit to OAuth2 scopes (read more [here](https://octelium.com/docs/octelium/latest/user/cli/connect.md#scopes)). Here is an example that limits the workflow to the `postgres` *Service* of the `db` *Namespace* as well as every *Service* belonging to the `ci` *Namespace*:

```yaml
      - uses: octelium/github-action@v2
        with:
          domain: example.com
          scopes: |
            service:postgres.db
            service:ci/*
```

## Publishing Services

Once connected, your workflow can access any *Service* assigned to the *User* by its hostname (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/overview.md#dns)). In some cases, however, your tooling can only speak to `localhost`. For such cases, you can map a *Service* to a port of the GitHub Action runner via the `publish` input (read more [here](https://octelium.com/docs/octelium/latest/user/cli/connect.md#mapping-services-to-host)). Here is an example:

```yaml
      - uses: octelium/github-action@v2
        with:
          domain: example.com
          publish: |
            postgres.db:5432
            redis.ns1:6379
```

Your workflow can now access the `postgres.db` and `redis.ns1` *Services* at `localhost:5432` and `localhost:6379` respectively.

## Serving Services

A GitHub Action runner can also remotely serve *Services* back to the *Cluster* via the `serve` input (read more about remotely serving *Services* [here](https://octelium.com/docs/octelium/latest/management/core/service/overview.md#remotely-via-a-connected-user)), which can be useful, for example, to expose a preview environment that is built inside the workflow. Here is an example:

```yaml
      - uses: octelium/github-action@v2
        with:
          domain: example.com
          serve: |
            preview-app
```

## Pinning the Client Version

By default the Action installs the latest `octelium` client release. You can pin a specific version for reproducible workflows as follows:

```yaml
      - uses: octelium/github-action@v2
        with:
          domain: example.com
          version: "0.42.0"
```

The Action downloads the release archive directly from the Octelium GitHub releases, verifies it against the release `SHA256SUMS` and refuses to install it upon a checksum mismatch.

You can also install the other Octelium binaries, which is useful for workflows that manage the *Cluster* itself via `octeliumctl` (read more [here](https://octelium.com/docs/octelium/latest/management/core/overview.md)), as follows:

```yaml
      - uses: octelium/github-action@v2
        with:
          command: install
          components: |
            octelium
            octeliumctl
```

## Additional Arguments

Most of the commonly used `octelium connect` flags already have their own dedicated inputs which you should always prefer. For anything else, the `extra-args` input takes one argument per line in the `--flag=value` form as follows:

```yaml
      - uses: octelium/github-action@v2
        with:
          domain: example.com
          extra-args: |
            --no-color
```

Note that the `extra-args` input is meant to be a forward-compatibility escape hatch and it is therefore not able to override the options that the Action itself manages. In other words, the `--detach`, `--domain`, `--auth-token`, `--assertion` and `--homedir` flags are rejected, since detaching the client would leave behind a tunnel that the Action can no longer manage and the authentication flags would place a *Credential* in the command-line arguments of the process.

## Disconnecting

The connection remains available for the rest of the job. You can explicitly tear it down and log the *Session* out via the `logout` command instead of leaving the *Session* to expire on its own (read more about *Sessions* [here](https://octelium.com/docs/octelium/latest/management/core/user.md#session)) as follows:

```yaml
      - name: Disconnect
        if: always()
        uses: octelium/github-action@v2
        with:
          domain: example.com
          command: logout
```

Note that the `if: always()` condition makes sure that the step still runs even if an earlier step of the job has failed, and that the `logout` step has to run in the same job as the step that connected to the *Cluster*. The step stops the client, waits for it to shut down gracefully and then revokes the *Session*. If the connection itself never comes up, then the Action automatically terminates the client before failing the step, which means that a runner is never left with a tunnel that is no longer managed.

> **Note:**
>
> The Action's inputs were reorganized in `v2`. If you are upgrading from `v1`, then the `args` input, whose value used to be split on whitespace, is now the newline-separated `extra-args` input, and most of its common use cases now have dedicated inputs such as `serve`, `publish` and `scopes`. The `wait` input, which simply slept for a number of seconds, is now the `timeout` input which sets how long the Action waits for the tunnel to actually come up. You can read about every supported input of the Action [here](https://github.com/octelium/github-action).
