De-slop: remove every em-dash + banned words across all modules + capstone (#94)
Sync course wiki / sync-wiki (push) Successful in 4s
Sync course wiki / sync-wiki (push) Successful in 4s
Co-authored-by: claude <claude@jpaul.io> Co-committed-by: claude <claude@jpaul.io>
This commit was merged in pull request #94.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Module 19 — Runners: The Compute Behind the Automation
|
||||
# Module 19: Runners, the Compute Behind the Automation
|
||||
|
||||
> **Every green check in the last five modules ran on someone else's computer. This module is where
|
||||
> you find out whose, and decide whether it should be yours.** Owning the runner is what turns "I
|
||||
@@ -8,19 +8,19 @@
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Module 8 — Remotes and Hosting.** You push to a forge, and you met the self-host track
|
||||
- **Module 8: Remotes and Hosting.** You push to a forge, and you met the self-host track
|
||||
(Forgejo, Gitea, GitLab CE, and others). Self-hosted runners are the compute half of that same
|
||||
"own your own infrastructure" decision.
|
||||
- **Module 14 — Continuous Integration.** You have a CI workflow that lints and tests `tasks-app`
|
||||
- **Module 14: Continuous Integration.** You have a CI workflow that lints and tests `tasks-app`
|
||||
on every push. Module 14 mentioned, in passing, that the job runs on "a fresh, throwaway Linux
|
||||
machine the forge spins up." This module is the full accounting of that machine.
|
||||
- **Module 18 — Continuous Delivery and Deployment.** The deploy jobs you automated there run on
|
||||
- **Module 18: Continuous Delivery and Deployment.** The deploy jobs you automated there run on
|
||||
the same compute. Once you self-host, deploy steps get direct line-of-sight to your private
|
||||
infrastructure — a feature and a footgun, both covered here.
|
||||
- Helpful but not required: **Module 16 — Containers**, since most runners execute jobs in
|
||||
infrastructure: a feature and a footgun, both covered here.
|
||||
- Helpful but not required: **Module 16: Containers**, since most runners execute jobs in
|
||||
containers and ephemeral runners lean on them.
|
||||
|
||||
You don't need to have read Module 18 in full — if you only have CI from Module 14, everything here
|
||||
You don't need to have read Module 18 in full. If you only have CI from Module 14, everything here
|
||||
still lands. CD just gives you a second, higher-stakes reason to care where jobs run.
|
||||
|
||||
---
|
||||
@@ -29,13 +29,13 @@ still lands. CD just gives you a second, higher-stakes reason to care where jobs
|
||||
|
||||
By the end of this module you can:
|
||||
|
||||
1. Explain what a runner *is* — the actual process and machine that executes your pipeline steps —
|
||||
1. Explain what a runner *is*, the actual process and machine that executes your pipeline steps,
|
||||
and tell, for any job, whether it ran on hosted or self-hosted compute.
|
||||
2. Make a reasoned hosted-vs-self-hosted decision for a given pipeline, on the five axes that
|
||||
actually move the needle: cost, data control, network reach, hardware, and air-gap/compliance.
|
||||
3. Register a self-hosted runner against your forge and run the `tasks-app` CI job on it.
|
||||
4. State, without flinching, the central security tradeoff: a self-hosted runner executes arbitrary
|
||||
code, is non-ephemeral by default, and can be a backdoor into your network — and name the
|
||||
code, is non-ephemeral by default, and can be a backdoor into your network. Name the
|
||||
mitigations that make it survivable.
|
||||
|
||||
---
|
||||
@@ -45,8 +45,8 @@ By the end of this module you can:
|
||||
### A runner is just a computer that does what the YAML says
|
||||
|
||||
A runner is **a process, on some machine, that checks out your code and executes the steps in your
|
||||
pipeline** — nothing more exotic than that. When your Module 14 workflow says "set up
|
||||
Python, install pytest, run the tests," *something physical* has to do that — pull the repo onto a
|
||||
pipeline**, nothing more exotic than that. When your Module 14 workflow says "set up
|
||||
Python, install pytest, run the tests," *something physical* has to do that: pull the repo onto a
|
||||
disk, run `pip install`, run `pytest`, report pass or fail back to the forge. That something is the
|
||||
runner.
|
||||
|
||||
@@ -58,12 +58,12 @@ The loop every runner runs, regardless of forge:
|
||||
4. **Stream logs and the final status** (pass/fail) back to the forge.
|
||||
5. Go to 2.
|
||||
|
||||
That's the whole machine. Everything else — hosted vs. self-hosted, ephemeral vs. persistent,
|
||||
containerized vs. bare metal — is a variation on *which computer runs that loop and who owns it.*
|
||||
That's the whole machine. Everything else (hosted vs. self-hosted, ephemeral vs. persistent,
|
||||
containerized vs. bare metal) is a variation on *which computer runs that loop and who owns it.*
|
||||
|
||||
### Hosted runners: you've been renting
|
||||
|
||||
Up to now, every job ran on a **hosted runner** — a machine the forge owns, spins up on demand, and
|
||||
Up to now, every job ran on a **hosted runner**: a machine the forge owns, spins up on demand, and
|
||||
bills you for. This is the default and, for most work, the right default. What you're actually
|
||||
getting:
|
||||
|
||||
@@ -72,7 +72,7 @@ getting:
|
||||
image and the machine is destroyed afterward. Clean room, every time.
|
||||
- **No ops burden.** You don't patch it, scale it, or keep it online. It exists for the length of
|
||||
your job and then it's gone.
|
||||
- **Metered billing.** You pay in **runner-minutes** — wall-clock time your jobs spend executing,
|
||||
- **Metered billing.** You pay in **runner-minutes**: wall-clock time your jobs spend executing,
|
||||
usually with a free monthly allotment and then per-minute pricing above it. Different machine
|
||||
sizes (more CPU/RAM, GPUs) bill at higher multipliers.
|
||||
|
||||
@@ -81,7 +81,7 @@ clean-room property is pure upside. You will keep using hosted runners for most
|
||||
|
||||
### Self-hosted runners: you own the computer
|
||||
|
||||
A **self-hosted runner** runs that exact same loop — register, poll, execute, report — but on a
|
||||
A **self-hosted runner** runs that exact same loop (register, poll, execute, report) but on a
|
||||
machine *you* own: a spare server, a VM in your own cloud account, a box in your homelab, a beefy
|
||||
workstation under a desk. You install the forge's runner agent, register it with a token, and it
|
||||
starts pulling jobs. To the pipeline author, almost nothing changes; the workflow just targets your
|
||||
@@ -91,13 +91,13 @@ This is the compute analogue of the Module 8 decision. There, you chose between
|
||||
a hosted forge versus self-hosting one. Here, you choose between renting compute to run your
|
||||
pipeline versus owning it. Same instinct, applied one layer down.
|
||||
|
||||
### Why you'd run your own — the five real reasons
|
||||
### Why you'd run your own: the five real reasons
|
||||
|
||||
Don't self-host for the vibe of it. Self-host when one of these actually applies:
|
||||
|
||||
1. **Cost at volume.** Runner-minutes are cheap until they aren't. A heavy pipeline — large test
|
||||
1. **Cost at volume.** Runner-minutes are cheap until they aren't. A heavy pipeline (large test
|
||||
matrices, container builds, long integration suites, or the AI eval/agent jobs from Unit 5 that
|
||||
call models on every run — can run the meter hard. If you already own idle hardware, a self-hosted
|
||||
call models on every run) can run the meter hard. If you already own idle hardware, a self-hosted
|
||||
runner turns "per-minute forever" into "electricity you're already paying for." (Verify the
|
||||
crossover with real numbers; see the checklist at the end.)
|
||||
|
||||
@@ -153,16 +153,16 @@ A **label** is how a workflow picks a runner. A runner advertises labels (`self-
|
||||
GitLab. So moving a job from hosted to your own runner is one line:
|
||||
|
||||
```yaml
|
||||
# before — hosted:
|
||||
# before, hosted:
|
||||
runs-on: ubuntu-latest
|
||||
# after — your runner, selected by label:
|
||||
# after, your runner, selected by label:
|
||||
runs-on: [self-hosted, linux, internal-net]
|
||||
```
|
||||
|
||||
That one line is the whole "I now own this pipeline" switch. Everything else in your Module 14
|
||||
workflow stays identical, because the runner runs the same loop either way.
|
||||
|
||||
### Ephemeral vs. persistent — the property that matters most
|
||||
### Ephemeral vs. persistent: the property that matters most
|
||||
|
||||
A hosted runner is **ephemeral**: fresh machine per job, destroyed after. A self-hosted runner is
|
||||
**persistent by default**: the same machine, with the same disk, runs job after job. That difference
|
||||
@@ -178,7 +178,7 @@ Two things make runners specifically an AI-era topic, not a generic ops footnote
|
||||
|
||||
**1. AI pipelines are compute-hungry, and that changes the cost math.** Unit 5 puts agents *inside*
|
||||
the pipeline: jobs that call a model to review a PR, triage an issue, or attempt a fix on a failing
|
||||
build. Module 25 takes this further — agents running as **triggered or scheduled runner jobs**, kicked
|
||||
build. Module 25 takes this further, into agents running as **triggered or scheduled runner jobs**, kicked
|
||||
off on a cron or by an event rather than a human push. Those jobs run longer and fire more often than
|
||||
a lint-and-test pass, and every one of them consumes runner-minutes. The "rent vs. own compute"
|
||||
decision you're learning here is the one that keeps an AI-heavy pipeline from quietly becoming your
|
||||
@@ -193,7 +193,7 @@ what makes it dangerous when the code it runs isn't yours. Which brings us to th
|
||||
|
||||
**3. AI writes the CI config too.** Ask an agent to "set up CI" and it will happily emit
|
||||
`runs-on: self-hosted` or wire a deploy step, because it's pattern-matching on examples that did. AI
|
||||
also opens PRs (Module 11) — and a pull request, from a human or an agent, is *untrusted code that
|
||||
also opens PRs (Module 11), and a pull request, from a human or an agent, is *untrusted code that
|
||||
your pipeline may execute.* You review the *code* in a PR (Module 10); you also have to review what
|
||||
your pipeline *does with that PR's code* before it runs on hardware that can reach your network. The
|
||||
review reflex from Module 10 has to extend to the workflow files, not just the application code.
|
||||
@@ -203,7 +203,7 @@ review reflex from Module 10 has to extend to the workflow files, not just the a
|
||||
## Hands-on lab
|
||||
|
||||
**Lab language:** shell, plus a one-line edit to the YAML workflow from Module 14. Runs on your own
|
||||
machine and your own forge — no hosted account required for the core of it.
|
||||
machine and your own forge, with no hosted account required for the core of it.
|
||||
|
||||
This lab has two tracks. **Track A** is mandatory and works for everyone: find out exactly where your
|
||||
jobs run today and walk the security tradeoffs concretely. **Track B** is the real thing: register a
|
||||
@@ -215,14 +215,14 @@ a repo also works). If a real runner is too heavy right now, Track A alone satis
|
||||
|
||||
- Your `tasks-app` repo with the Module 14 CI workflow in it.
|
||||
- The two starter files in this module's `lab/` folder:
|
||||
- `whoami-runner.yml` — a tiny workflow that reports *where it ran*.
|
||||
- `inspect-runner.sh` — a script you run on a candidate runner machine to see what an attacker
|
||||
- `whoami-runner.yml`, a tiny workflow that reports *where it ran*.
|
||||
- `inspect-runner.sh`, a script you run on a candidate runner machine to see what an attacker
|
||||
would see if they got code execution on it.
|
||||
- For Track B: a forge you can register a runner against, and a spare machine or VM to be the runner
|
||||
(your laptop is fine for a one-off; don't leave it registered).
|
||||
- Claude Code (sub your own agent).
|
||||
|
||||
### Track A — Find out whose computer you've been using (everyone)
|
||||
### Track A: Find out whose computer you've been using (everyone)
|
||||
|
||||
1. **Make the invisible visible.** Direct Claude Code (sub your own agent) to place
|
||||
`lab/whoami-runner.yml` in the same workflow directory your Module 14 `ci.yml` lives in, then
|
||||
@@ -231,14 +231,14 @@ a repo also works). If a real runner is too heavy right now, Track A alone satis
|
||||
Actions-style forge (`.github/`/`.forgejo/`/`.gitea/` under `workflows/`). **You verify:** the run
|
||||
shows up on the forge. It runs the same lint-and-test as Module 14, then prints the runner's
|
||||
hostname, OS, user, whether it looks ephemeral, and whether it can reach the public internet. The
|
||||
receipt step carries `if: always()` so it still prints even when lint or test fail — a diagnostic
|
||||
receipt step carries `if: always()` so it still prints even when lint or test fail; a diagnostic
|
||||
shouldn't disappear on a red build (the job still reports red). On GitLab CI the same idea is
|
||||
`when: always` on the job.
|
||||
|
||||
2. **Read the receipt.** Open the job logs on your forge and read the `Where did this run?` step.
|
||||
You're now able to answer, for a real job, the question this module opened with: *whose computer
|
||||
was that?* On a hosted runner you'll see a generic cloud hostname and a throwaway user. Note it —
|
||||
you'll compare against your own runner in Track B.
|
||||
was that?* On a hosted runner you'll see a generic cloud hostname and a throwaway user. Note it,
|
||||
because you'll compare against your own runner in Track B.
|
||||
|
||||
3. **See what code execution would expose.** On the machine you'd *consider* using as a self-hosted
|
||||
runner (your laptop is fine for the exercise), run:
|
||||
@@ -247,7 +247,7 @@ a repo also works). If a real runner is too heavy right now, Track A alone satis
|
||||
bash lab/inspect-runner.sh
|
||||
```
|
||||
|
||||
It inventories what a job — *any* job, including one from a pull request — could see if it ran
|
||||
It inventories what a job (*any* job, including one from a pull request) could see if it ran
|
||||
here: environment secrets, cloud credential files, SSH keys, Docker socket access, and which
|
||||
private hosts on your network are reachable. This is not hypothetical. A workflow step is a shell
|
||||
command; whatever the script can see, a malicious workflow step can see too.
|
||||
@@ -256,13 +256,13 @@ a repo also works). If a real runner is too heavy right now, Track A alone satis
|
||||
`inspect-runner.sh` output into the agent and ask: *"If this machine were a self-hosted CI runner
|
||||
and someone opened a pull request with a malicious workflow step, what could they reach or steal?
|
||||
Rank it worst-first."* Read the answer against your real output. This is the honest version of "why
|
||||
you'd run your own" — the network reach that makes a self-hosted runner *useful* is the exact same
|
||||
you'd run your own": the network reach that makes a self-hosted runner *useful* is the exact same
|
||||
reach that makes a compromised one *catastrophic.*
|
||||
|
||||
### Track B — Own the pipeline (if you can attach a runner)
|
||||
### Track B: Own the pipeline (if you can attach a runner)
|
||||
|
||||
5. **Get a registration token.** In your forge's settings, find the Runners / CI/CD section and
|
||||
generate a runner registration token (repo-level is the tightest scope — start there).
|
||||
generate a runner registration token (repo-level is the tightest scope, so start there).
|
||||
|
||||
6. **Register the runner.** Hand this to Claude Code (sub your own agent) on your runner machine:
|
||||
*"Look up the current runner-agent docs for my forge, then download the agent, register it against
|
||||
@@ -271,14 +271,14 @@ a repo also works). If a real runner is too heavy right now, Track A alone satis
|
||||
docs instead of running a half-remembered command. **You verify:** the runner shows as **online**
|
||||
in the forge's Runners list.
|
||||
|
||||
7. **Aim CI at your runner — the one-line switch.** Tell Claude Code (sub your own agent): *"Change
|
||||
7. **Aim CI at your runner, the one-line switch.** Tell Claude Code (sub your own agent): *"Change
|
||||
the `runs-on:` (or `tags:`) line in the `tasks-app` CI workflow to target my `self-hosted` runner
|
||||
instead of the hosted image, then commit and push."* That's the before/after edit from Key
|
||||
concepts. **You verify:** from the job log, the run executed on your own runner.
|
||||
|
||||
8. **Watch your own machine do the work.** Open the job logs. The lint-and-test pass from Module 14
|
||||
now runs on hardware you own. Re-run the `whoami-runner.yml` workflow too and compare its output to
|
||||
step 2: your hostname, your user, and — critically — note that it is **not** a fresh throwaway
|
||||
step 2: your hostname, your user, and, critically, note that it is **not** a fresh throwaway
|
||||
machine. Run it twice and look for leftovers (a `pip` cache, files from the previous run). That
|
||||
persistence is the thing to respect.
|
||||
|
||||
@@ -294,40 +294,40 @@ a repo also works). If a real runner is too heavy right now, Track A alone satis
|
||||
This is the section that earns the module. Self-hosted runners are the single sharpest-edged tool in
|
||||
this course. Be honest about all of it.
|
||||
|
||||
- **A runner executes arbitrary code — that's its entire job.** A "workflow step" is just a shell
|
||||
- **A runner executes arbitrary code; that's its entire job.** A "workflow step" is just a shell
|
||||
command someone put in a file in the repo. The runner runs it, faithfully, with whatever access
|
||||
that machine has. There is no sandbox unless you build one.
|
||||
|
||||
- **Pull requests are untrusted code, and this is the headline risk.** On a public repository, *anyone
|
||||
can fork it, edit the workflow, and open a PR* — and on a misconfigured setup, your self-hosted
|
||||
can fork it, edit the workflow, and open a PR*, and on a misconfigured setup, your self-hosted
|
||||
runner will dutifully execute their workflow on your hardware, inside your network. This is not
|
||||
theoretical: in 2025, real attacks used exactly this path — a malicious fork PR pulled a reverse
|
||||
theoretical: in 2025, real attacks used exactly this path. A malicious fork PR pulled a reverse
|
||||
shell onto a self-hosted runner and used the available token to push malicious code back to the
|
||||
origin repo. The blunt, widely-repeated guidance: **do not attach self-hosted runners to public
|
||||
repositories.** If you must, require manual approval before workflows from forks/first-time
|
||||
contributors run, and never give those jobs your real secrets.
|
||||
|
||||
- **Persistent runners accumulate compromise.** Because the default self-hosted runner is *not*
|
||||
ephemeral, anything a job leaves behind — a cached credential, a background process, a tampered
|
||||
tool on `PATH` — survives into the next job. A single compromised run can become a permanent
|
||||
ephemeral, anything a job leaves behind (a cached credential, a background process, a tampered
|
||||
tool on `PATH`) survives into the next job. A single compromised run can become a permanent
|
||||
implant. The fix is **ephemeral runners**: tear the environment down and rebuild it after every
|
||||
job (typically by running each job in a fresh container or a disposable VM). This is more setup, and
|
||||
it's the price of getting back the clean-room property hosted runners gave you for free.
|
||||
|
||||
- **Network reach cuts both ways.** The reason you self-host — line-of-sight to internal systems — is
|
||||
- **Network reach cuts both ways.** The reason you self-host, line-of-sight to internal systems, is
|
||||
also why a compromised runner is a pivot point into your network. Put runners on an isolated
|
||||
segment with only the egress they actually need, run them as a dedicated low-privilege user (never
|
||||
root, never your own login), and scope their secrets to the minimum. Treat the runner as
|
||||
semi-trusted at best.
|
||||
|
||||
- **"Free" compute isn't free.** You trade per-minute billing for ops work: patching the OS, keeping
|
||||
the agent online and version-matched to the forge (a runner significantly older than the server can
|
||||
the agent online and version-matched to the forge (a runner much older than the server can
|
||||
fail jobs in subtle ways), scaling under load, and securing all of the above. For a busy pipeline
|
||||
on idle hardware that math wins. For an occasional test run, the hosted clean room is cheaper once
|
||||
you count your own time.
|
||||
|
||||
- **Autoscaling is a real project, not a checkbox.** Matching a fleet of runners to bursty demand —
|
||||
spinning ephemeral runners up and down on a queue — is its own piece of infrastructure. Don't
|
||||
- **Autoscaling is a real project, not a checkbox.** Matching a fleet of runners to bursty demand,
|
||||
spinning ephemeral runners up and down on a queue, is its own piece of infrastructure. Don't
|
||||
assume one box; don't assume it's trivial to make it many.
|
||||
|
||||
---
|
||||
@@ -338,17 +338,17 @@ this course. Be honest about all of it.
|
||||
|
||||
- You can look at any pipeline run and state whether it executed on hosted or self-hosted compute,
|
||||
and back it up from the job's own output (you ran `whoami-runner.yml` and read the receipt).
|
||||
- You can give the five reasons to self-host and honestly say which, if any, apply to your situation
|
||||
— instead of self-hosting by default.
|
||||
- You can give the five reasons to self-host and honestly say which, if any, apply to your situation,
|
||||
instead of self-hosting by default.
|
||||
- (Track B) You ran `tasks-app` CI on a runner you own, by changing a single targeting line, and you
|
||||
saw firsthand that it is not a throwaway machine.
|
||||
- You can explain, to a skeptical colleague, the central tradeoff in one breath: a self-hosted runner
|
||||
executes arbitrary code on your hardware with reach into your network, is persistent by default, and
|
||||
must never be casually attached to a public repo — and you can name ephemeral runners, network
|
||||
must never be casually attached to a public repo. You can name ephemeral runners, network
|
||||
isolation, and least-privilege as the mitigations.
|
||||
|
||||
When "where does this run, and what can it touch?" is a question you ask reflexively about every job —
|
||||
and especially every job triggered by a PR or, soon, by an agent — you own the pipeline end to end.
|
||||
When "where does this run, and what can it touch?" is a question you ask reflexively about every job,
|
||||
and especially every job triggered by a PR or, soon, by an agent, you own the pipeline end to end.
|
||||
Module 25 will put autonomous agents on exactly this compute; you now know what they're standing on.
|
||||
|
||||
---
|
||||
@@ -359,17 +359,17 @@ This is an expansion-zone module and the runner ecosystem moves. Re-check at bui
|
||||
|
||||
- [ ] **Runner agent commands and config filenames** for each forge named (the GitHub-style
|
||||
`config`/`run` scripts, `gitlab-runner register`, `act_runner register`/`daemon`). Flags and
|
||||
script names drift between releases — confirm against current official runner docs, don't pin
|
||||
script names drift between releases; confirm against current official runner docs, don't pin
|
||||
from memory.
|
||||
- [ ] **Hosted runner pricing and free-minute allotments**, and the machine-size multipliers, for any
|
||||
forge a reader is likely to use. These change and vary by plan; state them as "check current
|
||||
pricing" rather than a hard number, and re-verify the cost-crossover framing.
|
||||
- [ ] **Fork-PR / untrusted-workflow defaults** — whether the major forges run fork PRs on
|
||||
- [ ] **Fork-PR / untrusted-workflow defaults**: whether the major forges run fork PRs on
|
||||
self-hosted runners by default or require approval, and the exact setting names. The security
|
||||
guidance here depends on current defaults; confirm them.
|
||||
- [ ] **Ephemeral-runner mechanics** — the current supported way to run jobs ephemerally
|
||||
- [ ] **Ephemeral-runner mechanics**: the current supported way to run jobs ephemerally
|
||||
(per-job containers, disposable VMs, the `--ephemeral`-style flags) on each forge.
|
||||
- [ ] **The 2025 attack reference** — keep it accurate and current; if newer, clearer public
|
||||
- [ ] **The 2025 attack reference**: keep it accurate and current; if newer, clearer public
|
||||
incidents exist at publish time, cite the most representative one rather than an aging example.
|
||||
- [ ] **Runner-to-server version-compatibility guidance** — confirm the "keep the agent version
|
||||
- [ ] **Runner-to-server version-compatibility guidance**: confirm the "keep the agent version
|
||||
matched to the forge" caveat still reflects current behavior.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Module 19 lab — what a CI job could see if it ran on THIS machine.
|
||||
# Module 19 lab: what a CI job could see if it ran on THIS machine.
|
||||
#
|
||||
# Run this on any machine you'd consider turning into a self-hosted runner (your laptop is fine for
|
||||
# the exercise). It does NOT change anything — it only LOOKS. The point is to make concrete what is
|
||||
# the exercise). It does NOT change anything; it only LOOKS. The point is to make concrete what is
|
||||
# otherwise abstract: a "workflow step" is just a shell command, so whatever this read-only script
|
||||
# can see, a malicious workflow step (e.g. from a pull request) running on this runner can see too.
|
||||
#
|
||||
@@ -42,7 +42,7 @@ echo "os : $(uname -srm 2>/dev/null)"
|
||||
echo " >> A runner should run as a dedicated low-privilege user, never root, never your login."
|
||||
|
||||
line "SECRETS SITTING IN THE ENVIRONMENT"
|
||||
# Don't print values — just the names. Seeing the NAMES is enough to make the point.
|
||||
# Don't print values, just the names. Seeing the NAMES is enough to make the point.
|
||||
env | grep -iE 'token|secret|key|password|passwd|credential|aws|gcp|azure|api' | cut -d= -f1 | sort -u \
|
||||
| sed 's/^/ exposed env var: /' || true
|
||||
echo " >> Any of these is readable by every job step. Scope runner secrets to the absolute minimum."
|
||||
@@ -76,7 +76,7 @@ else
|
||||
echo " no reachable docker socket"
|
||||
fi
|
||||
|
||||
line "PRIVATE NETWORK REACH (the reason you self-host — and the reason it's dangerous)"
|
||||
line "PRIVATE NETWORK REACH (the reason you self-host, and the reason it's dangerous)"
|
||||
# Probe a few common private ranges' gateways and any hosts you care about.
|
||||
# Edit these to match your network for a sharper result.
|
||||
PROBES=( "192.168.0.1:80" "192.168.1.1:80" "10.0.0.1:80" )
|
||||
@@ -86,7 +86,7 @@ for hp in "${PROBES[@]}"; do
|
||||
echo " REACHABLE: ${host}:${port}"
|
||||
fi
|
||||
done
|
||||
echo " (edit the PROBES list above to test your real internal hosts — databases, deploy targets)"
|
||||
echo " (edit the PROBES list above to test your real internal hosts: databases, deploy targets)"
|
||||
echo " >> Every reachable internal host is something a compromised runner can attack or exfiltrate."
|
||||
|
||||
line "BOTTOM LINE"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Module 19 lab — "Where did this actually run?"
|
||||
# Module 19 lab: "Where did this actually run?"
|
||||
#
|
||||
# This is the Module 14 CI pipeline (lint + test the tasks-app) with one extra step bolted on the
|
||||
# end: it makes the runner tell you who and where it is. Run it once on a hosted runner, then again
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
# Where this file goes: the same workflow directory as your Module 14 ci.yml. On Actions-style forges
|
||||
# (GitHub, and Forgejo/Gitea with Actions-compatible YAML) that's <forge-dir>/workflows/ at the repo
|
||||
# root — e.g. .github/workflows/whoami-runner.yml. The filename is yours; the directory is not.
|
||||
# root, e.g. .github/workflows/whoami-runner.yml. The filename is yours; the directory is not.
|
||||
#
|
||||
# For GitLab CI, the same idea is a one-job .gitlab-ci.yml: run the same script lines under `script:`
|
||||
# with `tags:` selecting your runner. The shape rhymes; only the YAML dialect changes.
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Install tools
|
||||
run: pip install pytest ruff
|
||||
|
||||
# The real Module 14 checks still run — a self-hosted runner has to actually do the work.
|
||||
# The real Module 14 checks still run; a self-hosted runner has to actually do the work.
|
||||
- name: Lint
|
||||
run: ruff check .
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
run: pytest -q
|
||||
|
||||
# The point of THIS workflow: make the runner identify itself.
|
||||
# if: always() so the receipt prints even when Lint/Test fail above — a diagnostic step
|
||||
# if: always() so the receipt prints even when Lint/Test fail above; a diagnostic step
|
||||
# shouldn't vanish on a red build. The job still reports red; only this step is unconditional.
|
||||
# (On GitLab CI the same idea is `when: always` on the job/step.)
|
||||
- name: Where did this run?
|
||||
@@ -69,9 +69,9 @@ jobs:
|
||||
echo
|
||||
echo "=== can this runner reach the public internet? ==="
|
||||
if curl -fsS -m 5 https://example.com >/dev/null 2>&1; then
|
||||
echo "YES — outbound internet works from here."
|
||||
echo "YES: outbound internet works from here."
|
||||
else
|
||||
echo "NO — no outbound internet (could be an air-gapped / isolated runner)."
|
||||
echo "NO: no outbound internet (could be an air-gapped / isolated runner)."
|
||||
fi
|
||||
echo
|
||||
echo "Now ask: is this machine MINE, and what else can it reach? (see inspect-runner.sh)"
|
||||
|
||||
Reference in New Issue
Block a user