Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/_includes/reference/vault_plugin/configure.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Configure the plugin.

* `buildkitd_address` (string, optional) — An address of a running buildkitd (unix://, tcp://, docker-container:// or kube-pod:// scheme) to build release artifacts with the BuildKit client; the docker CLI is used only when neither this nor buildkitd_driver is set. Build secrets are sent to that daemon, and tcp:// is neither encrypted nor authenticated, so securing the channel and isolating the daemon is the administrator's responsibility.
* `buildkitd_driver` (string, optional) — Provision an ephemeral buildkitd per build instead of using the docker CLI: kubernetes runs it as a pod and needs no docker binary next to the plugin. Cannot be combined with buildkitd_address, buildx_driver or buildx_driver_opts. A TRDL_BUILDKITD_ADDRESS set on the process wins over a stored driver, and the build reports the driver as unused.
* `buildkitd_driver_opts` (array, optional) — The buildkitd driver options, one name=value pair per element (e.g. namespace=trdl-build); they require buildkitd_driver to be set. The kubernetes driver accepts annotations, deadline, image, labels, limits.cpu, limits.ephemeral-storage, limits.memory, namespace, nodeselector, requests.cpu, requests.ephemeral-storage, requests.memory, rootless, serviceaccount and timeout; anything else is rejected.
* `buildkitd_driver_opts` (array, optional) — The buildkitd driver options, one name=value pair per element (e.g. namespace=trdl-build); they require buildkitd_driver to be set. The kubernetes driver accepts annotations, deadline, image, labels, limits.cpu, limits.ephemeral-storage, limits.memory, namespace, nodeselector, requests.cpu, requests.ephemeral-storage, requests.memory, rootless, serviceaccount and timeout; anything else is rejected. When deadline is not set, it defaults to the release task's remaining time at pod creation plus a five-minute margin, so a plugin crash cannot leave the builder pod running indefinitely.
* `buildx_driver` (string, optional) — The buildx driver to build release artifacts with: docker-container (used by default) or kubernetes. Takes precedence over the TRDL_BUILDX_DRIVER environment variable, and cannot be combined with buildkitd_address or buildkitd_driver.
* `buildx_driver_opts` (array, optional) — The buildx driver options, one --driver-opt per element (e.g. namespace=trdl-build), passed through as is. Take precedence over the TRDL_BUILDX_DRIVER_OPTS_* environment variables, and cannot be combined with buildkitd_address or buildkitd_driver.
* `git_repo_url` (string, required) — URL of the Git repository.
Expand Down
6 changes: 3 additions & 3 deletions docs/pages_en/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ The options are `name=value` pairs, one per list element and passed through as i
| `requests.cpu`, `requests.memory`, `requests.ephemeral-storage` | pod resource requests |
| `limits.cpu`, `limits.memory`, `limits.ephemeral-storage` | pod resource limits |
| `timeout` | how long to wait for the builder to become ready, e.g. `5m`; `2m` by default |
| `deadline` | a hard lifetime cap for the builder pod (`activeDeadlineSeconds`), e.g. `2h`; unset by default, and a whole number of seconds of at least `1s`. It is not a grace period: a build still running when it expires is killed too, so set it above the longest release this project takes |
| `deadline` | a hard lifetime cap for the builder pod (`activeDeadlineSeconds`), e.g. `2h`; defaults to the release task's remaining time at pod creation ([`task_timeout`](/reference/vault_plugin/task/configure.html), `30m` unless configured, minus whatever the release has already spent) plus a five-minute margin; a whole number of seconds of at least `1s`. It is not a grace period: a build still running when it expires is killed too, so set it above the longest release this project takes |

The option names are the buildx kubernetes driver's own wherever the two overlap, but the vocabulary is this driver's, not buildx's: options buildx accepts and this driver does not — `replicas`, `loadbalance`, `tolerations`, `schedulername`, `qemu.*`, the persistent-volume options — are rejected, and `deadline` has no buildx counterpart.

Expand All @@ -121,12 +121,12 @@ What the plugin needs in the target namespace is `create`, `get` and `delete` on
Notes on the pod:

* the namespace must exist and must admit the builder pod. By default the container runs `privileged`; with `rootless=true` it runs unprivileged but needs seccomp `Unconfined` and the `unconfined` AppArmor annotation instead. Either way the `baseline` PodSecurity level forbids it, so the namespace has to be labelled `privileged` or be exempt from PodSecurity admission — the same requirement the buildx `kubernetes` driver has. Unlike the buildx path, the rejection arrives directly from the `create` call rather than as a readiness timeout;
* the pod is removed when the build ends, including when it fails or is canceled, and when the builder never becomes ready. It is not removed if the plugin's own process dies outright, and not if the delete itself fails — a lost API connection, a withdrawn `delete` permission. That failure is reported in the release log and the plugin log, but it does not fail the release, so a privileged pod can outlive a build that reported success. `deadline` is what bounds both cases, at the cost of also capping a legitimate build;
* the pod is removed when the build ends, including when it fails or is canceled, and when the builder never becomes ready. It is not removed if the plugin's own process dies outright, and not if the delete itself fails — a lost API connection, a withdrawn `delete` permission. That failure is reported in the release log and the plugin log, but it does not fail the release, so a privileged pod can outlive a build that reported success. Both cases are bounded by `activeDeadlineSeconds`, which is always set — `deadline` overrides the default. Note it terminates the pod but does not delete the object: a Failed pod remains visible until removed by hand or by the cluster's pod garbage collection. It is also counted from the moment the pod starts running, so a pod that was never scheduled — no node, a quota rejection — is not capped by it;
* the builder is a bare Pod with `restartPolicy: Never`, deliberately: nothing may replace it mid-build, because the replacement would be a builder the release is not connected to.

Two things follow from the plugin creating the pod with its own credentials, and both are the administrator's to weigh:

* **`configure` write access becomes pod-create access.** Whoever can write a project's configuration chooses the `namespace` the builder runs in, the `serviceaccount` it runs as and the `image` it runs — anywhere the plugin's own Role reaches. Restrict `<project>/configure` to the same people who are trusted with the release keys, and keep the plugin's Role to namespaces that hold nothing else worth taking. Give the builder a ServiceAccount whose privileges you have audited in full: creating a workload lets it run as any ServiceAccount of that namespace, and a ServiceAccount carries whatever is bound to it directly, through a namespaced RoleBinding, through a ClusterRoleBinding, and through group bindings such as `system:serviceaccounts`. The absence of a RoleBinding in the namespace is not by itself an isolation guarantee, and the builder pod does mount its token.
* **`configure` write access becomes pod-create access.** Whoever can write a project's configuration chooses the `namespace` the builder runs in, the `serviceaccount` it runs as and the `image` it runs — anywhere the plugin's own Role reaches. Restrict `<project>/configure` to the same people who are trusted with the release keys, and keep the plugin's Role to namespaces that hold nothing else worth taking. Give the builder a ServiceAccount whose privileges you have audited in full: creating a workload lets it run as any ServiceAccount of that namespace. The builder pod never gets the automatically mounted ServiceAccount API-token volume (`automountServiceAccountToken: false`), whether or not a `serviceaccount` is configured. The build is therefore not handed a token for that ServiceAccount and cannot act as it against the API — not through a namespaced RoleBinding, not through a ClusterRoleBinding, not through a group binding such as `system:serviceaccounts`. That is a withheld credential, not isolation: the privileged container described below still reaches node-level credentials. What the ServiceAccount still confers is cloud workload identity, `imagePullSecrets` and any admission policy keyed on it — none of which the suppressed mount covers, so the build reaches whatever cloud role is bound to the ServiceAccount you pick.
* **The builder container is privileged by default.** BuildKit needs it; `rootless=true` trades it for seccomp `Unconfined` and the `unconfined` AppArmor annotation. Either way the build executes project-supplied instructions in a container the `baseline` PodSecurity level would refuse, so give it a namespace and nodes you are willing to lose, not the ones the signing keys live on.

##### Connecting to an existing buildkitd
Expand Down
6 changes: 3 additions & 3 deletions docs/pages_ru/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ vault write trdl-test-project/configure ... \
| `requests.cpu`, `requests.memory`, `requests.ephemeral-storage` | requests пода |
| `limits.cpu`, `limits.memory`, `limits.ephemeral-storage` | limits пода |
| `timeout` | сколько ждать готовности сборщика, например `5m`; по умолчанию `2m` |
| `deadline` | жёсткий предел времени жизни пода сборщика (`activeDeadlineSeconds`), например `2h`; по умолчанию не задан, задаётся целым числом секунд не меньше `1s`. Это не льготный период: сборка, которая к этому моменту ещё идёт, тоже будет убита, поэтому значение берут заведомо больше самого долгого релиза проекта |
| `deadline` | жёсткий предел времени жизни пода сборщика (`activeDeadlineSeconds`), например `2h`; по умолчанию — оставшееся время релизной задачи на момент создания пода ([`task_timeout`](/reference/vault_plugin/task/configure.html), `30m`, если не настроен иначе, минус уже потраченное релизом) плюс пять минут запаса, задаётся целым числом секунд не меньше `1s`. Это не льготный период: сборка, которая к этому моменту ещё идёт, тоже будет убита, поэтому значение берут заведомо больше самого долгого релиза проекта |

Имена опций совпадают с опциями buildx-драйвера `kubernetes` там, где опции пересекаются, но набор здесь свой, а не buildx: опции, которые принимает buildx и не принимает этот драйвер — `replicas`, `loadbalance`, `tolerations`, `schedulername`, `qemu.*`, опции постоянного тома, — отвергаются, а у `deadline` соответствия в buildx нет вовсе.

Expand All @@ -120,12 +120,12 @@ vault write trdl-test-project/configure ... \
Особенности пода:

* namespace должен существовать и должен пропускать под сборщика. По умолчанию контейнер запускается `privileged`; при `rootless=true` он непривилегированный, но требует seccomp `Unconfined` и AppArmor-аннотации `unconfined`. И то и другое запрещено на уровне PodSecurity `baseline`, поэтому namespace должен быть помечен как `privileged` либо исключён из PodSecurity-admission — ровно то же требование, что и у buildx-драйвера `kubernetes`. В отличие от buildx-пути, отказ приходит прямо из вызова `create`, а не в виде таймаута готовности;
* под удаляется по окончании сборки, в том числе при её падении и отмене, а также если сборщик так и не стал готов. Он не удаляется, если сам процесс плагина умер целиком, и если само удаление не прошло — оборвалась связь с API, отозвали право `delete`. Об этом сообщают лог релиза и лог плагина, но релиз при этом не падает, поэтому привилегированный под может пережить сборку, отчитавшуюся успехом. Оба случая ограничивает `deadline` — ценой такого же ограничения для нормальной сборки;
* под удаляется по окончании сборки, в том числе при её падении и отмене, а также если сборщик так и не стал готов. Он не удаляется, если сам процесс плагина умер целиком, и если само удаление не прошло — оборвалась связь с API, отозвали право `delete`. Об этом сообщают лог релиза и лог плагина, но релиз при этом не падает, поэтому привилегированный под может пережить сборку, отчитавшуюся успехом. Оба случая ограничивает `activeDeadlineSeconds`, который выставляется всегда, — `deadline` лишь переопределяет значение по умолчанию. Учтите, что он завершает под, но не удаляет объект: под в фазе `Failed` остаётся виден, пока его не удалят вручную или сборщик мусора подов кластера. Отсчёт идёт с момента запуска пода, поэтому под, который так и не был запланирован — нет узла, отказ по квоте, — им не ограничен;
* сборщик — именно отдельный Pod с `restartPolicy: Never`, и это осознанно: его нельзя подменять посреди сборки, потому что замена окажется сборщиком, с которым релиз не связан.

Из того, что под создаёт сам плагин своими кредами, следуют две вещи, и обе — предмет решения администратора:

* **Право записи в `configure` становится правом создавать поды.** Кто может писать конфигурацию проекта, тот выбирает `namespace`, в котором поднимется сборщик, `serviceaccount`, под которым он побежит, и `image`, который в нём запустится, — везде, куда достаёт Role самого плагина. Ограничьте доступ на запись в `<project>/configure` тем же кругом, которому доверены релизные ключи, а Role плагина — namespace'ами, в которых больше нечего брать. Сборщику давайте ServiceAccount, права которого проверены целиком: создание workload'а позволяет запуститься от имени любого ServiceAccount этого namespace, а ServiceAccount несёт всё, что привязано к нему напрямую, через namespaced RoleBinding, через ClusterRoleBinding и через групповые привязки вроде `system:serviceaccounts`. Отсутствие RoleBinding в namespace само по себе гарантией изоляции не является, и токен под сборщика монтируется.
* **Право записи в `configure` становится правом создавать поды.** Кто может писать конфигурацию проекта, тот выбирает `namespace`, в котором поднимется сборщик, `serviceaccount`, под которым он побежит, и `image`, который в нём запустится, — везде, куда достаёт Role самого плагина. Ограничьте доступ на запись в `<project>/configure` тем же кругом, которому доверены релизные ключи, а Role плагина — namespace'ами, в которых больше нечего брать. Сборщику давайте ServiceAccount, права которого проверены целиком: создание workload'а позволяет запуститься от имени любого ServiceAccount этого namespace. Под сборщика никогда не получает автоматически монтируемый том с API-токеном ServiceAccount (`automountServiceAccountToken: false`) — независимо от того, задан `serviceaccount` или нет. Значит, сборке не выдаётся токен этого ServiceAccount и она не может обращаться к API от его имени: ни через namespaced RoleBinding, ни через ClusterRoleBinding, ни через групповые привязки вроде `system:serviceaccounts`. Это невыданный токен, а не изоляция: привилегированный контейнер из следующего пункта по-прежнему дотягивается до кредов уровня узла. Что ServiceAccount всё же даёт: облачную workload identity, `imagePullSecrets` и любые admission-политики, завязанные на него, — ничего из этого отключённое монтирование не покрывает, так что сборка дотягивается до любой облачной роли, привязанной к выбранному вами ServiceAccount.
* **Контейнер сборщика по умолчанию привилегированный.** Этого требует BuildKit; `rootless=true` меняет привилегию на seccomp `Unconfined` и AppArmor-аннотацию `unconfined`. В любом случае сборка исполняет инструкции проекта в контейнере, который уровень PodSecurity `baseline` не пропустил бы, — значит и namespace, и узлы под неё надо давать те, которые не жалко, а не те, где лежат ключи подписи.

##### Подключение к существующему buildkitd
Expand Down
Loading
Loading