Skip to content
SkiMasque

Getting started: test your first CI connection

All guides

Guide

Create a GitHub Actions job that reaches http://hello.skimasque.com:8080/ through gateway.skimasque.com:443, then prove that an unlisted destination is denied. The hello service uses private DNS and is reachable from the managed gateway; your runner does not need to resolve or reach it directly.

1. Prepare your GitHub repository

Use a repository you can commit to and run Actions in. This guide uses YOUR_OWNER/YOUR_REPO, the branch main, and the workflow file hello-skimasque.yml. Substitute your real owner and repository everywhere; replace main in both the policy and workflow if your default branch differs. Enable GitHub Actions in the repository's Settings → Actions → General and allow skimasque-dev/connect if your organisation restricts third-party Actions. Use the GitHub-hosted ubuntu-24.04 runner for this walkthrough.

No SkiMasque token or GitHub personal access token goes in repository secrets. The job's id-token: write permission lets the Action request GitHub OIDC.

2. Create an organisation and verify the GitHub owner

Open the SkiMasque console, sign in with GitHub, and create or select your SkiMasque organisation. Under identity settings, verify the GitHub user or organisation that owns YOUR_OWNER/YOUR_REPO, following the verification flow or installing the GitHub App. Confirm the owner is verified in the selected SkiMasque organisation. Signing in alone does not associate all your repositories with an organisation.

Use the shared managed gateway for this test; no gateway VM, private DNS setup on the runner, or local CLI installation is required. The managed deployment must have its hello test endpoint enabled. In Gateways, copy the shared gateway's audience for the selected organisation. It has the form https://gateway.skimasque.com/o/YOUR_ORG_SLUG; replace YOUR_ORG_SLUG in the workflow with your SkiMasque organisation slug. This slug can differ from the GitHub owner. The bare gateway URL is not a tenant audience.

3. Publish the test policy

Open Policies in that organisation and open the policy editor. Under Add a document, enter hello-ci.toml, leave the target field empty for the shared gateway, and paste:

name = "hello-ci"

[match]
repository = "YOUR_OWNER/YOUR_REPO"
workflow = "hello-skimasque.yml"
branch = "main"
kind = "ci"

[[rules]]
application = "skimasque-test"
transport = "tcp"
action = "allow"
destinations = ["hello.skimasque.com:8080"]

[[tests]]
application = "skimasque-test"
destination = "hello.skimasque.com:8080"
expect = "allow"

[[tests]]
application = "skimasque-test"
destination = "hello.skimasque.com:8081"
expect = "deny"

Replace YOUR_OWNER/YOUR_REPO, click Save draft, and check that validation and both policy tests pass. Click Publish revision. Saving a draft alone does not change gateway authorization. Under Gateways, confirm the shared gateway is healthy and has acknowledged the published policy revision before running the job.

New organisations may already have a skimasque-test-OWNER.toml starter policy allowing skimasque-test to reach the hello service from that verified owner's repositories. The policy above gives this walkthrough an explicit repository, workflow and branch scope. If you want only that scope, remove the broader starter document from the draft before publishing. Preserve unrelated policies; ensure none allows hello.skimasque.com:8081, which is the denial test below.

4. Create the complete workflow

This repository includes the hello workflow and its hello CI policy, scoped to skimasque-dev/skimasque on main, with audience https://gateway.skimasque.com/o/skimasque-dev. Repository CI validates the policy; publish it in the console before running the workflow on main. The repository job runs automatically on every push to main and also supports manual dispatch.

Create .github/workflows/hello-skimasque.yml in your repository and paste. Replace YOUR_ORG_SLUG with the audience slug copied in step 2:

name: Hello through SkiMasque

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  hello:
    runs-on: ubuntu-24.04
    timeout-minutes: 5
    permissions:
      contents: read
      id-token: write
    steps:
      - name: Connect to the managed gateway
        id: network
        uses: skimasque-dev/connect@v2
        with:
          mode: proxy
          proxy: gateway.skimasque.com:443
          audience: https://gateway.skimasque.com/o/YOUR_ORG_SLUG
          application: skimasque-test
          version: v0.3.3

      - name: Reach the allowed hello service
        shell: bash
        env:
          SKIMASQUE_HTTP_PROXY: ${{ steps.network.outputs.http-proxy }}
        run: |
          curl --fail-with-body --show-error --silent \
            --connect-timeout 10 --max-time 30 \
            --proxy "$SKIMASQUE_HTTP_PROXY" --noproxy '' \
            http://hello.skimasque.com:8080/ | tee hello-response.txt
          grep -Fx 'private endpoint ok' hello-response.txt

      - name: Verify an unlisted port is denied
        shell: bash
        env:
          SKIMASQUE_HTTP_PROXY: ${{ steps.network.outputs.http-proxy }}
        run: |
          status=$(curl --show-error --silent \
            --connect-timeout 10 --max-time 30 \
            --proxy "$SKIMASQUE_HTTP_PROXY" --noproxy '' \
            --output denied-response.txt --write-out '%{http_code}' \
            http://hello.skimasque.com:8081/)
          test "$status" = 403 || {
            echo "Expected gateway policy denial (403), got $status"
            cat denied-response.txt
            exit 1
          }
          echo 'Gateway refused the unlisted port (403).'

Commit the file to your default branch (main here). A checkout step is not needed because the job only uses the Action and curl. mode: proxy is explicit: the Action defaults to transparent mode, which needs routes and DNS inputs. The application name must match the policy; it is declared context, not proof of the executable. The explicit proxy output and --noproxy '' ensure both requests use the gateway even if the runner has inherited proxy exclusions. The gateway resolves the hello hostname.

This example pins client v0.3.3, which provides the capabilities required by connect@v2, and preserves HTTP 403 for gateway policy denials. Client v0.3.2 supports startup but incorrectly maps those denials to 502. The older v0.3.1 client does not implement capabilities and cannot run this Action. Action and client versions are independent: @v2 without an explicit version downloads the latest client release. Pin the Action to a reviewed commit as well for reproducible production workflows.

5. Run the job and check its output

In GitHub, open Actions → Hello through SkiMasque → Run workflow, select main (or the branch you placed in the policy), and click Run workflow. Every push to main also starts the job automatically. Open the run and expand the hello job's steps.

  • Connect to the managed gateway must finish successfully: the Action exchanges GitHub OIDC and starts authenticated local proxy listeners.
  • Reach the allowed hello service must print private endpoint ok, followed by host:, seen client: and path: /. The client address is the gateway's internal address. The body assertion and curl's HTTP error check must pass.
  • Verify an unlisted port is denied must print Gateway refused the unlisted port (403). A timeout, DNS failure, connection refusal or 502 is a failed test, not evidence of policy denial.

The job should be green, including the Action's post-job cleanup. HTTP port 8080 is intentional; changing the hello URL to HTTPS or omitting the port tests a different destination. The runner-to-gateway connection uses encrypted QUIC; the demo service speaks HTTP on the gateway's internal network.

6. Verify the gateway's audit decisions

Open Audit in the same organisation. Find the entries around the workflow run time and inspect the repository, workflow hello-skimasque.yml, branch/ref main / refs/heads/main, workload kind ci, and application skimasque-test. Confirm an allow for hello.skimasque.com:8080 and a deny for hello.skimasque.com:8081, with the policy reason. Audit ingestion can take a short time; refresh if necessary.

Together, the expected response, explicit proxy path, 403 assertion and audit decisions verify connectivity and enforcement. Curling the service directly does not test gateway policy.

7. Troubleshoot a failed run

SymptomWhat to check
Workflow missing or no Run workflow buttonCommit to the default branch, keep workflow_dispatch, and enable Actions.
OIDC or credential exchange failsKeep job-level id-token: write, the organisation-specific audience copied from Gateways, and a verified repository owner in the selected organisation.
Action reports unrecognized subcommand capabilitiesThe downloaded client is too old for the Action. Set version: v0.3.3 or a newer compatible release.
Credential exchange returns unknown_orgCopy the selected organisation's audience from Gateways: https://gateway.skimasque.com/o/YOUR_ORG_SLUG. The bare gateway URL cannot identify your organisation.
Action cannot connect or times outCheck managed gateway health and UDP 443 reachability; QUIC requires UDP. Inspect the Action's startup diagnostics.
Hello request returns 403Check the published revision and gateway acknowledgement, actual repository/branch/workflow filename, kind = "ci", application and port. Read the audit denial reason.
Hello request returns 502Check gateway-side DNS/reachability and that the managed hello endpoint is enabled. Do not add public runner DNS overrides for this private service.
Denial test returns something other than 403Check audit and other matching policies for a broader allow. A network failure does not prove authorization was denied.
Policy changes have no effectPublish the saved draft, check the selected organisation and gateway revision, then rerun the job to create new tunnels.

8. Clean up and adapt the job

The Action's post hook stops its client and restores previous proxy variables on normal job success or failure. Remove hello-ci.toml (and any unused starter test document) from the console draft and publish the removal when done. Delete the workflow file if you no longer need the test.

To reach your own service, replace the destination and port in both policy and workflow, set the application context consistently, and use a gateway that can reach that service. The shared gateway does not automatically reach your VPC. See GitHub Actions for proxy and transparent modes and deployment modes for customer gateways.

Run a local command

After publishing a policy for your developer identity:

skimasque exec --app curl -- curl --fail https://api.staging.example.com/health
skimasque exec --forward 15432:db.internal:5432 -- psql -h 127.0.0.1 -p 15432

The second command requires its own matching database rule and a gateway that can reach the database. Local listeners close when the command exits. For a coding agent, use exec --agent with --sandbox srt or an explicit --unsandboxed choice; read agent sessions before granting access.

Next

  • GitHub Actions: modes, prerequisites, identity and cleanup.
  • Policies: workload kinds, baselines and enforcement limits.
  • CLI: exec, tunnels, agent sessions and management commands.
  • Gateways: deploy inside your network.
  • Self-hosting: operate a licensed or compatible control plane.
  • Troubleshooting: diagnose routing and policy decisions.