Skip to content

fix(server): harden the kubernetes builder pod - #425

Merged
alexey-igrychev merged 13 commits into
mainfrom
fix/server/pr422-review-fixes
Sep 2, 2026
Merged

fix(server): harden the kubernetes builder pod#425
alexey-igrychev merged 13 commits into
mainfrom
fix/server/pr422-review-fixes

Conversation

@alexey-igrychev

@alexey-igrychev alexey-igrychev commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

Stacked on #422 (base d7b21fa5ce5b): the thirteen commits in d7b21fa..HEAD are new. They implement four findings from the final review published in #422, plus four findings from a self-review of that work: the privileged builder pod no longer receives a ServiceAccount token it never uses, and its lifetime is always bounded, so a plugin crash mid-build cannot leave a privileged pod running indefinitely.

What

Builder pod hardening

  • Every builder pod gets automountServiceAccountToken: false, including when serviceaccount= is configured — the configuration the QUICKSTART recommends. Nothing in the pod calls the Kubernetes API: the readiness probe is a local buildctl, and the build stream rides pods/exec opened with the plugin's own credentials.
  • Cloud workload-identity keeps working: IRSA (aws-iam-token), EKS Pod Identity (eks-pod-identity-token) and Azure Workload Identity (azure-identity-token) arrive as separate webhook-injected projected volumes, and GKE Workload Identity uses the node metadata server — none is the kube-api-access-* volume this field suppresses.
  • No driver option exposes the old behavior: in-cluster API access from a build has to be asked for explicitly, not inherited from serviceaccount=.
  • Every builder pod carries activeDeadlineSeconds: deadline= when configured, otherwise the release task's remaining time at pod creation plus a five-minute margin (1h only when the context carries no deadline, unreachable in production).
  • The cap counts from the moment the pod starts running; a pod that was never scheduled is not bounded by it — stated in the docs rather than silently implied.
  • The deadline terminates the pod but does not delete the object: a Failed pod stays visible until removed by hand or by pod GC — documented.

Option parsing

  • rootless= true, deadline= 90m, timeout= 10s (whitespace-padded values) now parse like the other options already did; padded-but-invalid values (deadline= 90) still error.
  • deadline=0s is still rejected — the whole-seconds ≥ 1s validation is untouched.

Docs

  • buildkitd_driver_opts field description, its generated reference row and both QUICKSTART locales (en/ru) state the unconditional token behavior and give the deadline default as the task's remaining time at pod creation, not its full task_timeout.
  • A duplicated doc-comment clause in builder.go is dropped — no behavior change.

Deliberately unchanged

  • MAJOR 2 from the review (the buildkitd_driver=kubernetes value naming) and the remaining MINORs/NITs are not addressed here — maintainers' calls, tracked in the feat(server): build releases in kubernetes without the docker binary #422 review comment.
  • The pre-existing buildx_driver=kubernetes path orphans a self-healing Deployment on the same crash and has no deadline counterpart. Out of scope for this PR.
  • UNVERIFIED: behavior on a real cluster — the kind e2e job was not run locally; unit tests cover the manifest and parsing, CI covers the rest.

Why

buildkitPod suppressed the token only when no serviceaccount= was configured, so the documented happy path handed a token carrying that ServiceAccount's full RBAC to a privileged container running untrusted project build instructions. The rationale in the code claimed a configured ServiceAccount must keep the cluster default or IRSA and workload identity would lose their credentials; that is wrong for all four mechanisms, and the built-in ServiceAccount admission plugin runs before mutating webhooks anyway, so false cannot strip what a webhook adds afterwards. Separately, activeDeadlineSeconds was set only when deadline= was configured — with no ownerReferences and all cleanup in-process, a Vault restart or plugin crash mid-build orphaned a privileged, token-carrying pod forever.

A constant default deadline was rejected: any fixed value either leaves the orphan alive for hours at the default 30m task timeout or silently kills legitimate builds the moment an operator raises task_timeout. Deriving from the task context's remaining time tracks the operator's own knob and guarantees the build context always expires before the pod deadline fires — which is also why the five-minute slack is clock-skew and termination margin, not time for the build to finish.

Fixes findings MAJOR 1, MAJOR 3, MINOR 9 and NIT 15 of the review in #422.

@alexey-igrychev

alexey-igrychev commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Updated after the review round. The verification and review-focus notes below originally described the pre-review code; the four findings from the review were applied on this branch in e89d569..e7ae3b6. The full review report and its evidence are in the next comment.

Verification

  • task server:test:unit paths="./pkg/docker/..." (ginkgo --race) — green; task server:lint 0 issues; task docs:lint:prettier clean; task docs:gen reproduces the generated reference row.
  • The configure.md reference row was proven byte-equal to the path_configure.go description plus the generator's trailing period by string comparison, not by eye.
  • Mutation: reverting buildkitPod to its pre-review shape — dropping AutomountServiceAccountToken from the PodSpec literal and restoring the if opts.serviceAccountName == "" guard — fails TestBuildkitPodAppliesResourcesAndScheduling while TestBuildkitPodDefaults still passes.
  • Mutation: resolvePodDeadline call dropped from bootstrap → the wiring test failed while the resolver table stayed green (why both exist); slack dropped → both failed; the zero floor dropped → TestResolvePodDeadline/an_expired_context_cannot_produce_a_negative_deadline failed; configured-wins early return dropped → resolver table failed.
  • Mutation: each of the three TrimSpace calls dropped in turn → the padded-values test failed naming that option each time.
  • Not run: the kind e2e job (not runnable locally); no claim in this PR depends on it beyond what unit tests prove at the manifest level.

Review focus

  • Slack ordering in resolvePodDeadline: the pod deadline is derived from the build context (task budget), not the readiness-scoped one — waitForPod derives its own context after the manifest is built. Worth one adversarial re-read since getting this wrong caps builds at timeout+5m.
  • AutomountServiceAccountToken is now false unconditionally, including when serviceaccount= is configured. Confirm the reasoning holds: nothing in the pod calls the Kubernetes API, and the four cloud identity mechanisms deliver credentials through their own webhook-injected volumes or the node metadata server rather than the kube-api-access-* mount this suppresses. The earlier "nil means cluster decides, load-bearing for IRSA" framing was the defect the review's MAJOR 1 removed.

Follow-up

@alexey-igrychev

alexey-igrychev commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

All four findings below are already fixed on this branch — this report is published for the record, with the fixing commit named on each. MAJOR 2 and the remaining #422 MINORs/NITs are left to the maintainer, as stated in the PR description.

Finding Fixed in
MAJOR 1 — the API token is still mounted whenever serviceaccount= is configured e89d569, docs in 6b8c554
MINOR 2 — docs promise task_timeout + 5m, the code uses the remaining task time 406f3ff, generated row in e029a84
NIT 3 — truncated slack comment stating the wrong reason 25556b3
NIT 4 — arbitrary time.Minute floor 537c2ea

Code Review Report — #425

Base: d7b21fa5ce5b (PR #422 head; PR #425 is stacked on it and targets main)
Head: f107206c44b5d533e050b862ac789a8e8ac83643 (branch fix/server/pr422-review-fixes)
Diff reviewed: d7b21fa..HEAD — 4 commits, 7 files, +138/-14

Reviewer: not the diff's author. Two independent advisors consulted (OpenAI GPT-5, Anthropic Claude Fable) on the security default and the deadline arithmetic.

Verdict

  • Technical: the deadline derivation is correct on every reachable path and the arithmetic guarantees the task ctx always expires ≥5m before the pod deadline, so no legitimate build can be reaped. The TrimSpace fix and the NIT comment removal are clean. One security default is wrong.
  • Product: the documented default ("the release task's timeout plus five minutes") does not match the implemented one (remaining task time + five minutes); an operator sizing task_timeout is misled by a few minutes.
  • Risk: automountServiceAccountToken is left at the cluster default in exactly the configuration the QUICKSTART recommends, so the MAJOR-1 hardening does not cover the documented happy path.

DoD Criteria (inferred from the PR body)

Criterion Inferred? Met? Evidence
MAJOR 1 — no ServiceAccount token in a builder pod that does not need one yes ⚠️ kubernetes.go:539 — only when serviceaccount= is unset; see finding 1
MAJOR 3 — every builder pod is bounded even if the plugin dies yes kubernetes.go:133,477; mutation M1/M7/M8 all caught
MINOR 9 — padded option values parse yes kubernetes.go:374,377,380; kubernetes_test.go:576; mutation M6 caught
NIT 15 — duplicated comment clause dropped yes builder.go:147
Docs (reference + both QUICKSTART locales) describe the new behavior yes ⚠️ configure.md:14, QUICKSTART.md:115, ru :114 — wording drift, finding 2

Issues

MAJOR 1 — the token fix does not cover the configuration the docs recommend

server/pkg/docker/kubernetes.go:539 (rationale at :537-538, mirrored at kubernetes_test.go:518)

AutomountServiceAccountToken is set to false only when no serviceaccount= is configured. The QUICKSTART's own recommended config is ["namespace=trdl-build", "serviceaccount=trdl-buildkit"] (docs/pages_en/QUICKSTART.md:99), so the path operators are told to take still mounts a Kubernetes API token into a privileged container running project-supplied build instructions.

The stated reason is factually wrong. IRSA injects a separate aws-iam-token projected volume at /var/run/secrets/eks.amazonaws.com/serviceaccount via amazon-eks-pod-identity-webhook; EKS Pod Identity injects eks-pod-identity-token; Azure Workload Identity injects azure-identity-token; GKE Workload Identity uses the node metadata server. None is the kube-api-access-* volume that automountServiceAccountToken: false suppresses, and the built-in ServiceAccount admission plugin runs before mutating webhooks, so false cannot strip what a webhook adds afterwards. AWS's own EKS best-practices guide recommends automountServiceAccountToken: false for workloads that do not call the Kubernetes API — and nothing in this pod does: the readiness probe is a local buildctl debug workers, and the build stream rides pods/exec opened with the plugin's rest config (kubernetes_exec.go:36).

Setting serviceaccount is how an operator asks for cloud identity; it is not a request for a Kubernetes API token. Fix: drop the branch — pod.Spec.AutomountServiceAccountToken = lo.ToPtr(false) unconditionally (one branch less), correct the comment and the test message, and update QUICKSTART.md:129 / ru :128. If an escape hatch is genuinely wanted, it belongs in supportedKubernetesDriverOpts (kubernetes.go:47-64) as an explicit opt-in, not as an implicit side effect of serviceaccount. Security-relevant default ⇒ maintainer's call per AGENTS.md.

MINOR 2 — the documented default is not the implemented default

docs/pages_en/QUICKSTART.md:115, docs/pages_ru/QUICKSTART.md:114, server/path_configure.go:143 (→ generated docs/_includes/reference/vault_plugin/configure.md:14)

All four say the default is "the release task's timeout (task_timeout, 30m unless configured) plus a five-minute margin". resolvePodDeadline uses time.Until(ctx.Deadline()) — the remaining task time at pod creation, i.e. task_timeout minus the git clone, GPG verification, trdl.yaml parse, secrets fetch and context-tar generation that run first (path_release.go:121-147, build.go:53-98). On a large repo that is minutes. An operator who reads the docs and sizes task_timeout against their longest release gets a tighter cap than promised. One word: "the release task's remaining time plus a five-minute margin". The 1h fallback (kubernetes.go:41) is also not mentioned; acceptable if the docs say "remaining time", since that branch is unreachable from the release path.

NIT 3 — the slack comment is truncated and gives the wrong reason

server/pkg/docker/kubernetes.go:37

"…so the deadline only ever fires when the plugin is no longer around to." — the sentence ends mid-clause. It is also the wrong rationale: the pod deadline is anchored at pod StartTime ≥ bootstrap, and the task ctx expires at bootstrap + remaining, so the ctx always fires ≥5m earlier and the plugin has already torn the pod down. The 5m is clock-skew and termination margin, not time for the build to finish.

NIT 4 — the 1-minute floor is unreachable, and a test asserts the unreachable case

server/pkg/docker/kubernetes.go:487, server/pkg/docker/kubernetes_test.go:419

max(..., time.Minute) only bites when the ctx is already expired at bootstrap — in which case createPod(ctx) (kubernetes.go:263) fails immediately and no pod is ever created. The floor is still load-bearing for the invariant (a negative remaining would make if opts.deadline > 0 false and silently drop activeDeadlineSeconds, the exact bug being fixed), so keep it — but time.Minute is an arbitrary value where max(..., 0) holds the same invariant, and the test case name ("an expired context still yields a usable deadline") reads as a production guarantee it is not.

Verification

  • go test -race -count=1 ./pkg/docker/... from server/ok github.com/werf/trdl/server/pkg/docker 2.162s. go vet ./pkg/docker/... — clean.
  • task server:test:unit / task server:lint could not run: the root Taskfile includes a remote common-ci Taskfile and task refuses it — Taskfile "https://raw.githubusercontent.com/werf/common-ci/refs/heads/main/Taskfile.format_lint.yml" not trusted by user. CI must still run the task-based checks.
  • Test falsifiability (test-the-tests), 8 mutations applied through go test -overlay= so no repository file was modified — every one failed the intended test:
    1. drop opts.deadline = resolvePodDeadline(...) in bootstrapTestKubernetesBuilderBootstrapAlwaysSetsAPodDeadline FAIL
    2. drop the automount block → TestBuildkitPodDefaults FAIL
    3. automount unconditional falseTestBuildkitPodAppliesResourcesAndScheduling FAIL
    4. drop .Round(time.Second)TestResolvePodDeadline/the_remaining_task_time_plus_the_slack FAIL
    5. drop the max(..., time.Minute) floor → TestResolvePodDeadline/an_expired_context... FAIL
    6. revert the three TrimSpacees → TestParseKubernetesDriverOptsTrimsPaddedValues FAIL
    7. configured > 0configured >= 0 → 3 subtests + bootstrap test FAIL
    8. always return the 1h fallback → 2 subtests + bootstrap test FAIL
  • Check-gaming: none found. The one assertion whose meaning changed (kubernetes_test.go:468, ActiveDeadlineSeconds nil) is legitimate — buildkitPod takes no ctx, the guarantee moved to bootstrap, and the new bootstrap test covers it.
  • The hand-edited generated reference row (configure.md:14) is byte-identical to what task docs:gen would emit: the template at server/pkg/gendocs/templates.go:325 is * \{{ .Name }}` ({{ .Type }}, {{ .RequiredOrOptional }}) — {{ .Description }}.and the row is the newpath_configure.go:143` description plus the trailing period. Not a finding.
  • Not verified: behavior on a real cluster (no kind e2e run here), and the server plugin does not compile on macOS beyond this package.

Not findings (checked)

  • No false-positive kill of a legitimate build: pod expiry = StartTime + remaining + 5m, task ctx expiry = bootstrap + remaining, StartTime ≥ bootstrap ⇒ the ctx always wins by ≥5m.
  • int64(opts.deadline.Seconds()) cannot truncate: configured values are validated whole-seconds ≥1s (kubernetes.go:411-415) and derived ones are Rounded to whole seconds.
  • A pod stuck Pending is not capped (activeDeadlineSeconds counts from StartTime), and the deadline terminates without deleting the object — both are now documented in both locales (QUICKSTART.md:124, ru :123). Acceptable; the structural fix is a label plus a sweep at plugin start.
  • No user-facing breaking change: the buildkitd_driver=kubernetes feature is unreleased (feat(server): build releases in kubernetes without the docker binary #422 still open).
  • Out of scope: the pre-existing buildx_driver=kubernetes path (builder.go:349, present on main) orphans a self-healing Deployment on the same crash and has no deadline counterpart. Follow-up, not a finding against this diff.

ANCHORS:
server/pkg/docker/kubernetes.go:539 — MAJOR: automountServiceAccountToken: false only when no serviceaccount= is set, so the QUICKSTART's own recommended config (serviceaccount=trdl-buildkit) still mounts a Kubernetes API token into a privileged container running untrusted build instructions; make it unconditional.
server/pkg/docker/kubernetes.go:538 — MAJOR (same finding, rationale): IRSA, EKS Pod Identity and Azure Workload Identity deliver credentials through their own webhook-injected projected volumes, and GKE WI through the metadata server — none depends on automountServiceAccountToken, so this comment's reason does not hold.
server/pkg/docker/kubernetes_test.go:518 — MAJOR (same finding): the assertion message encodes the same wrong claim ("or IRSA and workload identity lose their token") as a test-level guarantee.
docs/pages_en/QUICKSTART.md:115 — MINOR: says the default is task_timeout + 5m, but the code uses the task's remaining time at pod creation (minus clone, GPG verification, secrets, context tar).
docs/pages_ru/QUICKSTART.md:114 — MINOR: same drift in the Russian locale ("таймаут самой релизной задачи" vs. the remaining time).
server/path_configure.go:143 — MINOR: same drift in the field description, which is copied verbatim into the generated reference row (configure.md:14).
server/pkg/docker/kubernetes.go:37 — NIT: truncated sentence ("no longer around to.") and the wrong reason for the 5m slack — the task ctx always expires first, so it is clock-skew/termination margin.
server/pkg/docker/kubernetes.go:487 — NIT: the time.Minute floor is only reachable with an already-expired ctx, where createPod fails before a pod exists; keep the floor for the invariant but 0 would do, and the matching test case reads as a production guarantee it is not.


Verification evidence

All checks ran through task (no raw go test / go vet / golangci-lint), on darwin/arm64.

  • task --yes server:test:unit — Test Suite Passed, composite coverage 47.8%. A pre-flight run on the unmodified tree passed first, so every later result is attributable to the change.
  • task --yes server:lint0 issues., prettier clean (after task --yes deps:install:golangci-lint).
  • task --yes docs:lint:prettier — clean. Note it checks **/*.{yaml,yml} only, so it is a no-op for the three markdown files here and is not evidence that they are format-clean.
  • task --yes docs:gen — ran successfully; docs/_includes/reference/vault_plugin/configure.md was regenerated, no other reference page moved. The row was byte-compared against the template output (* + name + (array, optional) — + field description + .) and matches.

Both behavior fixes were mutation-tested on a committed tree, each mutation reverted with a path-scoped git restore and gated on git diff --quiet:

  • MAJOR 1 — reverting buildkitPod to its pre-fix shape (drop the literal field, restore the if opts.serviceAccountName == "" guard) fails TestBuildkitPodAppliesResourcesAndScheduling while TestBuildkitPodDefaults still passes, so the assertion is falsifiable for exactly the configured-ServiceAccount case the finding is about.
  • NIT 4 — dropping the floor (max(..., 0) → bare time.Until(...)) fails TestResolvePodDeadline/an_expired_context_cannot_produce_a_negative_deadline. The floor stays because it carries a real invariant: a negative remaining would make opts.deadline > 0 false and silently drop activeDeadlineSeconds.

Three further review passes over the fix range produced no CRITICAL and no MAJOR. They did produce four documentation corrections against my own wording, all applied: the security bullet first contradicted itself (it told operators to audit RBAC bindings that are reachable only through the token the next sentence said was never mounted), then overstated the result as "not reachable from inside the build" when the very next bullet describes a privileged container that can escape to node-level credentials — it now scopes the claim to the withheld credential and names that residual — plus three RU register slips and the removal of a vendor-mechanism claim this repo's CI never exercises. Commits 8cb7f1e, c08cd9d, e7ae3b6.

Recorded but deliberately NOT fixed, as it falls outside the four findings and into the base PR range (6fffde9): fakeAPIServer.createdPod() (server/pkg/docker/kubernetes_test.go:154) returns f.created.DeepCopy(), which is nil when no pod was created, so a caller reaching into .Spec panics instead of failing the assertion. Harmless today — every call site is preceded by require.NoError on bootstrap — but a future test on a failure path would get a nil-deref instead of a message. One require.NotNil in the helper closes it, at the maintainer's discretion.

UNVERIFIED, unchanged from the PR description: behavior on a real cluster. The kind e2e job was not run locally; CI is the gate.

@alexey-igrychev
alexey-igrychev force-pushed the fix/server/pr422-review-fixes branch from 4b40e9a to 537c2ea Compare September 2, 2026 11:37
@alexey-igrychev alexey-igrychev changed the title fix(server, docs): harden the kubernetes builder pod fix(server): harden the kubernetes builder pod Sep 2, 2026
@alexey-igrychev
alexey-igrychev marked this pull request as ready for review September 2, 2026 12:50
The builder pod runs project-supplied build instructions in a privileged
container. With no ServiceAccount of its own it was still given the
namespace's default token, so a build could reach the API server with
whatever that account carries. Disable the automount in that case.

A configured serviceaccount keeps the cluster default instead of an
explicit false, because IRSA, EKS Pod Identity and workload identity all
deliver their credentials through the mounted token.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
Nothing outside the plugin process deletes the builder pod: a crash or a
kill between creating it and removing it left a privileged container
running project instructions for as long as the cluster would have it,
and only an operator who had set deadline= was protected.

Derive the pod's activeDeadlineSeconds from the release task's own
context deadline plus a five-minute margin whenever no deadline is
configured, so the pod dies shortly after the build that owns it. A
configured deadline still wins, and a context without one falls back to
an hour.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The namespace, image and serviceaccount options trim their values, but
rootless, deadline and timeout handed the raw text to ParseBool and
ParseDuration, so a pair written as "deadline= 90m" was rejected with a
parse error the operator cannot spot in their own configuration. Trim
those three the same way the sibling options already do.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The doc comment on unusedBuilderSettings ended with the same clause as
the inline comment in NewBuilder that explains the environment/configure
asymmetry. Keep the explanation in one place.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The builder pod suppressed the API token only when no `serviceaccount=` was
configured, so the configuration the QUICKSTART recommends handed a token with
that ServiceAccount's full RBAC to a privileged container running
project-supplied build instructions. Nothing in the pod calls the Kubernetes
API: the readiness probe is a local `buildctl` and the build stream rides
`pods/exec` opened with the plugin's own credentials.

The removed rationale claimed a configured ServiceAccount must keep the cluster
default or IRSA and workload identity lose their credentials. That is wrong for
all four mechanisms: IRSA injects `aws-iam-token`, EKS Pod Identity injects
`eks-pod-identity-token` and Azure Workload Identity injects
`azure-identity-token` as separate webhook-mounted projected volumes, while GKE
Workload Identity uses the node metadata server — none is the `kube-api-access-*`
volume this field suppresses.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The token bullet in both QUICKSTART locales still described the old conditional
behavior — a token mounted whenever `serviceaccount` was configured. It now
states the unconditional `automountServiceAccountToken: false` and scopes the
claim to the mount that field actually suppresses, noting that cloud
workload-identity mechanisms are unaffected because they deliver credentials
through their own volumes or the node metadata server.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The field description and both QUICKSTART rows promised the release task's own
timeout plus a five-minute margin, but resolvePodDeadline derives the cap from
the time the task context has left when the pod is created — clone, tag
verification and context tar have already been spent by then. An operator
sizing task_timeout for their longest release got a cap tighter than documented.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
Picks up the corrected buildkitd_driver_opts deadline wording from
path_configure.go. Generated by `task docs:gen`; no other reference page moved.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The sentence ended mid-clause ("no longer around to.") and gave the wrong
reason: the five minutes are not time for the build to finish. The pod deadline
is anchored at the pod's StartTime, at or after bootstrap, while the build
context expires at bootstrap plus the same remaining time — so the context
always fires first and the slack is clock-skew and termination margin.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The time.Minute floor only ever applied to an already-expired context, where
createPod fails on that same context before any pod exists — so the minute was
arbitrary. The floor still carries a real invariant, which is why it stays: a
negative remaining would make `opts.deadline > 0` false in buildkitPod and
silently drop activeDeadlineSeconds. Zero holds that just as well, and the test
case now names the invariant instead of promising a production guarantee.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The bullet told operators to audit RBAC bound to the builder's ServiceAccount
through RoleBindings, ClusterRoleBindings and group bindings, and then said the
pod never receives an API token — but Kubernetes RBAC is exercisable from a pod
only through a mounted ServiceAccount credential, so that enumeration named
privileges the build cannot reach. It now states the unreachability as the
consequence of the missing mount.

The same sentence framed surviving cloud workload identity as reassurance
inside a threat-model bullet, when it is the residual leak: a privileged
container running project-supplied instructions still reaches whatever cloud
role is bound to the chosen ServiceAccount, so the audit advice is re-scoped to
that, `imagePullSecrets` and admission policy. The RU translation also dropped
a transliterated «моунт» and «не страдают», neither of which the file uses
elsewhere. The constant comment now records that the user-facing description
deliberately omits the unreachable 1h fallback.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The bullet said RBAC bound to the builder's ServiceAccount "is not reachable
from inside the build", but the next bullet states the container is privileged
by default — and a privileged container reaches the node filesystem, so other
pods' projected tokens and the kubelet's own credentials are a container escape
away. The accurate claim is narrower: the build is not handed a token for that
ServiceAccount and cannot act as it against the API. The bullet now says so and
names the residual node-level exposure instead of implying isolation.

The RU twin also translated admission policy as «политики допуска», while the
file keeps «PodSecurity-admission» untranslated twice, and opened the list with
a phrase that did not agree with it. Separately, the `defaultBuildkitPodDeadline`
comment loses the clause about what the user-facing description omits: it
described another artifact and would rot the moment that description changed.

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
The bullet explained how cloud workload identity delivers credentials — through
its own volumes or the node metadata server — which is a generic claim about
four vendor mechanisms this repo's CI never exercises, and it made an already
long security bullet longer. The operative statement is unchanged: those
mechanisms are not covered by the suppressed mount, so the build reaches
whatever cloud role is bound to the chosen ServiceAccount.

The RU twin also carried a calque of "act as it against the API"; it now reads
«обращаться к API от его имени».

Signed-off-by: Aleksei Igrychev <aleksei.igrychev@palark.com>
@alexey-igrychev
alexey-igrychev force-pushed the fix/server/pr422-review-fixes branch from e7ae3b6 to ecc4f7e Compare September 2, 2026 12:54
@alexey-igrychev
alexey-igrychev merged commit a10e3b9 into main Sep 2, 2026
27 of 28 checks passed
@alexey-igrychev
alexey-igrychev deleted the fix/server/pr422-review-fixes branch September 2, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant