Commit Graph

69 Commits

Author SHA1 Message Date
DL6ER 20164bff26 tests: resolve iCloud Private Relay zones from local pdns
The API query-count assertions depend on resolving mask.icloud.com, whose
mask.icloud.com -> mask.apple-dns.net CNAME chain was recursed to the public
internet. dnsmasq fires extra DNSKEY validation queries depending on whether
Apple currently DNSSEC-signs icloud.com / apple-dns.net, and Apple toggles this
over time. The runtime DS-probing workaround in conftest.py could not reliably
model dnsmasq's behaviour (e.g. when Apple returns SERVFAIL on DS), so the suite
went flaky again.

Serve the icloud.com and apple-dns.net zones from the local authoritative
PowerDNS server instead, so the chain resolves hermetically and the query counts
are deterministic regardless of Apple's upstream DNSSEC posture. The DS-probing
fixture is dropped and the expected counters become fixed constants again.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-06-24 06:25:39 +00:00
Dominik 46ff6fb937 Address review comments
Signed-off-by: Dominik <dl6er@dl6er.de>
2026-05-06 19:10:21 +02:00
Dominik f99b052600 Mitigate possible race window after Teleporter test
Signed-off-by: Dominik <dl6er@dl6er.de>
2026-04-12 20:32:34 +02:00
copilot-swe-agent[bot] 959a532d6b test: move DNSSEC probe to autouse session fixture with retry/backoff
Agent-Logs-Url: https://github.com/pi-hole/FTL/sessions/f711f6f8-1f54-48c8-9d99-661803762c38

Co-authored-by: DL6ER <16748619+DL6ER@users.noreply.github.com>
2026-04-12 08:01:21 +00:00
Dominik e198fc5587 test: auto-detect upstream DNSSEC state for query-count assertions
The pytest API tests assert exact query counts that depend on whether icloud.com and apple-dns.net are DNSSEC-signed.  When Apple removed DNSSEC from those zones (April 2026), dnsmasq stopped firing two DNSKEY validation queries during the mask.icloud.com CNAME chain walk, breaking 7 tests on every CI run - including re-runs of previously green commits.

Instead of hardcoding either set of numbers, detect the current DNSSEC state at test startup by querying the local pdns_recursor (port 5555, bypassing FTL to avoid counter pollution) for DS records on both domains.  Four module-level constants (TOTAL, FORWARDED, DNSKEY, TOP_DOMAIN) are set accordingly, and the 11 affected assertions now reference these constants.

The bats "Special domain: Record is returned when explicitly allowed" test is preserved unchanged - the hybrid detection makes it safe regardless of upstream DNSSEC posture.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-04-12 07:56:23 +02:00
Dominik 4b87386d13 fix: handle strdup NULL check for cJSON reference domains in list API
Add missing NULL check after strdup() when making a writable copy of
cJSON reference strings in api_list_write(). Without this, a failed
allocation under memory pressure would dereference NULL in the
lowercasing loop. Also fix minor docstring typo in punycode test.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-04-09 21:38:44 +02:00
Dominik 9e81373ec4 fix: accept punycode domains that libidn2 rejects under IDNA2008
When adding or searching for exact domains, the API unconditionally
passes the input through idn2_to_ascii_lz() for IDN normalization.
This round-trips punycode domains: decode to Unicode, validate against
IDNA2008, re-encode to ASCII. Characters like emoji are disallowed by
IDNA2008 (RFC 5892), so valid punycode domains such as
xn--4ca0bs45142c.com (äöü😀.com) are rejected with "string contains
a disallowed character" even though they are perfectly valid DNS names.

Fix by checking whether the input is already pure ASCII before calling
idn2_to_ascii_lz(). If every byte is <= 0x7F, skip IDN conversion
entirely — the domain is already in a DNS-compatible form and only
needs lowercasing and valid_domain() validation. Non-ASCII input
(actual Unicode domains) still goes through the IDN conversion path.

Applied to both the list API (src/api/list.c) and the search API
(src/api/search.c).

Fixes: https://github.com/pi-hole/FTL/issues/2837

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-04-06 11:08:42 +02:00
Dominik 9cbdc30e8e test: rewrite auth stress tests to target thread-safety races in #2835
Replaces the generic parallel auth tests with targeted tests that
exercise the specific race conditions fixed by PR #2835:

- Set-Cookie SID mismatch from pi_hole_extra_headers global buffer race
- Corrupted session listings from unsynchronised get_all_sessions() with
  JSON_REF_STR_IN_OBJECT reading auth_data[] during concurrent deletes
- HTTP 200 with valid=false from api->session pointer going stale when
  delete_session() memsets the slot between check_client_auth() and
  get_session_object()

Uses barrier-synchronised bursts and correctness checks (not just
crash detection) so the tests fail without the fix and pass with it.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-04-05 20:55:39 +02:00
Dominik a8415ffbd7 test: add parallel authentication stress test and gitignore test/libs
Add test/api/test_s_auth_stress.py exercising the auth subsystem under
concurrent load (12 threads, kept below max_sessions=16). Sessions are
created sequentially (respecting FTL's 3-attempts/s rate limit with
retry-on-429 backoff), then concurrency targets the thread-safety
surfaces:

- Parallel session validation (concurrent GET /api/auth with SIDs)
- Concurrent logout with cross-session isolation checks (logout half,
  verify other half survives)
- Mixed session check/logout operations from a shared session pool
- Concurrent wrong-password rejection (verifies no SIGSEGV; runs last
  since it intentionally triggers rate-limiting)

Every test cleans up its sessions to avoid exhausting max_sessions
across test boundaries. Bump expected config rotation count in
test_final.bats from 14 to 16 for the stress test's password
set/remove cycle.

Also add test/libs/ to .gitignore per review feedback — the directory is
populated at test time by test/run.sh cloning bats-core.

See alse: https://github.com/pi-hole/FTL/pull/2835

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-04-05 20:20:53 +02:00
Dominik d89ddf920f Merge branch 'development' into new/pytests 2026-04-04 06:36:37 +02:00
Mohammed Alzahrani de7f2673ed Block Teleporter import for CLI sessions 2026-04-03 13:05:13 +03:00
Dominik cc33fbc545 test: add PUT/DELETE, batch delete, query filter, and auth tests
Add ~80 new pytest API integration tests covering previously untested
endpoints and HTTP methods:

- DELETE 204/404 for groups, domains, clients, lists, config array
  items, network devices, and info messages
- PUT create/replace round-trips for groups, domains, clients, lists
- PUT error cases (missing body, invalid domain type)
- Batch delete (POST :batchDelete) for groups, domains, clients, lists
- DNS blocking toggle (POST disable + re-enable)
- Auth session logout (DELETE /api/auth) and delete-by-ID
- GET endpoints: clients, config, network gateway/routes, info
  host/sensors/metrics, query filters (domain, client_ip, upstream,
  blocklist pseudo-upstream), query suggestions, query cursor
  pagination, stats database with time ranges, history database
  with time ranges
- Search with partial matching
- TOTP credential suggestion (GET /api/auth/totp)
- Config PATCH round-trip (bool and integer, change + verify + restore)
- NTP server protocol-level test (UDP NTPv4 request/response)
- Update test_final.bats known-warning patterns and config write counts

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-30 21:26:16 +02:00
Dominik d3e724e51d fix: correct auth_method in test error messages, fix stale comment
test_openapi.py: Store auth_method per endpoint alongside errors so
the assertion message shows the correct auth method for each failing
endpoint instead of the last loop iteration's value.

run.sh: Update comment to reflect that BATS no longer terminates FTL
(termination was moved to test_final.bats).

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-29 21:14:22 +02:00
Dominik bb89fbfe7b test: add comprehensive API endpoint tests, fix double-free in printFTLenv
Add pytest tests for all previously untested GET API endpoints:
dns/blocking, domains (all type/kind combinations and single lookup),
groups, stats/summary, stats/top_domains, stats/top_clients,
stats/upstreams, stats/query_types, stats/recent_blocked,
stats/database (error handling), dhcp/leases, endpoints, info/ftl,
info/login, info/version, info/messages, info/client, info/database,
info/system, network/devices, network/interfaces, logs (dnsmasq, ftl,
webserver), and padd.

All assertions use exact expected values derived from the deterministic
BATS DNS query seeding (137 total queries, 49 blocked, 47 forwarded,
41 cached, 11 active clients, 8 gravity domains).  On failure, the
full JSON response is dumped to /tmp/ftl_test_*.json for easy
inspection.

Fix double-free bug in printFTLenv() (src/config/env.c): when
printFTLenv() was called more than once (e.g. after config reload
triggered by the CLI password test), it would free item->error a
second time because neither the pointer nor the error_allocated flag
were reset after the first free.  This produced "Trying to free NULL
pointer in printFTLenv()" warnings.  Fix: set item->error = NULL and
item->error_allocated = false after freeing.

Files modified:
  src/config/env.c      — reset error/error_allocated after free
  test/api/test_api.py  — add 34 new endpoint tests (22 -> 56 total)

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-25 13:29:17 +01:00
Dominik e189c8e823 test: add pytest API test files and final BATS validation suite
Add the pytest test files and final BATS suite that were described
in the previous commit but not yet included.

New files:
  test/api/conftest.py     — shared fixtures (api_session, openapi, ftl)
  test/api/pytest.ini      — pytest configuration
  test/api/test_api.py     — HTTP errors, config validation, search,
                              history, lists, queries, Lua pages
  test/api/test_openapi.py — OpenAPI spec validation, teleporter
  test/api/test_z_auth.py  — auth workflow (app password, login,
                              rate limiting, password removal)
  test/test_final.bats     — log validation, config rotation counts,
                              FTL termination

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-25 11:28:03 +01:00
Dominik 1f9cb007cd Port API tests from BATS to pytest and fix ResponseVerifyer
Move all API-related tests (HTTP endpoints, config validation, auth,
search, history, lists, Lua pages, OpenAPI spec validation) from BATS
shell tests to pytest. BATS retains DNS, regex, CLI, and system-level
tests. The auth test suite now removes the password at the end, leaving
no net state change.

Fix ResponseVerifyer to respect the OpenAPI "required" field: optional
properties absent from FTL's response are silently skipped instead of
flagged as errors. The required list is propagated through all recursive
calls (top-level objects, allOf, nested objects, array items).

Add a set_config() helper that changes FTL configuration via the API
instead of shelling out to the CLI. The API PATCH is synchronous, so no
log polling or sleeps are needed.

Add build.sh test-api target to run only the pytest API tests (skips
BATS and perf tests).

Test migration mapping (25 BATS tests removed, all covered in pytest):

| # | Removed BATS test | Pytest counterpart |
|---|---|---|
| 1 | HTTP server responds with JSON error 404 to unknown API path | TestHTTPErrors::test_api_404_returns_json |
| 2 | HTTP server responds with error 404 to path outside /admin | TestHTTPErrors::test_non_admin_path_returns_404 |
| 3 | Config validation working on the API (type-based checking) | TestConfigValidationAPIType (2 tests) |
| 4 | Config validation working on the API (validator-based checking) | TestConfigValidationAPIValidator (4 tests) |
| 5 | Changing a config option set forced by ENVVAR is not possible via the API | TestEnvvarProtectedConfig::test_api_rejects_envvar_override |
| 6 | API domain search: Non-existing domain | TestDomainSearch::test_nonexistent_domain |
| 7 | API domain search: antigravity.ftl | TestDomainSearch::test_antigravity_domain |
| 8 | API domain search: Internationalized/partially capital domain | TestDomainSearch::test_punycode_normalization |
| 9 | API history: Returns full 24 hours | TestHistory::test_history_returns_24h |
| 10 | API history/clients: Returns full 24 hours | TestHistory::test_history_clients_returns_24h |
| 11 | Check /api/lists?type=block | TestLists::test_block_lists_only |
| 12 | Check /api/lists?type=allow | TestLists::test_allow_lists_only |
| 13 | Check /api/lists without type parameter | TestLists::test_all_lists_includes_both_types |
| 14 | API: No UNKNOWN reply in API | TestQueries::test_no_unknown_reply |
| 15 | API: No UNKNOWN status in API | TestQueries::test_no_unknown_status |
| 16 | Lua server page outside /admin is not served by default | TestLuaServerPages::test_lua_page_outside_admin_not_served_by_default |
| 17 | Lua server page is generating proper backtrace | TestLuaServerPages::test_lua_page_generates_proper_backtrace |
| 18 | Lua server page outside of webhome is served without login | TestLuaServerPages::test_lua_page_outside_webhome_served_without_login |
| 19 | API validation (checkAPI.py) | TestEndpointCoverage (3 tests) + TestEndpointResponses + TestTeleporter |
| 20 | API authorization (without password): No login required | TestAuthWorkflow::test_01_no_password_means_session_valid |
| 21 | Create, set, and use application password | TestAuthWorkflow::test_02 + test_03 + test_04 |
| 22 | CLI password file is as expected | TestAuthWorkflow::test_04b_cli_password_file |
| 23 | API authorization: Setting password | TestAuthWorkflow::test_05_set_password |
| 24 | API authorization (with password): Incorrect password is rejected | TestAuthWorkflow::test_06_incorrect_password_rejected |
| 25 | API authorization (with password): Correct password is accepted | TestAuthWorkflow::test_07_correct_password_accepted |

New tests without a BATS predecessor:
- TestAuthWorkflow::test_08_rate_limiting_enforced
- TestAuthWorkflow::test_09_remove_password
- TestAuthWorkflow::test_10_no_password_after_removal

Final count: 192 BATS + 38 pytest = 230 total (was 216 BATS). No tests
lost, 14 net new.

Signed-off-by: Dominik <dl6er@dl6er.de>
2026-03-25 08:51:04 +01:00
Dominik 868733abb4 Check for allOf definitions in an array defining arrays of objects where all components must be checked. Also remove a bit pointless comparison between YAML examples and FTL output. We already validate the YAML examples against the YAML schema which is enough. This avoids false-positives when FTL happens to return a different number of items than we have examples (which is perfectly fine)
Signed-off-by: Dominik <dl6er@dl6er.de>
2025-12-14 18:47:45 +01:00
DL6ER 962fab86df Really skip on RICV64
Signed-off-by: DL6ER <dl6er@dl6er.de>
2025-06-02 19:57:02 +02:00
DL6ER 2fc6a64242 Skip API verification on RISCV64
Signed-off-by: DL6ER <dl6er@dl6er.de>
2025-06-01 07:54:14 +02:00
DL6ER 5d3972a54d Add ability to trace API validator script (off by default)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2025-06-01 07:53:41 +02:00
DL6ER dd39c5eb0c Expose EDE information via API if available
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-12-18 10:58:44 +01:00
DL6ER 35166fd00c Fix a small bug in the API response verifier and ensure we always favor using 64 bit interface statistics if available. The reason is that the legacy statistics use 32 bit conters which overflow every 4 GB of interface traffic
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-07-14 15:45:29 +02:00
DL6ER f2a7662e95 Be more verbose in which tables are imported during teleporter importing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-02 21:59:45 +02:00
DL6ER e3c4dcf215 Restructure API response from /history/clients and /history/database/clients to allow for sparse data.
Add new config option webserver.api.client_history_global_max controling if the activities chart should sort and show the *global* (integrated over 24 hours) or the `local` (measured individually in each time slot) most active clients
Allow setting webserver.api.maxClients to 0 to always return all clients in /api/history/clients

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-16 17:15:20 +01:00
Dominik 933e6f605f Merge pull request #1728 from pi-hole/tweak/query_auth
Add authentication via query string
2024-01-07 07:50:58 +01:00
DL6ER 2dbb7f3b63 Merge pull request #1731 from pi-hole/fix/dhcp-range
Improve DHCP handling
2023-11-16 22:25:09 +01:00
DL6ER 503c0538ed IPv4 address 0.0.0.0 and IPv6 address :: correspond to empty strings in FTL settings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-16 12:43:47 +01:00
DL6ER a69130585e Use package ipaddress for IP address validation in API tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-15 11:49:03 +01:00
DL6ER 90de6c1b83 Merge pull request #1727 from pi-hole/fix/toml_utf8
Declare pihole.toml as UTF-8 document
2023-11-14 22:25:04 +01:00
DL6ER 11127f0f13 Fix OpenAPI checker not being able to discover properties that are returned by FTL but not documented in the OpenAPI specs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 17:11:37 +01:00
DL6ER 75cd372d0e Add string format verification in API checker
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 12:38:24 +01:00
DL6ER a96c283c0c Add authentication via query string
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-03 19:41:08 +01:00
DL6ER f1c59db0f6 Declare pihole.toml as UTF-8 document. we add a compile-time switch to use ASCII with UTF-8 escaping instead in case this is a necessity for anyone (I don't really expect this in the third millenial but we also know people are still using Windows XP on the web...)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-03 10:12:07 +01:00
DL6ER d805bd75f4 Update test/api/checkAPI.py
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-11-03 06:47:32 +01:00
DL6ER 15ae21e39c Clarify error wording
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-02 05:55:27 +01:00
DL6ER 3a67a77a08 Report number of checked endpoints in the result and warn if the number of specified endpoints in FTL and the OpenAPI specs do not match
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-02 05:50:32 +01:00
DL6ER 5993b66bca Add checking of all endpoints defined in FTL but not in the OpenAPI specs and vice versa
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-02 05:28:07 +01:00
DL6ER f4e6dfc061 Implement deep-recursion of API arrays
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-30 07:42:51 +01:00
DL6ER 2141db3d64 Add rate-limiting on password login attempts
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 19:59:32 +02:00
Christian König da544dcbb2 Change default webport to 80
Signed-off-by: Christian König <ckoenig@posteo.de>
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-08-01 15:54:20 +02:00
DL6ER 813509841b Accept cookie authentication only when CSRF header is provided (and correct)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-04 19:29:54 +02:00
Christian König 62cfc25b95 Fix spelling in v6
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-05-30 22:42:13 +02:00
DL6ER 19c72d354e !!! BREAKING CHANGE !!! Switch to the proven memory-hard password-hashing alogorithm BALLOON. The stored password hash will be upgraded on the first successdful login. To wave the necessity to implement BALLOON with every client trying to access the API, we remove the existing challenge-response authentication in favor of allowing login straight with the password. This has been avoided in the past, however, seems now acceptable that FTL (even by default) offers secure end-to-end encryption over HTTPS.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-30 21:22:45 +02:00
DL6ER 149ec4e0dd Add test for re-importing the just exported Teleporter file during the tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-26 20:11:55 +01:00
DL6ER 48fc06d46b Add POST /api/teleporter to upload and install backed up configuration
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-25 21:51:12 +01:00
DL6ER e4383775d1 Add /api/action/gravity which can be used to trigger a run of pihole -g. The output is live streamed using HTTP/1.1 chunked encoding.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-24 20:15:45 +01:00
DL6ER 13168c377b Add GET /api/teleporter
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-23 21:56:11 +01:00
DL6ER 88e8ab9fd5 !!! BREAKING CHANGE !!! Redesign TOML config structure
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 12:32:10 +01:00
DL6ER 47ac129a53 !!! BREAKING CHANGE !!! Rename pihole-FTL.toml to pihole.toml and it is a Pi-hole wide config file also covering all the dnsmasq settings, etc.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 10:01:54 +01:00
DL6ER 2d6c25d573 Add GET /config/{element} for more specific requests as well as PATCH and DELETE /config/{element}/{value} for direct array manipulation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 18:19:19 +01:00