Commit Graph
14682 Commits
Author SHA1 Message Date
evangelostsak df4be52aa6 test(web-app-openbao): drop the env keys the retired tier spec consumed 2026-09-12 00:17:42 +02:00
evangelostsak 840f59e080 test(web-app-openbao): stop aggregating the retired tier spec 2026-09-12 00:17:33 +02:00
evangelostsak 49201309d5 test(web-app-openbao): retire the account-based RBAC tier spec 2026-09-12 00:17:20 +02:00
evangelostsak 0053b07f2c fix(web-app-openbao): drop the dedicated RBAC tier accounts
baooperator and baoreader existed only to hold one RBAC group each, and a
role-declared account has no stable password until the inventory pins one, so
they needed a workaround to be logged in as at all. Moving biber through the
groups covers the same three tiers with an account whose password was never
broken, and additionally proves the grant and the revoke rather than a steady
state.
2026-09-12 00:17:07 +02:00
kevinveenbirkenbachandClaude Opus 5 ffb04da40a refactor(tor): keep the onion timeout multipliers with the flavor they key on
The per-flavor multipliers lived in test-e2e-playwright's vars, where only
that role could read them. The CSP health check needs the same number: it
probes onion vhosts through the Tor proxy, where the checker's navigation
budget has to grow by the same factor as a Playwright wait.

They now sit in svc-net-tor's meta/services.yml next to services.tor.flavor,
the key they are indexed by, and the Playwright var reads them through
lookup('config', 'svc-net-tor', 'services.tor.timeout_multipliers'). That
lookup resolves from the role defaults, so it answers even where svc-net-tor
is not part of the round, which is how the flavor was already read.

The flavor-coverage test moves to the tor role and reads the new location;
the values (chutney 2, public 5) and what the Playwright run computes from
them are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 00:16:17 +02:00
kevinveenbirkenbachandClaude Opus 5 c32bf320ff feat(ci): add a fourth deploy chunk so a short priority chunk strands no budget
A sweep may deploy 210 rows (available) in chunks of 80. Three declared
blocks hold exactly that when there is no priority line, but priority
rows get a chunk of their own that is never topped up with regular rows.
Nine priority rows therefore occupied a whole block, left the regular
rows 2 x 80 = 160, and 42 rows of budget went unused.

Adding a block to the YAML alone would not have changed that:
matrix.chunks_of handed the split slots.chunk_count(), which is
min(ceil(available / chunk size), INFINITO_CI_MAX_CHUNKS) and ignores
the priority seam, so the plan still stopped at three blocks.

call-orchestrator.yml now declares test-deploy-chunk-3, gated on chunks
0 to 2 like every chunk gates on all before it, and report-main-failures
and done need it. INFINITO_CI_MAX_CHUNKS is 4, one above what the budget
fills, and slots.chunk_blocks() returns it. chunks_of hands the split
every declared block; available still caps the rows, and a chunk whose
slice comes back empty skips its deploy. chunk_count() stays the "chunks
filled" figure of the budget report.

Tests: the split spends 9 + 80 + 80 + 41 = 210 rows with a spare block;
chunks_of plans three chunks from chunk_blocks=3 while chunk_count=2
(the previous wiring yields [1, 10]); an integration guard requires the
declared chunk jobs to equal INFINITO_CI_MAX_CHUNKS and to exceed
ceil(available / chunk size) by one, and fails on the stale value 3.

A shell that inherited INFINITO_CI_MAX_CHUNKS=3 keeps failing that guard
until it is restarted; make dotenv-force rewrites .env.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:46:48 +02:00
kevinveenbirkenbachandClaude Opus 5 fbe07d7fb0 chore(dashboard): move to port-ui 2.1.3 for the CSP-safe interface strings
port-ui 2.1.2 set window.I18N from an executable inline script. Core
serves the dashboard with 'unsafe-inline' in script-src-elem only while
its logout feature is on (csp_filters adds it for web-svc-logout);
otherwise the directive carries hashes and the browser blocked the
script. That failed the dashboard specs wherever web-svc-logout was not in
the round: Fediwall#1 in run 34615516009 and both Workspace jobs in run
34614940508, each with "Executing inline script violates ...
script-src-elem 'self' 'sha256-...'". Fider#0 and n8n#0 deployed
web-svc-logout and passed. The 2.1.2 bump claimed the existing CSP covered
the new script; it did only in rounds with logout.

2.1.3 ships the strings as a type="application/json" data block that CSP
does not govern, and its suite forbids executable inline scripts. It also
fixes the card icon onerror handler, a SecurityError on cross-origin
iframes and a recalc loop that never stopped after fullscreen toggles.

The tag was not on ghcr when this was committed (manifest 404 upstream and
in the mirror); a deploy pulls it only after the release, and the
orchestrator mirrors the missing tag before it deploys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:35:10 +02:00
evangelostsak c9e6a0fddc docs(requirements): correct how the RBAC tier accounts get their password 2026-09-11 23:29:57 +02:00
evangelostsak 3ebc686857 docs(web-app-openbao): correct how the RBAC tier accounts get their password 2026-09-11 23:29:45 +02:00
evangelostsak ef4b83014d fix(web-app-openbao): give the RBAC tier accounts ansible_become_password
Matches user-administrator and web-app-semaphore, the only other roles that
set a password in meta/users.yml, and the key the meta lint allow-lists. It
adds no exposure: user-administrator already assigns that password to the
administrator account, which holds the OpenBao administrator policy through
the same LDAP groups, so both accounts are strictly less privileged than one
that already shares the secret.
2026-09-11 23:28:58 +02:00
evangelostsak 85bd62de30 Revert "feat(web-app-openbao): generate the RBAC tier account passwords as role credentials"
meta/*.yml is static schema, not a runtime template, and a lookup into the
applications tree from there was the most runtime-dependent expression in any
meta file. The accounts take ansible_become_password instead.
2026-09-11 23:28:26 +02:00
kevinveenbirkenbachandClaude Opus 5 926d92cfbf docs(test-e2e-playwright): name the real only_roles default and how a round picks specs
The README gave allowed_applications as the default of
test_e2e_playwright_only_roles and told contributors to list their app
there. Neither exists in the role any more: the default is
lookup('deployment').running (defaults/main.yml), which resolves to every
role the round deploys, dependencies included, in compose through the
--id whitelist and in swarm through the inventory groups.

Both places now say so, and the how-to step points at deploying a round
that includes the app.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:02:07 +02:00
kevinveenbirkenbachandClaude Opus 5 72a7f39505 fix(sso-proxy): restart the swarm sidecar when its rendered config changes
Snipe-IT#1 (run 34615516009, job 103327034508) failed every login in the
async pass with an oauth2-proxy 500. The sidecar log shows 24x "error
saving redis session: WRONGPASS invalid username-password pair". The swarm
matrix rotated 54 credentials before that pass, svc-db-redis re-rendered
its requirepass and redis_redis was recreated ("about an hour ago"), while
snipe-it_sso-proxy kept running from the first pass ("3 hours ago").

oauth2-proxy reads its config, redis password included, from a bind mount
at start. Its render task notifies compose-up, which in compose is
"compose up -d --force-recreate" and so recreates the sidecar, but in swarm
is "docker stack deploy", which only restarts a service whose spec changed.
A new file behind an unchanged bind mount is no spec change, so the sidecar
never read the rotated password. pihole#1 in the same run shows the compose
side: shared redis, 47 rotated values, the config re-rendered, compose up
recreated the sidecar, and the job passed.

The sidecar now carries a container label with the sha256 of its config,
rendered from the same template sso_proxy.yml writes, with
sso_proxy_application_id supplied through template_vars. A changed config
changes the label and with it the service spec, so stack deploy restarts
only the sidecar; an unchanged config keeps the hash and restarts nothing.
Compose behaviour is unchanged.

A guard test pins the label and requires it to hash the template
sso_proxy.yml renders; it fails against the previous template. The hash is
stable for equal input and changes with a rotated secret, checked with the
template lookup on ansible-core.

Not covered: other bind-mounted configs that embed rotated secrets have the
same swarm exposure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:00:36 +02:00
kevinveenbirkenbachandClaude Opus 5 ea85683796 fix(security): hoist uniform credential no_log to the include and mask bridge loops
b3ce43a596 set no_log on every task of two included files, which
test_include_uniform_apply_keyword rejects: a keyword every included task
repeats belongs in the include's apply block. The Cloudflare development
mode tasks and the ChatGPT bridge token tasks now receive no_log through
apply at their include sites. The matrix include had set no_log on the
include task itself, which never reaches the included tasks.

Two loops over MATRIX_BRIDGES were unmasked: the bridge folder creation
and the readiness gate. A loop result prints the whole item, and every item
carries the bridge's database_password, so both printed it at -v whenever
MASK_CREDENTIALS_IN_LOGS is on. The credential lint cannot see this because
the loop variable's name carries no secret marker.

Verified: test_include_uniform_apply_keyword, the credential masking lint,
the task lints and the matrix unit tests pass, and ansible-lint is clean on
the five files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 22:50:54 +02:00
Alejandro Roman 0549c55487 fix(swarm-stalwart): fix mailu issue and other roles from CI 2026-09-11 23:56:35 +04:00
kevinveenbirkenbachandClaude Opus 5 b3ce43a596 fix(security): require no_log on every task that carries a credential
At -vvv Ansible prints each module argument and result, so a password in
a command line, a request body, a header or a fact lands in the deploy
log. Nothing enforced masking outside the MCP task files, and no check
looked at parents at all.

A new lint, test_credential_tasks_are_masked, finds tasks whose printed
parts (module arguments, environment, loop) reference an identifier ending
in password, secret, token, api_key or private_key, or
secrets.credentials. A task passes when it or an enclosing block sets
no_log, when every file that pulls it in does so through include_tasks
with apply.no_log or import_tasks with no_log (transitively), when the
secret only reaches a parameter the module masks itself, or when the
module masks its values. no_log on an include_tasks task itself does not
count, because it does not reach the included tasks. The rule can be
suppressed per task with `# nocheck: credential-masking`, now listed in
the suppression catalog, and the lint tests its parent and suppression
logic on synthetic cases.

It found 39 unmasked tasks in 19 roles, all real: join tokens, client
secrets, API tokens in Cloudflare headers, database passwords in
command lines, the ChatGPT bot login body and more. Each now sets
no_log from MASK_CREDENTIALS_IN_LOGS. The sys-dns-hetzner-rdns alias
hetzner_no_log, which only restated that switch, is gone.

Not covered yet: template and copy tasks whose rendered file carries a
secret print it as a diff under --diff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 21:52:18 +02:00
kevinveenbirkenbachandClaude Opus 5 43e09ebf0d fix(n8n): carry a rotated owner password into the account n8n already has
n8n accepts the owner password only at /rest/owner/setup, which answers
400 "Instance owner already setup" once an owner exists. After the CI
rotated owner_password between the passes, the probe got 401, setup got
400, the assert accepted "already setup", and the owner kept the old
password: the Playwright owner login failed in n8n#1 and the AI gateway
login in n8n#0 got 401 and 429 (run 34615516009, jobs 103327034581 and
103327034619).

Only in exactly that state, probe 401/403 plus "already setup", a node
script now runs inside the n8n container with n8n's own modules and
DB_POSTGRESDB_* settings: it compares the stored bcrypt hash with the
deployed password, writes a fresh bcryptjs hash when they differ, and
prints CHANGED or OK. A single confirming login then requires 200, so a
failed write stops the deploy with a clear status. Fresh hosts and deploys
whose password already matches never run the script. The pattern follows
web-app-flowise's identity_password.js.

The module path was measured in n8nio/n8n:1.100.1: bcryptjs and pg
resolve from /usr/local/lib/node_modules/n8n/node_modules, pg's
transitive modules load, and bcryptjs hashes as $2a$ like n8n.

no_log moves from the bootstrap include's apply to the four tasks that
carry the password, so the setup assert and the host and container
resolution stay readable when they fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 21:30:14 +02:00
kevinveenbirkenbachandClaude Opus 5 a20883600b fix(matrix): gate bridge health on liveness and readiness on a deploy task
304144ce67 probed each bridge's healthcheck at /_matrix/mau/ready. mautrix
sets Ready only after Synapse has pinged the appservice (connector.go
starts the listener, then ensureConnection, then AS.Ready = true), and a
swarm task with a healthcheck stays "starting" and receives no VIP traffic
until it is healthy. The ping could never arrive: Synapse logged 150
ConnectionRefused per bridge, each bridge exited FTL 13, and the stack
never converged (run 34615516009, Matrix#2, job 103327034630).

The healthcheck now probes /_matrix/mau/live, which answers once the
listener is up, before the ping. A deploy task after the Synapse wait asks
every bridge for /_matrix/mau/ready from inside Synapse, over the same
name Synapse pings, so a bridge that never becomes ready still fails the
deploy without gating swarm routing.

The ChatGPT bot is rendered only once its access token exists. Its account
and token are created by stack_host_provisioning after the first stack
deploy, so an earlier bot task failed its login with M_FORBIDDEN until the
converge wait called it stuck. The re-render after provisioning adds it
with a valid token.

Verified in a compose run of variant 2: the readiness task got 200 from
meta, signal, slack and whatsapp on the first attempt. Tests pin the live
probe and the token guard and fail against the previous template.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 21:17:27 +02:00
evangelostsak 58aa57680e docs(requirements): correct how the RBAC tier accounts get their password 2026-09-11 21:16:14 +02:00
evangelostsak 8eeb202932 docs(web-app-openbao): record the credential-backed RBAC account passwords 2026-09-11 21:16:06 +02:00
evangelostsak 6e96d45e66 fix(web-app-openbao): keep the RBAC tier accounts role-local with a stable password
The password resolves to the role's generated credential when OpenBao is
deployed and falls back to the previous random default otherwise, so no other
deployment changes.
2026-09-11 21:16:00 +02:00
evangelostsak dbb8503eeb feat(web-app-openbao): generate the RBAC tier account passwords as role credentials
A user with no explicit password inherits the strong_password fallback, which
is re-evaluated on every render with nothing persisted, so ldap_passwd and
playwright.env can receive different strings and the bind returns 403. Same
mechanism web-app-zammad uses for api_bot_password.
2026-09-11 21:15:53 +02:00
evangelostsak c21002780e Revert "test(inventory): declare the OpenBao operator and reader RBAC accounts next to biber"
The shared CI inventory is the wrong home for one role's test accounts. The
stable password they need comes from a role-generated credential instead.
2026-09-11 21:15:09 +02:00
kevinveenbirkenbachandClaude Opus 5 641f7a40dd fix(nextcloud): keep the sociallogin uid within 64 chars on onion deploys
Sociallogin creates a Nextcloud user as "<provider name>-<keycloak sub>",
and the provider name was the Keycloak domain. On an onion variant that
domain alone is 67 chars, so every OIDC callback died with
InvalidArgumentException "Username is too long" and HTTP 500 at
/apps/sociallogin/custom_oidc/auth.<onion> (run 34615516009,
Nextcloud#1, 204 occurrences). The whiteboard and admin OIDC specs failed
on the same login.

This is the second derived identifier an onion domain overflows; the
kernel hostname is the first, clamped to 63 bytes by container_hostname
since the pretix and bookwyrm fixes. The domain-or-entity rule now lives in
one lookup, bounded_name(application_id, max_length): the primary domain
when it fits, otherwise the entity name. container_hostname delegates to it
at 63 and keeps its eight callers and its behaviour.

The provider name is bounded_name('web-app-keycloak', 64 - 1 - 36), the uid
limit minus the separator and the UUID sub. A short clearnet domain stays
the name, so existing users keep their uids; an onion deploy gets
"keycloak", which, unlike a hash of the domain, survives a rotated onion
address.

Tests cover the lookup's fit, boundary and fallback cases and its argument
validation, container_hostname's delegation at 63, and the provider name's
room for the sub.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 21:14:55 +02:00
evangelostsak 86fcff42f1 docs(requirements): correct where the RBAC test accounts are declared 2026-09-11 20:55:08 +02:00
evangelostsak 7e0120bf2e docs(web-app-openbao): record why the RBAC test accounts live in the inventory 2026-09-11 20:55:00 +02:00
evangelostsak 822f143680 test(inventory): declare the OpenBao operator and reader RBAC accounts next to biber
Test accounts that something authenticates as need a literal password, which
only the inventory provides; the role-local meta/users.yml fallback is
regenerated on every render.
2026-09-11 20:54:56 +02:00
evangelostsak 484ebb31ac fix(web-app-openbao): move the RBAC test accounts to the inventory so they can log in
Declaring baooperator and baoreader in the role's meta/users.yml provisioned
them correctly into LDAP and Keycloak, but every login returned 403: a user
with no explicit password inherits "{{ 42 | strong_password }}", which is
re-evaluated through secrets.choice on each render with nothing persisted, so
the password written by ldap_passwd and the one rendered into playwright.env
were different strings.
2026-09-11 20:54:38 +02:00
kevinveenbirkenbachandClaude Opus 5 88218fc740 fix(discourse): enable OpenID Connect only after its prerequisites are set
Discourse latest now validates openid_connect_enabled and refuses it
until the discovery document, client ID and client secret are set. The
bootstrap enabled OIDC first, so a fresh database aborted the launcher
rebuild at `rails r "SiteSetting.openid_connect_enabled = true"` with
Discourse::InvalidHTMLParameters (run 34615516009, WordPress#0, job
103327034654). Existing databases hid it because the values were already
stored.

The enable step now follows the three settings it depends on. A test
reads the real template and requires every prerequisite line to precede
the enable line; against the previous template it fails.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 20:54:37 +02:00
kevinveenbirkenbachandClaude Opus 5 c7d162fa88 fix(coredns): derive the domain regex from INFINITO_DOMAIN when rendering
INFINITO_DOMAIN_RE carried an already escaped regex through the env
chain: EnvBuilder.setdefault prefers a value already in the process env,
quote_for_dotenv escapes every backslash, and load.sh unescapes it again.
One inherited value with a doubled backslash was enough to write
"infinito\\\\.test" into .env, the rendered Corefile then matched a
literal backslash, and getent hosts test.infinito.test failed with rc 2.

The renderer now substitutes DOMAIN_RE = re.escape(INFINITO_DOMAIN) at
render time, so no backslash travels through the env or .env at all. The
template placeholder is renamed because it is a derived render value, not
an env key, and INFINITO_DOMAIN_RE is removed from default.env and the
passthrough keys, leaving INFINITO_DOMAIN as the single source.

A renderer test renders the real template and requires a single-escaped
match with no doubled backslash and no leftover placeholder. Verified
live: the Corefile renders ^infinito\.test\.$, test.infinito.test
resolves to 172.30.0.10 and test.infinitoXtest does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 20:53:19 +02:00
kevinveenbirkenbachandClaude Opus 5 128e177255 fix(bluesky): notify the compose rebuild only after the backend renders the stack
The pre-compose staging (social-app clone, login-broker copy, pdsadmin
download) ran before sys-stk-backend and notified compose-build and
compose-up. On a fresh host the container runtime's first flush ("Restart
and enable docker service") then ran the compose handlers before
/usr/bin/ca-inject and compose.yml existed, and "Generate CA trust
override" failed (run 34615516009, job 103327034609).

The staging tasks now only register their result. A changed-only notifier
after the sys-stk-backend include triggers compose-build and compose-up, so
no compose topic is pending at any earlier flush, and the handler-only
include that registered the owner early is gone. The ordering test asserts
both halves: the staging files notify no compose topic, and the rebuild
notifier sits after the backend include.

Verified locally in compose, variant 1: the reinstall full cycle passed
pass 1 with failed=0 and the rebuild task changed after the backend
include, pass 2 Playwright passed, and an update run finished failed=0 with
the rebuild task skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 20:46:10 +02:00
evangelostsak c6bf74aa0f docs(requirements): close the RBAC tier criterion and relocate the seal alert 2026-09-11 18:38:11 +02:00
evangelostsak c4e6cfb490 docs(web-app-openbao): record the RBAC tier coverage and the role-local alert rules 2026-09-11 18:38:07 +02:00
evangelostsak a9afc5b45c style(web-app-openbao): drop the narrative comments from the server config template 2026-09-11 18:37:19 +02:00
evangelostsak 793b32f742 style(web-app-openbao): drop the narrative comment from the reader policy 2026-09-11 18:37:14 +02:00
evangelostsak 2f1db60f14 style(web-app-openbao): drop the narrative comment from the operator policy 2026-09-11 18:37:09 +02:00
evangelostsak 6cd99b402f style(web-app-openbao): drop the narrative comment from the administrator policy 2026-09-11 18:37:05 +02:00
evangelostsak 241b01f94a style(web-app-openbao): drop the narrative comments from 04_auth_methods.yml 2026-09-11 18:37:01 +02:00
evangelostsak 47217af7a1 style(web-app-openbao): drop the narrative comment from 03_policies.yml 2026-09-11 18:36:55 +02:00
evangelostsak cbda5724c8 style(web-app-openbao): drop the narrative comments from 01_init.yml 2026-09-11 18:36:50 +02:00
evangelostsak 71f2a0c414 style(web-app-openbao): drop the narrative comment from 00_core.yml 2026-09-11 18:36:45 +02:00
evangelostsak 4a206f6d52 style(web-app-openbao): drop the narrative comment from the group-alias helper 2026-09-11 18:36:41 +02:00
evangelostsak c2620a7087 style(web-app-openbao): drop the narrative comment from meta/server.yml 2026-09-11 18:36:31 +02:00
evangelostsak 694ee29f46 style(web-app-openbao): drop the narrative comment from the RBAC denial spec 2026-09-11 18:36:24 +02:00
evangelostsak 2149255aaa style(web-app-openbao): drop the narrative comment from the LDAP spec 2026-09-11 18:36:15 +02:00
evangelostsak ce8ad5c97e style(web-app-openbao): drop the narrative comments from the OIDC spec 2026-09-11 18:36:10 +02:00
evangelostsak ac4609d937 style(sys-svc-compose): drop the narrative comment on the namespace label 2026-09-11 18:35:59 +02:00
evangelostsak 025168e2f7 fix(test-e2e-playwright): record video for hand-built browser contexts
Cherry-picked from Alejandro Roman's 4287dee96b on infinito-nexus/core#429.
Playwright applies the project's use.video setting only to the context behind
the page fixture, so a spec that builds its own context through
browser.newContext() reaches the artefact with a trace but no video.
2026-09-11 18:35:55 +02:00
evangelostsak 3f6a454a53 test(web-app-openbao): aggregate the RBAC tier spec 2026-09-11 18:35:40 +02:00
evangelostsak 754d8bf140 test(web-app-openbao): assert every RBAC tier receives its policy and its capabilities 2026-09-11 18:35:36 +02:00