You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fail fast when OIDC credentials cannot be obtained (#31)
The `Configure AWS credentials` step took 82 minutes to fail on a
cyber-dojo/dashboard run. Each of the twelve default retry attempts sat
on a TCP connect to the STS endpoint that took ~6m45s to time out at the
OS level. `connect ETIMEDOUT` on port 443 means no socket was ever
opened, so nothing was going to succeed — but the runner was held and
billed for 82 minutes, and the caller's `terraform-<name>` concurrency
group was held with it, queueing every subsequent apply for that
environment behind a job that was already doomed.
Bound the step so it dies quickly instead:
action-timeout-s: 45 bounds the action as a whole, retries included
retry-max-attempts: 3 the default of 12 only helps if attempts are fast
timeout-minutes: 2 backstop, independent of the action's behaviour
45 seconds is justified by measurement: across recent successful runs in
kosli-dev and cyber-dojo the step took 0-2 seconds, worst case 6, so the
timeout sits far above the real p99 and will not fail a slow-but-healthy
authentication. `disable-retry` is deliberately not used — STS
throttling is genuinely transient and a couple of quick retries is worth
having now that their total cost is bounded.
Every job also gets a `timeout-minutes` rather than silently inheriting
GitHub's 360-minute default, which an 82-minute hang would never have
tripped. For the plan/apply job the ceiling follows from
`aws_role_duration` rather than from how long Terraform might run: the
credentials are static environment variables that are never refreshed,
so 20 minutes (the 1200s default) after the credentials step every AWS
call fails with ExpiredToken and the work cannot usefully continue.
Setup before that step measures 1-35s, so a job consuming its whole
credential lifetime lands around 22-23 minutes — hence a default of 30,
which is headroom over the real ceiling rather than an arbitrary number.
Housekeeping jobs get 5-10.
Because that ceiling is coupled to `aws_role_duration`, it is exposed as
a `job_timeout_minutes` input on plan.yml, apply.yml and
detect-drift.yml rather than hard-coded. A repository with a
legitimately long apply raises both together, without needing a change
here. Raising only the session duration would let the job be cancelled
part-way through an apply, which can leave the state lock held.
Beyond base.yml and apply.yml named in the ticket, detect-drift.yml has
two `Configure AWS credentials` steps with the same failure mode, so
they are covered here too. The values are recorded in the README as the
standard for other repositories to adopt.
Copy file name to clipboardExpand all lines: .github/workflows/apply.yml
+9Lines changed: 9 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -58,6 +58,10 @@ on:
58
58
description: "When true, pass `--commit <ref>` to the Kosli commands via the COMMIT_ARG environment variable."
59
59
default: false
60
60
type: boolean
61
+
job_timeout_minutes:
62
+
description: "Minutes before GitHub cancels the apply job. Raise it together with aws_role_duration when a legitimate apply needs longer; see the README on timeouts."
63
+
default: 30
64
+
type: number
61
65
secrets:
62
66
kosli_api_token:
63
67
description: "Kosli API token. Required when kosli_template_file is set."
Copy file name to clipboardExpand all lines: .github/workflows/base.yml
+8Lines changed: 8 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -21,6 +21,10 @@ on:
21
21
tf_apply:
22
22
default: false
23
23
type: boolean
24
+
job_timeout_minutes:
25
+
description: "Minutes before GitHub cancels the plan/apply job. The default follows aws_role_duration: the OIDC credentials are static environment variables that are never refreshed, so AWS calls start failing with ExpiredToken 1200s (20 min) after the credentials step and a longer ceiling would buy nothing. Raise both together if a legitimate apply needs longer."
Copy file name to clipboardExpand all lines: .github/workflows/detect-drift.yml
+14Lines changed: 14 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -25,6 +25,10 @@ on:
25
25
description: "Extra environment variables, one KEY=VALUE per line, exported before the drift plan. To set Terraform variable `foo`, use `TF_VAR_foo=...`. Single-line values only. Note: for per-build values (e.g. an image tag) give the variable a default in variables.tf instead, otherwise drift detection reports false drift."
26
26
default: ""
27
27
type: string
28
+
job_timeout_minutes:
29
+
description: "Minutes before GitHub cancels the drift plan job. Raise it together with aws_role_duration when a legitimate plan needs longer; see the README on timeouts."
Copy file name to clipboardExpand all lines: .github/workflows/plan.yml
+5Lines changed: 5 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -46,6 +46,10 @@ on:
46
46
kosli_cli_version:
47
47
default: "latest"
48
48
type: string
49
+
job_timeout_minutes:
50
+
description: "Minutes before GitHub cancels the plan job. Raise it together with aws_role_duration when a legitimate plan needs longer; see the README on timeouts."
51
+
default: 30
52
+
type: number
49
53
secrets:
50
54
kosli_api_token:
51
55
description: "Kosli API token. Required when kosli_template_file is set."
| `tf_version` | no | `1.14.6` | Terraform version to install |
102
102
| `tf_vars` | no | `""` | Extra env vars (one `KEY=VALUE` per line) exported before plan/apply; see [Supplying Terraform variables](#supplying-terraform-variables) |
103
+
| `job_timeout_minutes` | no | `30` | Minutes before GitHub cancels the plan/apply job; see [Timeouts](#timeouts) |
103
104
104
105
Plus, for opting into Kosli attestation (see [Kosli attestation](#kosli-attestation) below):
105
106
@@ -169,6 +170,60 @@ image tag, which changes every run.
169
170
| `kosli_api_token` | if `kosli_template_file` is set | Kosli API token for the attest steps. |
170
171
| `kosli_github_token` | no (only `apply.yml`) | GitHub token used by `kosli attest pr github` to look up pull requests. When omitted, the pull-request attestation step is skipped. Typically passed as `${{ secrets.GITHUB_TOKEN }}` — in which case the **calling job must also declare `pull-requests: read`** in its `permissions:` block (see example below), otherwise the attestation step will fail with `Resource not accessible by integration`. |
171
172
173
+
### Timeouts
174
+
175
+
Every job carries a `timeout-minutes` rather than inheriting GitHub's 360-minute default, and the
176
+
OIDC credential step is bounded so that an unreachable STS endpoint fails in under a minute instead
177
+
of holding a runner — and the environment's `concurrency` group — for over an hour:
These are the standard values for **any** Kosli workflow using
190
+
`aws-actions/configure-aws-credentials`, not just the ones here. The reasoning:
191
+
192
+
| Setting | Value | Why |
193
+
|---|---|---|
194
+
| `action-timeout-s` | `45` | Bounds the action as a whole, retries included. Across recent successful runs in `kosli-dev` and `cyber-dojo` the step took 0–2s, worst case 6s, so 45s is far above the real p99 and will not fail a slow-but-healthy authentication. |
195
+
| `retry-max-attempts` | `3` | The default is 12. STS throttling is genuinely transient and worth retrying, but 12 attempts is only useful if each attempt is fast — which is exactly what fails to hold when the endpoint is unreachable. |
196
+
| `timeout-minutes` (step) | `2` | A backstop that holds regardless of how the action behaves or what a future version changes. |
197
+
| `timeout-minutes` (job) | `30` plan/apply, `5`–`10` housekeeping | Bounded by `aws_role_duration`, not by how long Terraform might take — see below. |
198
+
199
+
`disable-retry` is deliberately **not** used: a couple of quick retries is worth having, and
200
+
`action-timeout-s`already bounds the total cost of them.
201
+
202
+
The plan/apply job's default of 30 minutes is **derived from `aws_role_duration`**, which defaults
203
+
to `1200` (20 minutes). The credentials the OIDC step exports are static environment variables and
204
+
are never refreshed, so 20 minutes after that step every AWS call starts failing with
205
+
`ExpiredToken`— a longer job ceiling would buy nothing, because the work cannot usefully continue.
206
+
Measured against real runs, setup before the credentials step takes 1–35s, so a job that consumes
207
+
its entire credential lifetime lands around 22–23 minutes; 30 leaves headroom without being
208
+
arbitrary.
209
+
210
+
The two values are coupled, so **raise `job_timeout_minutes` and `aws_role_duration` together** when
211
+
a repository has a legitimately long apply. Raising only the session duration lets the job be
212
+
cancelled part-way through an `apply`, which can leave the state lock held — a worse outcome than a
213
+
slow run. Raising only the job ceiling buys time in which every AWS call fails:
214
+
215
+
```yaml
216
+
with:
217
+
aws_role_duration: "3600" # 60 min session
218
+
job_timeout_minutes: 70 # 60 + setup + headroom
219
+
```
220
+
221
+
The role's own maximum session duration is the hard limit on `aws_role_duration`; if a longer
222
+
session is refused, that maximum needs raising on the IAM role first.
223
+
224
+
A job that fails in 45 seconds can be re-run for nothing. A job that hangs for 82 minutes blocks
0 commit comments