Commit Graph
21 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 eda6b25329 fix(pihole): pass the admin password through a dotenv-quoted env file and judge the denial before the callback URL
Run 34421944328 pihole#1 (README replay, sso off) never left
/admin/login: the native login was refused. The compose template put
the administrator password inline under environment:, and compose
interpolates every $name in it, so the container held the inventory
password with each $name removed (its sha256 prefix matches that
mangled value, not the real one). The environment moves into
templates/env.j2, which has_env wires as env_file, with the password
through dotenv_quote like every other role. Rendering the real template
with a password containing $name, ${...} and $$ and reading it back from a
compose v5.5.1 container returns it unchanged, while an unquoted RAW=S$x
control line in the same file comes back as "S". TZ now comes from the
shared sys-svc-container include, so PIHOLE_TIMEZONE goes.

pihole#0 (onion) timed out on "still handing off": oauth2-proxy serves
its 403 page for a user outside the allowed group on /oauth2/callback,
and the poll checked the callback URL before the page text. The CI
trace holds "You do not have permission to access this resource." in
that page's collapsed More Info block, which textContent reads. The
poll now judges the denial text first, then the Keycloak auth URL, and
only then treats the callback URL as a handoff in progress. The loose
"403" and "forbidden" matches go, since a reached admin page can
contain either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 13:47:22 +02:00
kevinveenbirkenbachandClaude Opus 5 b95d9a2f54 fix(pihole): make the login and denial checks able to fail
Three assertions in these specs passed whether or not the thing they name
happened.

The native login test navigates to `/admin/login` and then asserted that the URL
contains `/admin`, which it already did before anyone typed a password, next to
a check that `body` is visible, which is true the moment the document parses. A
failed login scored green. It now waits for the URL to leave `/login` and for
the password field it just filled to be gone.

The SSO login test asserted `page.url()` once, immediately after a settle whose
result is discarded, so whether it saw the finished redirect was luck. Its two
sibling tests in the same file poll for the Pi-hole base URL and wait out
`/oauth2/callback` first; this one never did. It does now, and it also rejects
being parked on the callback, which nothing checked before.

The denial test counted `/oauth2/callback` and the auth endpoint as evidence of
denial. Both appear mid-handoff on a login that is about to SUCCEED, so an
authorization regression could pass here unnoticed. The check becomes a poll
over three distinct states: still handing off keeps polling, back at the auth
endpoint or a forbidden body is denial, anything else fails and reports where
the browser actually landed. A transient no longer reads as a pass.

The obvious form of that fix — requiring the URL to leave `/oauth2/callback`
before judging — is avoided on purpose: oauth2-proxy may render its 403 on the
callback path itself, and the test would then time out on a correct denial.
Judging the state rather than the path does not care.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 01:14:55 +02:00
kevinveenbirkenbachandClaude Opus 5 575a5a87ac refactor(layout): sort shipped scripts by language and split the unit suite
Every script a role ships now lives under `files/<language>/`, and the unit
suite is split the same way: `tests/unit/python/` beside new sibling trees for
the other languages. `tests/lint/filesystem/test_role_files_layout.py` keeps it
that way; directories whose shape is dictated from outside carry a `.nocheck`
naming the constraint (Docker build contexts, WordPress mu-plugins, the Joomla
extension, Odoo addons, Playwright suites, the `files/test/test.sh` harness
that `test-e2e-cli` resolves generically).

This cannot be applied in halves. Moving a file and repointing what names it
are the same change: 14 references resolve only at deploy time, so the tree is
green either before or after, never between.

Those 14 were found by static resolution over every reference class, not by the
test suite -- a full `make quality-high` was green while all of them were
broken:

  * 7 `src:` values built from a role variable holding a bare filename
    (svc-bkp-remote-2-local, decidim, espocrm, friendica, joomla, pixelfed x2).
    The variable doubles as the destination name inside the container, so the
    language segment belongs at the call site, not in the variable.
  * `sys-service/tasks/04_base.yml` resolved every consumer's systemd script
    through `first_found(..., errors='strict')` over `files/script.{sh,py}`.
    18 roles ship exactly that and have no template fallback: the play would
    have died on each.
  * `sys-front-inj-javascript` fell back to `<role>/files/javascript.js`; the
    interface now reads `files/javascript/javascript.js`.
  * `sys-front-inj-logout/templates/head_sub.j2` built the cache-busting mtime
    path through `path_absolute(... 'files', ...)`.
  * 4 `ansible.builtin.script:` invocations naming a bare command
    (pull.py, prune.sh, set_postgres_superuser_password.sh, ganesha-serve.sh).
  * `dev-python/files/shell/install.sh` pointed at a sibling role's script.

Depth-dependent paths inside the moved trees were bumped accordingly: 59
`parents[N]` computations, 12 relative climbs, and the per-role recover CLI,
whose path `cli/administration/recover/recoverers.py` still assembled from the
pre-move location.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 01:31:09 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4c247b61ec test(e2e): give every standalone Playwright request an onion-aware timeout
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 04:45:03 +02:00
kevinveenbirkenbachandClaude Opus 4.8 07c6ad8e7c test(e2e): scale every nav click + timeout literal for onion targets
Over Tor the redirect chain behind a login/logout/OIDC click routinely exceeds Playwright's fixed 30s action timeout, so the click throws before the flow's scaled waits run. Sweep all role Playwright suites and the shared persona helpers: give every navigation/auth-transition click an onion-scaled timeout (.click({ timeout: resolveTimeout(30_000) })) and route the remaining raw millisecond literals (timeout options, waitForTimeout, sleep-promise setTimeout, positional wait args) through resolveTimeout. Local UI clicks (menus, toggles, modal dismissals, DOM clicks inside evaluate) stay untouched.

Two new lints keep it that way: test_no_raw_timeout_literals forbids raw numeric timeout literals in Playwright specs (opt-out: nocheck: raw-timeout), test_persona_clicks_scaled requires every click in the shared persona auth flows to carry a resolveTimeout timeout (opt-out: nocheck: unscaled-click).

Verified: node --check on all 133 changed JS files, all 13 playwright lints green, make autoformat clean, make test green (2745+196+163+4 tests OK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 11:35:53 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4c91df5de7 test(e2e): forbid raw page.goto, migrate all specs to Tor-resilient gotoOnion
New lint test_no_raw_goto.py forbids raw `page.goto(...)` in role playwright specs + persona flows (the gotoOnion wrapper in personas/utils is exempt) so navigation retries transient Tor-transport errors over onion. Migrates every raw call across ~75 roles (343 sites) to `gotoOnion(page, ...)` and teaches gotoOnion to detect relative onion URLs (`isOnionCanonical`). gotoOnion is a behaviour-identical drop-in on clearnet (single attempt, same return). Verified: no-raw-goto lint green, eslint 0 errors, make test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:59:38 +02:00
kevinveenbirkenbachandClaude Opus 4.8 35dee215c8 test(playwright): route all timeouts through resolveTimeout for onion-compat
Add the global TIMEOUT_FACTOR and a shared timeouts.js helper: resolveTimeout(base) scales by TIMEOUT_FACTOR and, over a .onion target, an extra multiplier, so Tor's per-request latency no longer trips hard-coded timeouts. Migrate every raw timeout in the specs (timeout:/timeout=/setTimeout/waitForTimeout) to resolveTimeout, stage the helper in all three staging paths, and add test_no_raw_timeouts to forbid raw numeric timeouts. Clearnet behaviour is unchanged (factor 1: resolveTimeout(N) === N).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:38:06 +02:00
kevinveenbirkenbachandClaude Opus 4.8 e986fe5d9e refactor(test-e2e): route HSTS assertions through the shared TLS-gated helper
HSTS (Strict-Transport-Security) is an HTTPS-only header: a browser ignores it
over plain http (RFC 6797) and OpenResty only emits it on the TLS vhost. On an
onion node TLS is off and every domain is served over http, so the inline
`expect(headers["strict-transport-security"]).toBeTruthy()` in 33 specs failed
with `Received: undefined`.

Route all 33 through the shared expectHstsWhenTls(headers, baseUrl, label)
helper (personas/utils/tls.js), which requires HSTS over https and asserts its
absence over http. On a TLS node the base URL is https, so the assertion is
identical to before; on an onion node the same spec now passes. HSTS logic
lives in one place (SPOT) instead of 34 inline copies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 03:26:03 +02:00
Prageeth Panicker 0647229afc fix(pihole): Pi-hole v6 compatibility fixes for login and password setup
- native.spec.js: update button selector from /sign in|login/i to
  /log in|sign in/i — Pi-hole v6's submit button text is
  'Log in (uses cookie)', not 'Sign in' or 'Login'

- 01_setup.yml: remove pihole setpassword CLI task — Pi-hole v6 blocks
  this command when FTLCONF_webserver_api_password is set via env var
  ('set by environment variable; please unset it to use this function').
  Password is now set at container startup via the env var, which is
  the v6-idiomatic approach.
2026-06-23 13:19:25 +00:00
Prageeth Panicker dfcfd0ef58 fix(pihole): variant-1 test failures and Pi-hole v6 compatibility
Three fixes:

1. guest.spec.js: skip instead of fail when OIDC_ISSUER_URL is unset
   (variant-1 has SSO disabled so no Keycloak redirect is expected;
   the old expect().toBeTruthy() caused a hard failure instead of skip)

2. playwright.env.j2: only emit OIDC_ISSUER_URL when SSO is proxy-gated
   (was unconditionally set, causing native.spec.js to wrongly skip in
   variant-1 and oauth2.spec.js/guest.spec.js to fail on missing redirect)

3. compose.yml.j2: Pi-hole v6 compatibility fixes
   - Replace WEBPASSWORD with FTLCONF_webserver_api_password (v6 dropped
     legacy env vars; WEBPASSWORD is silently ignored in v6)
   - Add FTLCONF_webserver_domain={{ domain }} to whitelist the nginx
     proxy Host header; Pi-hole v6 FTL returns 403 for unrecognised Host
     headers (DNS rebinding protection) — this was causing the baseline
     playwright.spec.js to fail with HTTP 403 in variant-1
2026-06-23 13:19:24 +00:00
Prageeth Panicker 22302cb373 fix(pihole): rename persona Playwright specs to *.spec.js so they actually run
Playwright's default testMatch only discovers files matching
*.spec.js / *.test.js. test-guest.js, test-oauth2.js, and
test-native.js never matched this pattern and were silently never
executed in any CI variant -- only playwright.spec.js's 2 baseline
tests ever ran (confirmed: 0 occurrences of these files in any
playwright-junit.xml across variant-0/variant-1, sync/async).

Renamed to guest.spec.js, oauth2.spec.js, native.spec.js so the SSO
persona scenarios (Keycloak redirect, admin/biber login, native login)
actually execute and validate the sso-proxy CA trust fix.
2026-06-23 13:19:24 +00:00
Prageeth Panicker 20f5fb8e64 fix(pihole): address review feedback (Kevin)
- playwright.spec.js: remove duplicated SSO/persona scenarios now
  covered by test-guest.js/test-oauth2.js/test-native.js. Reduce to
  baseline checks (front page + TLS/HSTS, HTML content-type), matching
  the pgadmin pattern.
- meta/main.yml: rewrite description to describe Pi-hole the software
  (network-wide DNS sinkhole, DNS/DHCP management, web dashboard)
  rather than the role/integration details.
2026-06-23 13:19:22 +00:00
Prageeth Panicker f002b0ce61 fix(web-app-pihole): fix nested template literal syntax error in playwright spec 2026-06-23 13:19:20 +00:00
Prageeth Panicker d60d1b9ded fix(web-app-pihole): fix string concatenation lint error in playwright spec 2026-06-23 13:19:19 +00:00
Prageeth Panicker 7dfd416d94 fix(web-app-pihole): address reviewer feedback - SSO/native password, split tests, remove schema 2026-06-23 13:19:18 +00:00
Prageeth Panicker 6e7f847bc8 fix(web-app-pihole): fix dynamic flags, port, personas, block size, docker exec, README 2026-06-23 13:19:17 +00:00
Prageeth Panicker fe6ca57c81 fix(web-app-pihole): resolve CI lint failures and test compliance 2026-06-23 13:19:17 +00:00
Prageeth Panicker b837d26739 fix(web-app-pihole): fix oauth2-proxy deployment and javascript injection
- Fix oauth2.enabled: replace Jinja2 group_names expression with literal true
- Set oauth2.shared: false to deploy role-local oauth2-proxy instance
- Add allowed_groups for RBAC administrator access control
- Add javascript service for logout button injection
- Remove variant 1 (standalone mode) - pihole always requires Keycloak
- Delete compose.override.yml.j2 (--ssl-insecure-skip-verify no longer needed)
- Fix javascript.js.j2: wrap redirect in DOMContentLoaded to fix DOM timing
- Add TEST_E2E_PLAYWRIGHT_WAIT_ENABLED: false for oauth2-protected app
2026-06-23 13:19:16 +00:00
Prageeth Panicker 1d58e10648 fix(web-app-pihole): fix playwright logout test, remove duplicate oauth2-proxy block, add playwright wait flag
- Update logout test to navigate directly to oauth2/sign_out endpoint with Keycloak logout redirect
- Remove duplicate oauth2-proxy service definition from compose.yml.j2 (was causing duplicate key error)
- Add TEST_E2E_PLAYWRIGHT_WAIT_ENABLED: false to vars/main.yml (oauth2-proxy returns 403 before redirect)
2026-06-23 13:19:11 +00:00
Prageeth Panicker 9718c83eb5 fix(web-app-pihole): add variants, javascript injection, fix oauth2 DNS, update playwright tests 2026-06-23 13:19:09 +00:00
Prageeth Panicker 02bcc94320 feat(web-app-pihole): add Pi-hole role with OAuth2/Keycloak RBAC
- Deploy Pi-hole as web app with NGINX proxy and OAuth2 protection
- Only web-app-pihole-administrators group can access the dashboard
- DNS port 53 (TCP/UDP) exposed for network-wide ad blocking
- Upstream DNS configurable per inventory (default: Cloudflare 1.1.1.1)
- Redis session store for OAuth2 proxy
- Timezone from HOST_TIMEZONE platform variable
- Port bindings use DOCKER_BIND_HOST platform variable
- Register subnet 192.168.102.240/28 in networks
- Register ports 8070 (http) and 16493 (oauth2) in ports
- Playwright tests: oauth2 protection, admin login, biber denied (3/3 passing)

Known limitations:
- Pi-hole v6 gravity blocklist requires manual setup via admin UI
- Internal *.infinito.example DNS forwarding to CoreDNS planned as follow-up
2026-06-23 13:18:51 +00:00