diff --git a/packages/pihole/.gitignore b/packages/pihole/.gitignore new file mode 100644 index 00000000000..4107a3a391b --- /dev/null +++ b/packages/pihole/.gitignore @@ -0,0 +1,2 @@ +# Claude AI context file +claude.md diff --git a/packages/pihole/LICENSE.txt b/packages/pihole/LICENSE.txt new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/packages/pihole/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/pihole/_dev/build/docs/README.md b/packages/pihole/_dev/build/docs/README.md new file mode 100644 index 00000000000..1b046fb2479 --- /dev/null +++ b/packages/pihole/_dev/build/docs/README.md @@ -0,0 +1,255 @@ +{{- generatedHeader }} + +# Pi-hole Integration + +The Pi-hole Integration allows you to monitor DNS query activity from your Pi-hole instances. Pi-hole is a network-level DNS filtering application that blocks ads and trackers. + +Use the Pi-hole Integration to collect DNS query logs from your Pi-hole instances. Then visualize that data in Kibana, create alerts to notify you of DNS issues or suspicious activity, and analyze network traffic patterns. + +## Data streams + +The Pi-hole Integration collects both logs and metrics that provide insights into DNS activity on your network: + +### Logs +- **DNS Queries**: Individual DNS query records with query type, resolution status, client information, upstream server details, and DNSSEC status + +### Metrics +- **Query History**: Time-series metrics showing DNS query volume over time, broken down by total queries, cached responses, blocked queries, and forwarded queries +- **Pi-hole Summary**: Comprehensive statistics including total queries, blocking effectiveness, query type breakdowns (A, AAAA, PTR, etc.), query status breakdowns (cache hits, forwarded, blocked, etc.), and query reply types +- **Top Clients**: Metrics identifying the most active DNS clients on your network, with separate tracking for allowed and blocked queries +- **Top Domains**: Metrics showing the most frequently queried domains, with separate tracking for allowed and blocked domains + +## Requirements + +You need Elasticsearch for storing and searching your data and Kibana for visualizing and managing it. +You can use our hosted Elasticsearch Service on Elastic Cloud, which is recommended, or self-manage the Elastic Stack on your own hardware. + +### Pi-hole Requirements + +- Pi-hole instance (v6.0 or later recommended) with API access enabled +- Admin panel password for authentication +- Network connectivity from the Elastic Agent to the Pi-hole instance +- Pi-hole API endpoint accessible at `http(s):///api/` + +## Setup + +For step-by-step instructions on how to set up an integration, see the +[Getting started](https://www.elastic.co/guide/en/welcome-to-elastic/current/getting-started-observability.html) guide. + +### Pi-hole API Authentication + +This integration uses Pi-hole's session-based authentication mechanism: + +1. The integration POSTs the admin password to `/api/auth` to obtain a session ID (SID) +2. The SID is included in the `X-FTL-SID` header for all subsequent API requests +3. After data collection completes, the session is terminated with a DELETE request to `/api/auth` + +Each collection cycle obtains a fresh session ID and terminates it after use, preventing authentication errors from session expiration. + +### Configuration + +**Required Fields:** +- **Pi-hole URL**: The URL of your Pi-hole instance (e.g., `http://192.168.1.1` or `https://pihole.example.com`) +- **API Password**: Your Pi-hole admin panel password + +**Optional Fields:** +- **Collection Interval**: How often to collect DNS queries (default: 60s). For high-volume networks (>400 queries/minute), consider reducing to 10-15 seconds to avoid missing queries. +- **Proxy URL**: Optional HTTP proxy for connecting to Pi-hole +- **SSL Configuration**: Custom SSL/TLS settings for HTTPS connections +- **HTTP Client Timeout**: Request timeout duration (default: 30s) + +### Data Collection Strategy + +**DNS Queries (Logs):** +The integration uses timestamp-based pagination to avoid duplicate records: +- On the first run, it collects up to 1000 most recent queries +- On subsequent runs, it fetches only queries newer than the last collection using the `from` parameter +- The timestamp cursor persists across agent restarts and integration upgrades +- Maximum of 1000 queries per collection interval (adjust interval if you exceed this limit) + +**Metrics Data Streams:** +Query History, Pi-hole Summary, Top Clients, and Top Domains collect point-in-time snapshots at each collection interval: +- Default collection interval: 5 minutes (configurable per data stream) +- No pagination required - each collection captures the current state +- Query History provides 10-minute interval buckets for time-series analysis +- Top Clients and Top Domains each collect top 10 allowed and top 10 blocked items per collection + +## Logs reference + +### DNS Queries + +The `dns_queries` data stream collects individual DNS query records from Pi-hole with detailed information about each query. + +**Collected Information:** +- DNS query details (domain, query type, response code) +- Client information (IP address, hostname) +- Upstream DNS server +- Query status (cached, forwarded, blocked, etc.) +- DNSSEC validation status +- Reply time in seconds +- Extended DNS Error (EDE) codes and text +- CNAME records (if applicable) +- Gravity list ID (for blocked queries) + +#### Example Event + +{{ event "dns_queries" }} + +#### ECS Field Mappings + +The integration maps Pi-hole data to the Elastic Common Schema (ECS): + +| Pi-hole Field | ECS Field | Description | +|--------------|-----------|-------------| +| `domain` | `dns.question.name` | The queried domain name | +| `type` | `dns.question.type` | DNS record type (A, AAAA, CNAME, etc.) | +| `reply_type` | `dns.response_code` | DNS response code | +| `client_ip` | `source.ip` | Client IP address | +| `client_name` | `source.domain` | Client hostname | +| `upstream_name` | `destination.ip` | Upstream DNS server | +| `time` | `@timestamp` | Query timestamp | + +#### Exported fields + +{{ fields "dns_queries" }} + +## Metrics reference + +### Query History + +The `query_history` data stream collects time-series metrics showing DNS query patterns over time with 10-minute intervals. + +**Collected Information:** +- Total DNS queries in the interval +- Queries answered from cache +- Blocked queries +- Queries forwarded to upstream DNS servers + +This data stream is ideal for tracking DNS traffic patterns, identifying peak usage times, and monitoring the effectiveness of Pi-hole's caching and blocking mechanisms. + +#### Example Event + +{{ event "query_history" }} + +#### Exported fields + +{{ fields "query_history" }} + +### Pi-hole Summary + +The `pihole_summary` data stream collects comprehensive statistics about Pi-hole's overall performance and activity. + +**Collected Information:** +- Total queries and blocking statistics +- Query frequency (queries per minute) +- Unique domains and clients +- Gravity list information and last update timestamp +- Query type breakdown (A, AAAA, PTR, TXT, MX, HTTPS, SVCB, and 10+ other record types) +- Query status breakdown (cache hits, forwarded, blocked by gravity/regex, special domains, stale cache, and 10+ other statuses) +- Query reply type breakdown (NODATA, NXDOMAIN, CNAME, IP, SERVFAIL, and 10+ other reply types) + +This data stream provides a complete picture of Pi-hole's DNS filtering and caching behavior, making it invaluable for capacity planning, troubleshooting, and security monitoring. + +#### Example Event + +{{ event "pihole_summary" }} + +#### Exported fields + +{{ fields "pihole_summary" }} + +### Top Clients + +The `top_clients` data stream identifies the most active DNS clients on your network. + +**Collected Information:** +- Client IP address and hostname +- Number of queries from each client +- Query action (allowed or blocked) +- Total queries and blocked queries across all clients + +Each collection gathers the top 10 clients for allowed queries and top 10 clients for blocked queries, providing visibility into both normal DNS usage patterns and potential security issues or misconfigured devices. + +#### Example Event + +{{ event "top_clients" }} + +#### Exported fields + +{{ fields "top_clients" }} + +### Top Domains + +The `top_domains` data stream shows the most frequently queried domains. + +**Collected Information:** +- Domain name +- Number of queries for the domain +- Query action (allowed or blocked) +- Total queries and blocked queries across all domains + +Each collection gathers the top 10 allowed domains and top 10 blocked domains, helping identify popular services, potential data exfiltration attempts, and the effectiveness of blocklists. + +#### Example Event + +{{ event "top_domains" }} + +#### Exported fields + +{{ fields "top_domains" }} + +## Troubleshooting + +### Common Issues + +**No data appearing in Elasticsearch:** +- Verify Pi-hole URL is correct and accessible from the Elastic Agent +- Check that the API password is correct +- Enable request tracing (`enable_request_tracer: true`) and check logs at `logs/cel/http-request-trace-*.ndjson` + +**Missing queries (less than expected):** +- If you have high query volume (>400 queries/minute), reduce the collection interval to 10-15 seconds +- Check Elastic Agent logs for errors or timeouts + +**Authentication errors:** +- Verify the admin password is correct +- Check Pi-hole API is accessible at `/api/auth` +- Ensure Pi-hole version supports session-based authentication (v6.0+) + +For help with Elastic ingest tools, check [Common problems](https://www.elastic.co/docs/troubleshoot/ingest/fleet/common-problems). + +## Scaling + +For high-volume networks with multiple Pi-hole instances: +- Deploy separate Elastic Agent instances for each Pi-hole +- Use namespace separation to distinguish between different Pi-hole instances +- Consider reducing collection interval for high-traffic instances + +For more information on architectures that can be used for scaling this integration, check the [Ingest Architectures](https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures) documentation. + +## Reference + +### Inputs used + +{{ inputDocs }} + +### API usage + +This integration uses the following Pi-hole API endpoints: + +**Authentication:** +- `POST /api/auth` - Authenticates and obtains a session ID +- `DELETE /api/auth` - Terminates the session + +**Data Collection:** +- `GET /api/queries?length=1000&from=` - Retrieves DNS query logs with timestamp-based filtering (dns_queries data stream) +- `GET /api/history` - Retrieves time-series query metrics (query_history data stream) +- `GET /api/stats/summary` - Retrieves comprehensive Pi-hole statistics (pihole_summary data stream) +- `GET /api/stats/top_clients?blocked=false` - Retrieves top allowed DNS clients (top_clients data stream) +- `GET /api/stats/top_clients?blocked=true` - Retrieves top blocked DNS clients (top_clients data stream) +- `GET /api/stats/top_domains?blocked=false` - Retrieves top allowed domains (top_domains data stream) +- `GET /api/stats/top_domains?blocked=true` - Retrieves top blocked domains (top_domains data stream) + +All data collection endpoints require authentication via the `X-FTL-SID` header. Each data stream manages its own session lifecycle independently. + +For more information about the Pi-hole API, see the [Pi-hole API documentation](https://docs.pi-hole.net/ftldns/api/). diff --git a/packages/pihole/changelog.yml b/packages/pihole/changelog.yml new file mode 100644 index 00000000000..cb1805f878d --- /dev/null +++ b/packages/pihole/changelog.yml @@ -0,0 +1,134 @@ +# newer versions go on top +- version: "0.1.23" + changes: + - description: Made dns_queries max queries per request configurable via max_queries_per_request setting (default 1000) + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.22" + changes: + - description: Removed duplicate "example event" text in documentation and standardized variable descriptions across all data stream manifests + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.21" + changes: + - description: Fixed dashboard filter metadata to reference data_stream.dataset field instead of non-existent query field, resolving "field query does not exist in current view" error + type: bugfix + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.20" + changes: + - description: Updated dashboard typeMigrationVersion to 10.2.0 for compatibility with production Kibana + type: bugfix + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.19" + changes: + - description: Added three Kibana dashboards (Overview, Security & Blocking, DNS Analysis) for comprehensive Pi-hole monitoring and visualization + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 + - description: Changed default collection interval for query_history data stream from 5 minutes to 10 minutes + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 + - description: Made collection interval setting visible by default for dns_queries data stream + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.18" + changes: + - description: Force CEL to complete logout requests by checking logout response status, preventing session leaks while ensuring data collection continues regardless of logout success + type: bugfix + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.17" + changes: + - description: Fixed CEL syntax error in pihole_summary data stream caused by extra closing parenthesis + type: bugfix + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.16" + changes: + - description: Fixed CEL program logic to process API responses before validating logout, preventing empty events when logout validation fails + type: bugfix + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.15" + changes: + - description: Added three Kibana dashboards (Overview, Security & Blocking, DNS Analysis) for comprehensive Pi-hole monitoring and visualization + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.14" + changes: + - description: Added logout response validation to force CEL to wait for session cleanup completion and log errors if logout fails, ensuring Pi-hole API sessions are actually closed instead of relying on expiration + type: bugfix + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.13" + changes: + - description: Fixed session cleanup in all data streams (dns_queries, query_history, pihole_summary, top_clients, top_domains) to ensure API sessions are always logged out immediately after data collection, preventing session leaks and duplicate data ingestion + type: bugfix + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.11" + changes: + - description: Added data view (index pattern) definitions for all data streams to support saved searches and dashboards + type: bugfix + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.10" + changes: + - description: Added three Kibana dashboards (Overview, Security & Blocking, DNS Analysis) and three saved searches (Blocked Queries, DNSSEC Failures, Slow Queries) for comprehensive Pi-hole monitoring and analysis + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.9" + changes: + - description: Updated README documentation to include all data streams (query_history, pihole_summary, top_clients, top_domains) and generalized sample events to use example.com domains + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.8" + changes: + - description: Added comprehensive query metrics to pihole_summary including query frequency, gravity last update, query type breakdowns (A, AAAA, PTR, etc.), query status breakdowns (gravity, cache, forwarded, etc.), and query reply breakdowns (NODATA, NXDOMAIN, IP, etc.) + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.7" + changes: + - description: Added query_action dimension to top_clients and top_domains data streams to distinguish between allowed and blocked queries + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.6" + changes: + - description: Added total_queries and blocked_queries metrics to top_domains data stream + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.5" + changes: + - description: Added top_domains data stream for top queried domains statistics + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.4" + changes: + - description: Added top_clients data stream for top DNS clients statistics + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.3" + changes: + - description: Fixed pihole_summary field extraction for Pi-hole v6 API structure + type: bugfix + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.2" + changes: + - description: Added pihole_summary data stream for overall statistics + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.1" + changes: + - description: Added query_history data stream for time-series metrics + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 +- version: "0.1.0" + changes: + - description: First official release of Pi-hole integration with dns_queries data stream + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 + - description: Implemented session-based authentication with Pi-hole API + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 + - description: Added timestamp-based pagination with cursor persistence for reliable data collection + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 + - description: Added ECS field mappings for DNS queries + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 + - description: Added comprehensive documentation and official Pi-hole logo + type: enhancement + link: https://github.com/elastic/integrations/pull/16743 diff --git a/packages/pihole/data_stream/dns_queries/agent/stream/cel.yml.hbs b/packages/pihole/data_stream/dns_queries/agent/stream/cel.yml.hbs new file mode 100644 index 00000000000..6f1c3c44858 --- /dev/null +++ b/packages/pihole/data_stream/dns_queries/agent/stream/cel.yml.hbs @@ -0,0 +1,146 @@ +config_version: 2 +interval: {{interval}} +resource.tracer: + enabled: {{enable_request_tracer}} + filename: "../../logs/cel/http-request-trace-*.ndjson" + maxbackups: 5 +{{#if proxy_url}} +resource.proxy_url: {{proxy_url}} +{{/if}} +{{#if ssl}} +resource.ssl: {{ssl}} +{{/if}} +{{#if http_client_timeout}} +resource.timeout: {{http_client_timeout}} +{{/if}} +resource.url: {{url}} +state: + url: {{url}} + cursor: + last_timestamp: 0.0 +program: | + // Authenticate and collect queries in a single flow + post_request( + state.url.trim_right("/") + "/api/auth", + "application/json", + { + "password": "{{api_password}}" + }.encode_json() + ).do_request().as(auth_resp, + auth_resp.StatusCode != 200 ? + { + "events": [{ + "error": { + "message": "Authentication failed: HTTP " + string(auth_resp.StatusCode) + } + }], + "want_more": false + } + : + auth_resp.Body.decode_json().as(auth_body, + !has(auth_body.session) || !has(auth_body.session.sid) ? + { + "events": [{ + "error": { + "message": "Authentication failed: invalid session response" + } + }], + "want_more": false + } + : + // Got valid session, now fetch queries + auth_body.session.sid.as(session_id, + // Use session token to collect queries with timestamp-based pagination + ((has(state.cursor) && has(state.cursor.last_timestamp) && state.cursor.last_timestamp > 0) ? + // We have a timestamp from previous run, use it to get only newer records + request( + "GET", + state.url.trim_right("/") + "/api/queries?length={{max_queries_per_request}}&from=" + string(state.cursor.last_timestamp) + ) + : + // First run or state lost, start from beginning + request( + "GET", + state.url.trim_right("/") + "/api/queries?length={{max_queries_per_request}}" + ) + ).with({ + "Header": { + "X-FTL-SID": [session_id] + } + }).do_request().as(queries_resp, + // Process queries response first + queries_resp.StatusCode != 200 ? + // Query request failed, still try to logout before returning error + request( + "DELETE", + state.url.trim_right("/") + "/api/auth" + ).with({ + "Header": { + "X-FTL-SID": [session_id] + } + }).do_request().as(logout_resp, { + "events": [{ + "error": { + "message": "Failed to collect queries: HTTP " + string(queries_resp.StatusCode) + } + }], + "want_more": false, + "cursor": has(state.cursor) ? state.cursor : {"last_timestamp": 0.0} + }) + : + // Query request succeeded, process response + queries_resp.Body.decode_json().as(queries_body, + // Check if queries field exists and has data + has(queries_body.queries) && size(queries_body.queries) > 0 ? + // Capture newest (first) timestamp from this batch + queries_body.queries[0].time.as(newest_ts, + // Logout after successful collection + request( + "DELETE", + state.url.trim_right("/") + "/api/auth" + ).with({ + "Header": { + "X-FTL-SID": [session_id] + } + }).do_request().as(logout_resp, + // Force logout to complete by checking status, but always emit events + logout_resp.StatusCode == 204 ? { + "cursor": {"last_timestamp": newest_ts}, + "events": queries_body.queries.map(query, {"id": query.id, "type": query.type, "domain": query.domain, "status": query.status, "dnssec": query.dnssec, "reply_time": query.reply.time, "client_ip": query.client.ip, "client_name": query.client.name, "upstream_name": query.upstream, "reply_type": query.reply.type, "cname": query.cname, "list_id": query.list_id, "ede_code": query.ede.code, "ede_text": query.ede.text, "time": query.time}) + } : { + "cursor": {"last_timestamp": newest_ts}, + "events": queries_body.queries.map(query, {"id": query.id, "type": query.type, "domain": query.domain, "status": query.status, "dnssec": query.dnssec, "reply_time": query.reply.time, "client_ip": query.client.ip, "client_name": query.client.name, "upstream_name": query.upstream, "reply_type": query.reply.type, "cname": query.cname, "list_id": query.list_id, "ede_code": query.ede.code, "ede_text": query.ede.text, "time": query.time, "_logout_failed": "HTTP " + string(logout_resp.StatusCode)}) + } + ) + ) + : + // No queries in response, logout and preserve existing cursor + request( + "DELETE", + state.url.trim_right("/") + "/api/auth" + ).with({ + "Header": { + "X-FTL-SID": [session_id] + } + }).do_request().as(logout_resp, { + "cursor": has(state.cursor) ? state.cursor : {"last_timestamp": 0.0}, + "events": [] + }) + ) + ) + ) + ) + ) +tags: +{{#if preserve_original_event}} + - preserve_original_event +{{/if}} +{{#if tags}} +{{#each tags as |tag|}} + - {{tag}} +{{/each}} +{{/if}} +{{#if processors}} +processors: +{{processors}} +{{/if}} diff --git a/packages/pihole/data_stream/dns_queries/elasticsearch/ingest_pipeline/default.yml b/packages/pihole/data_stream/dns_queries/elasticsearch/ingest_pipeline/default.yml new file mode 100644 index 00000000000..4a0e3c65d0c --- /dev/null +++ b/packages/pihole/data_stream/dns_queries/elasticsearch/ingest_pipeline/default.yml @@ -0,0 +1,116 @@ +--- +description: "Process Pi-hole DNS query logs" +processors: + # Add ECS version and event metadata + - set: + field: ecs.version + value: "8.11.0" + - set: + field: event.kind + value: "event" + - set: + field: event.category + value: ["network"] + - set: + field: event.type + value: ["protocol"] + - set: + field: event.module + value: "pihole" + - set: + field: event.dataset + value: "pihole.dns_queries" + + # Add data_stream fields + - set: + field: data_stream.type + value: "logs" + - set: + field: data_stream.dataset + value: "pihole.dns_queries" + - set: + field: data_stream.namespace + value: "default" + + # Convert Unix timestamp (with fractional seconds) to ISO format + - date: + field: time + target_field: '@timestamp' + formats: + - UNIX + + # Map DNS query fields to ECS + - rename: + field: domain + target_field: dns.question.name + ignore_missing: true + - rename: + field: type + target_field: dns.question.type + ignore_missing: true + - rename: + field: reply_type + target_field: dns.response_code + ignore_missing: true + - rename: + field: client_ip + target_field: source.ip + ignore_missing: true + - rename: + field: client_name + target_field: source.domain + ignore_missing: true + - rename: + field: upstream_name + target_field: destination.ip + ignore_missing: true + + # Map Pi-hole specific fields + - rename: + field: id + target_field: pihole.query.id + ignore_missing: true + - rename: + field: status + target_field: pihole.query.status + ignore_missing: true + - rename: + field: dnssec + target_field: pihole.query.dnssec + ignore_missing: true + - rename: + field: reply_time + target_field: pihole.query.reply_time + ignore_missing: true + - rename: + field: cname + target_field: pihole.cname + ignore_missing: true + - rename: + field: list_id + target_field: pihole.list_id + ignore_missing: true + - rename: + field: ede_code + target_field: pihole.ede.code + ignore_missing: true + - rename: + field: ede_text + target_field: pihole.ede.text + ignore_missing: true + + # Add observer information + - set: + field: observer.vendor + value: "Pi-hole" + - set: + field: observer.product + value: "Pi-hole" + - set: + field: observer.type + value: "dns" + +on_failure: + - set: + field: error.message + value: '{{ _ingest.on_failure_message }}' diff --git a/packages/pihole/data_stream/dns_queries/fields/base-fields.yml b/packages/pihole/data_stream/dns_queries/fields/base-fields.yml new file mode 100644 index 00000000000..7c798f4534c --- /dev/null +++ b/packages/pihole/data_stream/dns_queries/fields/base-fields.yml @@ -0,0 +1,12 @@ +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: data_stream.dataset + type: constant_keyword + description: Data stream dataset. +- name: data_stream.namespace + type: constant_keyword + description: Data stream namespace. +- name: '@timestamp' + type: date + description: Event timestamp. diff --git a/packages/pihole/data_stream/dns_queries/fields/ecs.yml b/packages/pihole/data_stream/dns_queries/fields/ecs.yml new file mode 100644 index 00000000000..c74c8afb851 --- /dev/null +++ b/packages/pihole/data_stream/dns_queries/fields/ecs.yml @@ -0,0 +1,47 @@ +- name: dns + type: group + description: DNS query and response information + fields: + - name: question + type: group + description: DNS question information + fields: + - name: name + type: keyword + description: The domain name being queried + - name: type + type: keyword + description: The type of DNS record being queried (A, AAAA, CNAME, etc.) + - name: response_code + type: keyword + description: The DNS response code +- name: source + type: group + description: Source information (client making the DNS query) + fields: + - name: ip + type: ip + description: IP address of the client + - name: domain + type: keyword + description: Domain name/hostname of the client +- name: destination + type: group + description: Destination information (upstream DNS server) + fields: + - name: ip + type: keyword + description: Upstream DNS server that handled the query +- name: observer + type: group + description: Observer (Pi-hole) information + fields: + - name: vendor + type: keyword + description: Observer vendor name + - name: product + type: keyword + description: Observer product name + - name: type + type: keyword + description: Observer type diff --git a/packages/pihole/data_stream/dns_queries/fields/fields.yml b/packages/pihole/data_stream/dns_queries/fields/fields.yml new file mode 100644 index 00000000000..6c1070653b5 --- /dev/null +++ b/packages/pihole/data_stream/dns_queries/fields/fields.yml @@ -0,0 +1,36 @@ +- name: pihole + type: group + description: Pi-hole DNS query log fields + fields: + - name: query + type: group + description: DNS query information + fields: + - name: id + type: long + description: Unique identifier for this query + - name: status + type: keyword + description: Status of the query (CACHE, FORWARDED, BLOCKED, ALLOWED, etc.) + - name: dnssec + type: keyword + description: DNSSEC status (UNKNOWN, SECURE, INSECURE, BOGUS) + - name: reply_time + type: double + description: Query reply time in seconds + - name: cname + type: keyword + description: CNAME if the query resulted in a CNAME + - name: list_id + type: long + description: Gravity list ID if the query matched a blocklist + - name: ede + type: group + description: Extended DNS Error information + fields: + - name: code + type: long + description: Extended DNS Error code + - name: text + type: keyword + description: Extended DNS Error text diff --git a/packages/pihole/data_stream/dns_queries/manifest.yml b/packages/pihole/data_stream/dns_queries/manifest.yml new file mode 100644 index 00000000000..6584d75b4e7 --- /dev/null +++ b/packages/pihole/data_stream/dns_queries/manifest.yml @@ -0,0 +1,69 @@ +title: "Query Log" +type: logs +streams: + - input: cel + title: Collect Pi-hole query logs + description: Collect individual DNS queries from Pi-hole API with query details and actions + template_path: cel.yml.hbs + vars: + - name: interval + type: text + title: Collection Interval + description: Interval for data collection + default: 60s + required: false + show_user: true + - name: max_queries_per_request + type: integer + title: Maximum Queries Per Request + description: Maximum number of queries to fetch per API request (1-10000) + default: 1000 + required: false + show_user: true + - name: enable_request_tracer + type: bool + title: Enable Request Tracer + description: Enable request tracing for debugging + default: false + - name: proxy_url + type: text + title: Proxy URL + description: Optional HTTP proxy for connecting to Pi-hole + required: false + show_user: false + - name: ssl + type: yaml + title: SSL Configuration + description: Custom SSL/TLS settings for HTTPS connections + required: false + show_user: false + - name: http_client_timeout + type: text + title: HTTP Client Timeout + description: Request timeout duration (default 30s) + default: 30s + required: false + show_user: false + - name: preserve_original_event + type: bool + title: Preserve Original Event + description: Preserve the original event data for debugging + default: false + required: false + show_user: false + - name: tags + type: text + title: Tags + multi: true + default: + - pihole-dns_queries + required: false + show_user: false + - name: processors + type: yaml + title: Processors + description: Custom processors for data processing + required: false + show_user: false +elasticsearch: + source_mode: default diff --git a/packages/pihole/data_stream/dns_queries/sample_event.json b/packages/pihole/data_stream/dns_queries/sample_event.json new file mode 100644 index 00000000000..7e01f6593f6 --- /dev/null +++ b/packages/pihole/data_stream/dns_queries/sample_event.json @@ -0,0 +1,49 @@ +{ + "@timestamp": "2024-10-22T17:47:49.139Z", + "data_stream": { + "type": "logs", + "dataset": "pihole.dns_queries", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "event", + "category": [ + "network" + ], + "type": [ + "protocol" + ], + "module": "pihole", + "dataset": "pihole.dns_queries" + }, + "dns": { + "question": { + "name": "monitor.example.com", + "type": "AAAA" + }, + "response_code": "NODATA" + }, + "source": { + "ip": "192.168.1.100", + "domain": "workstation.example.com" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "pihole": { + "query": { + "id": 22438755, + "status": "CACHE", + "dnssec": "UNKNOWN", + "reply_time": 0.0000243 + }, + "ede": { + "code": -1 + } + } +} diff --git a/packages/pihole/data_stream/pihole_summary/agent/stream/cel.yml.hbs b/packages/pihole/data_stream/pihole_summary/agent/stream/cel.yml.hbs new file mode 100644 index 00000000000..22c8f5845e0 --- /dev/null +++ b/packages/pihole/data_stream/pihole_summary/agent/stream/cel.yml.hbs @@ -0,0 +1,233 @@ +config_version: 2 +interval: {{interval}} +resource.tracer: + enabled: {{enable_request_tracer}} + filename: "../../logs/cel/http-request-trace-*.ndjson" + maxbackups: 5 +{{#if proxy_url}} +resource.proxy_url: {{proxy_url}} +{{/if}} +{{#if ssl}} +resource.ssl: {{ssl}} +{{/if}} +{{#if http_client_timeout}} +resource.timeout: {{http_client_timeout}} +{{/if}} +resource.url: {{url}} +state: + url: {{url}} +redact: + fields: + - api_password +program: | + // Authenticate with Pi-hole API + post_request( + state.url.trim_right("/") + "/api/auth", + "application/json", + { + "password": "{{api_password}}" + }.encode_json() + ).do_request().as(auth_resp, + auth_resp.StatusCode != 200 ? + { + "events": [{ + "error": { + "message": "Authentication failed: HTTP " + string(auth_resp.StatusCode) + } + }], + "want_more": false + } + : + auth_resp.Body.decode_json().as(auth_body, + !has(auth_body.session) || !has(auth_body.session.sid) ? + { + "events": [{ + "error": { + "message": "Authentication response missing session ID" + } + }], + "want_more": false + } + : + // Get summary statistics using session ID + request( + "GET", + state.url.trim_right("/") + "/api/stats/summary" + ).with({ + "Header": { + "X-FTL-SID": [auth_body.session.sid] + } + }).do_request().as(summary_resp, + // Process summary response first + summary_resp.StatusCode != 200 ? + // Summary request failed, still try to logout before returning error + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(logout_resp, { + "events": [{ + "error": { + "message": "Failed to fetch summary: HTTP " + string(summary_resp.StatusCode) + } + }], + "want_more": false + }) + : + // Summary request succeeded, process response + summary_resp.Body.decode_json().as(summary_body, + // Logout after successful collection + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(logout_resp, + // Force logout completion by checking status + logout_resp.StatusCode != 204 ? + { + "events": [{ + "_logout_failed": "HTTP " + string(logout_resp.StatusCode), + "dns_queries_today": has(summary_body.queries) && has(summary_body.queries.total) ? int(summary_body.queries.total) : 0, + "ads_blocked_today": has(summary_body.queries) && has(summary_body.queries.blocked) ? int(summary_body.queries.blocked) : 0, + "ads_percentage_today": has(summary_body.queries) && has(summary_body.queries.percent_blocked) ? double(summary_body.queries.percent_blocked) : 0.0, + "domains_being_blocked": has(summary_body.gravity) && has(summary_body.gravity.domains_being_blocked) ? int(summary_body.gravity.domains_being_blocked) : 0, + "queries_forwarded": has(summary_body.queries) && has(summary_body.queries.forwarded) ? int(summary_body.queries.forwarded) : 0, + "queries_cached": has(summary_body.queries) && has(summary_body.queries.cached) ? int(summary_body.queries.cached) : 0, + "clients_ever_seen": has(summary_body.clients) && has(summary_body.clients.total) ? int(summary_body.clients.total) : 0, + "unique_clients": has(summary_body.clients) && has(summary_body.clients.active) ? int(summary_body.clients.active) : 0, + "unique_domains": has(summary_body.queries) && has(summary_body.queries.unique_domains) ? int(summary_body.queries.unique_domains) : 0, + "query_frequency": has(summary_body.queries) && has(summary_body.queries.frequency) ? double(summary_body.queries.frequency) : 0.0, + "gravity_last_update": has(summary_body.gravity) && has(summary_body.gravity.last_update) ? int(summary_body.gravity.last_update) : 0, + "status": "enabled", + "query_types_a": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.A) ? int(summary_body.queries.types.A) : 0, + "query_types_aaaa": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.AAAA) ? int(summary_body.queries.types.AAAA) : 0, + "query_types_any": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.ANY) ? int(summary_body.queries.types.ANY) : 0, + "query_types_srv": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.SRV) ? int(summary_body.queries.types.SRV) : 0, + "query_types_soa": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.SOA) ? int(summary_body.queries.types.SOA) : 0, + "query_types_ptr": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.PTR) ? int(summary_body.queries.types.PTR) : 0, + "query_types_txt": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.TXT) ? int(summary_body.queries.types.TXT) : 0, + "query_types_naptr": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.NAPTR) ? int(summary_body.queries.types.NAPTR) : 0, + "query_types_mx": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.MX) ? int(summary_body.queries.types.MX) : 0, + "query_types_ds": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.DS) ? int(summary_body.queries.types.DS) : 0, + "query_types_rrsig": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.RRSIG) ? int(summary_body.queries.types.RRSIG) : 0, + "query_types_dnskey": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.DNSKEY) ? int(summary_body.queries.types.DNSKEY) : 0, + "query_types_ns": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.NS) ? int(summary_body.queries.types.NS) : 0, + "query_types_svcb": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.SVCB) ? int(summary_body.queries.types.SVCB) : 0, + "query_types_https": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.HTTPS) ? int(summary_body.queries.types.HTTPS) : 0, + "query_types_other": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.OTHER) ? int(summary_body.queries.types.OTHER) : 0, + "query_status_unknown": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.UNKNOWN) ? int(summary_body.queries.status.UNKNOWN) : 0, + "query_status_gravity": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.GRAVITY) ? int(summary_body.queries.status.GRAVITY) : 0, + "query_status_forwarded": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.FORWARDED) ? int(summary_body.queries.status.FORWARDED) : 0, + "query_status_cache": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.CACHE) ? int(summary_body.queries.status.CACHE) : 0, + "query_status_regex": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.REGEX) ? int(summary_body.queries.status.REGEX) : 0, + "query_status_denylist": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.DENYLIST) ? int(summary_body.queries.status.DENYLIST) : 0, + "query_status_external_blocked_ip": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.EXTERNAL_BLOCKED_IP) ? int(summary_body.queries.status.EXTERNAL_BLOCKED_IP) : 0, + "query_status_external_blocked_null": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.EXTERNAL_BLOCKED_NULL) ? int(summary_body.queries.status.EXTERNAL_BLOCKED_NULL) : 0, + "query_status_external_blocked_nxra": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.EXTERNAL_BLOCKED_NXRA) ? int(summary_body.queries.status.EXTERNAL_BLOCKED_NXRA) : 0, + "query_status_gravity_cname": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.GRAVITY_CNAME) ? int(summary_body.queries.status.GRAVITY_CNAME) : 0, + "query_status_regex_cname": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.REGEX_CNAME) ? int(summary_body.queries.status.REGEX_CNAME) : 0, + "query_status_denylist_cname": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.DENYLIST_CNAME) ? int(summary_body.queries.status.DENYLIST_CNAME) : 0, + "query_status_retried": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.RETRIED) ? int(summary_body.queries.status.RETRIED) : 0, + "query_status_retried_dnssec": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.RETRIED_DNSSEC) ? int(summary_body.queries.status.RETRIED_DNSSEC) : 0, + "query_status_in_progress": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.IN_PROGRESS) ? int(summary_body.queries.status.IN_PROGRESS) : 0, + "query_status_dbbusy": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.DBBUSY) ? int(summary_body.queries.status.DBBUSY) : 0, + "query_status_special_domain": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.SPECIAL_DOMAIN) ? int(summary_body.queries.status.SPECIAL_DOMAIN) : 0, + "query_status_cache_stale": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.CACHE_STALE) ? int(summary_body.queries.status.CACHE_STALE) : 0, + "query_status_external_blocked_ede15": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.EXTERNAL_BLOCKED_EDE15) ? int(summary_body.queries.status.EXTERNAL_BLOCKED_EDE15) : 0, + "query_replies_unknown": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.UNKNOWN) ? int(summary_body.queries.replies.UNKNOWN) : 0, + "query_replies_nodata": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.NODATA) ? int(summary_body.queries.replies.NODATA) : 0, + "query_replies_nxdomain": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.NXDOMAIN) ? int(summary_body.queries.replies.NXDOMAIN) : 0, + "query_replies_cname": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.CNAME) ? int(summary_body.queries.replies.CNAME) : 0, + "query_replies_ip": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.IP) ? int(summary_body.queries.replies.IP) : 0, + "query_replies_domain": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.DOMAIN) ? int(summary_body.queries.replies.DOMAIN) : 0, + "query_replies_rrname": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.RRNAME) ? int(summary_body.queries.replies.RRNAME) : 0, + "query_replies_servfail": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.SERVFAIL) ? int(summary_body.queries.replies.SERVFAIL) : 0, + "query_replies_refused": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.REFUSED) ? int(summary_body.queries.replies.REFUSED) : 0, + "query_replies_notimp": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.NOTIMP) ? int(summary_body.queries.replies.NOTIMP) : 0, + "query_replies_other": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.OTHER) ? int(summary_body.queries.replies.OTHER) : 0, + "query_replies_dnssec": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.DNSSEC) ? int(summary_body.queries.replies.DNSSEC) : 0, + "query_replies_none": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.NONE) ? int(summary_body.queries.replies.NONE) : 0, + "query_replies_blob": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.BLOB) ? int(summary_body.queries.replies.BLOB) : 0 + }], + "want_more": false + } + : { + "events": [{ + "dns_queries_today": has(summary_body.queries) && has(summary_body.queries.total) ? int(summary_body.queries.total) : 0, + "ads_blocked_today": has(summary_body.queries) && has(summary_body.queries.blocked) ? int(summary_body.queries.blocked) : 0, + "ads_percentage_today": has(summary_body.queries) && has(summary_body.queries.percent_blocked) ? double(summary_body.queries.percent_blocked) : 0.0, + "domains_being_blocked": has(summary_body.gravity) && has(summary_body.gravity.domains_being_blocked) ? int(summary_body.gravity.domains_being_blocked) : 0, + "queries_forwarded": has(summary_body.queries) && has(summary_body.queries.forwarded) ? int(summary_body.queries.forwarded) : 0, + "queries_cached": has(summary_body.queries) && has(summary_body.queries.cached) ? int(summary_body.queries.cached) : 0, + "clients_ever_seen": has(summary_body.clients) && has(summary_body.clients.total) ? int(summary_body.clients.total) : 0, + "unique_clients": has(summary_body.clients) && has(summary_body.clients.active) ? int(summary_body.clients.active) : 0, + "unique_domains": has(summary_body.queries) && has(summary_body.queries.unique_domains) ? int(summary_body.queries.unique_domains) : 0, + "query_frequency": has(summary_body.queries) && has(summary_body.queries.frequency) ? double(summary_body.queries.frequency) : 0.0, + "gravity_last_update": has(summary_body.gravity) && has(summary_body.gravity.last_update) ? int(summary_body.gravity.last_update) : 0, + "status": "enabled", + "query_types_a": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.A) ? int(summary_body.queries.types.A) : 0, + "query_types_aaaa": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.AAAA) ? int(summary_body.queries.types.AAAA) : 0, + "query_types_any": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.ANY) ? int(summary_body.queries.types.ANY) : 0, + "query_types_srv": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.SRV) ? int(summary_body.queries.types.SRV) : 0, + "query_types_soa": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.SOA) ? int(summary_body.queries.types.SOA) : 0, + "query_types_ptr": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.PTR) ? int(summary_body.queries.types.PTR) : 0, + "query_types_txt": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.TXT) ? int(summary_body.queries.types.TXT) : 0, + "query_types_naptr": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.NAPTR) ? int(summary_body.queries.types.NAPTR) : 0, + "query_types_mx": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.MX) ? int(summary_body.queries.types.MX) : 0, + "query_types_ds": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.DS) ? int(summary_body.queries.types.DS) : 0, + "query_types_rrsig": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.RRSIG) ? int(summary_body.queries.types.RRSIG) : 0, + "query_types_dnskey": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.DNSKEY) ? int(summary_body.queries.types.DNSKEY) : 0, + "query_types_ns": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.NS) ? int(summary_body.queries.types.NS) : 0, + "query_types_svcb": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.SVCB) ? int(summary_body.queries.types.SVCB) : 0, + "query_types_https": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.HTTPS) ? int(summary_body.queries.types.HTTPS) : 0, + "query_types_other": has(summary_body.queries) && has(summary_body.queries.types) && has(summary_body.queries.types.OTHER) ? int(summary_body.queries.types.OTHER) : 0, + "query_status_unknown": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.UNKNOWN) ? int(summary_body.queries.status.UNKNOWN) : 0, + "query_status_gravity": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.GRAVITY) ? int(summary_body.queries.status.GRAVITY) : 0, + "query_status_forwarded": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.FORWARDED) ? int(summary_body.queries.status.FORWARDED) : 0, + "query_status_cache": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.CACHE) ? int(summary_body.queries.status.CACHE) : 0, + "query_status_regex": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.REGEX) ? int(summary_body.queries.status.REGEX) : 0, + "query_status_denylist": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.DENYLIST) ? int(summary_body.queries.status.DENYLIST) : 0, + "query_status_external_blocked_ip": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.EXTERNAL_BLOCKED_IP) ? int(summary_body.queries.status.EXTERNAL_BLOCKED_IP) : 0, + "query_status_external_blocked_null": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.EXTERNAL_BLOCKED_NULL) ? int(summary_body.queries.status.EXTERNAL_BLOCKED_NULL) : 0, + "query_status_external_blocked_nxra": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.EXTERNAL_BLOCKED_NXRA) ? int(summary_body.queries.status.EXTERNAL_BLOCKED_NXRA) : 0, + "query_status_gravity_cname": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.GRAVITY_CNAME) ? int(summary_body.queries.status.GRAVITY_CNAME) : 0, + "query_status_regex_cname": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.REGEX_CNAME) ? int(summary_body.queries.status.REGEX_CNAME) : 0, + "query_status_denylist_cname": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.DENYLIST_CNAME) ? int(summary_body.queries.status.DENYLIST_CNAME) : 0, + "query_status_retried": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.RETRIED) ? int(summary_body.queries.status.RETRIED) : 0, + "query_status_retried_dnssec": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.RETRIED_DNSSEC) ? int(summary_body.queries.status.RETRIED_DNSSEC) : 0, + "query_status_in_progress": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.IN_PROGRESS) ? int(summary_body.queries.status.IN_PROGRESS) : 0, + "query_status_dbbusy": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.DBBUSY) ? int(summary_body.queries.status.DBBUSY) : 0, + "query_status_special_domain": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.SPECIAL_DOMAIN) ? int(summary_body.queries.status.SPECIAL_DOMAIN) : 0, + "query_status_cache_stale": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.CACHE_STALE) ? int(summary_body.queries.status.CACHE_STALE) : 0, + "query_status_external_blocked_ede15": has(summary_body.queries) && has(summary_body.queries.status) && has(summary_body.queries.status.EXTERNAL_BLOCKED_EDE15) ? int(summary_body.queries.status.EXTERNAL_BLOCKED_EDE15) : 0, + "query_replies_unknown": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.UNKNOWN) ? int(summary_body.queries.replies.UNKNOWN) : 0, + "query_replies_nodata": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.NODATA) ? int(summary_body.queries.replies.NODATA) : 0, + "query_replies_nxdomain": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.NXDOMAIN) ? int(summary_body.queries.replies.NXDOMAIN) : 0, + "query_replies_cname": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.CNAME) ? int(summary_body.queries.replies.CNAME) : 0, + "query_replies_ip": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.IP) ? int(summary_body.queries.replies.IP) : 0, + "query_replies_domain": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.DOMAIN) ? int(summary_body.queries.replies.DOMAIN) : 0, + "query_replies_rrname": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.RRNAME) ? int(summary_body.queries.replies.RRNAME) : 0, + "query_replies_servfail": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.SERVFAIL) ? int(summary_body.queries.replies.SERVFAIL) : 0, + "query_replies_refused": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.REFUSED) ? int(summary_body.queries.replies.REFUSED) : 0, + "query_replies_notimp": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.NOTIMP) ? int(summary_body.queries.replies.NOTIMP) : 0, + "query_replies_other": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.OTHER) ? int(summary_body.queries.replies.OTHER) : 0, + "query_replies_dnssec": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.DNSSEC) ? int(summary_body.queries.replies.DNSSEC) : 0, + "query_replies_none": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.NONE) ? int(summary_body.queries.replies.NONE) : 0, + "query_replies_blob": has(summary_body.queries) && has(summary_body.queries.replies) && has(summary_body.queries.replies.BLOB) ? int(summary_body.queries.replies.BLOB) : 0 + }], + "want_more": false + } + ) + ) + ) + ) + ) +tags: +{{#if preserve_original_event}} + - preserve_original_event +{{/if}} +{{#if tags}} +{{#each tags as |tag|}} + - {{tag}} +{{/each}} +{{/if}} +{{#if processors}} +processors: +{{processors}} +{{/if}} diff --git a/packages/pihole/data_stream/pihole_summary/elasticsearch/ingest_pipeline/default.yml b/packages/pihole/data_stream/pihole_summary/elasticsearch/ingest_pipeline/default.yml new file mode 100644 index 00000000000..2d9d6c22dc0 --- /dev/null +++ b/packages/pihole/data_stream/pihole_summary/elasticsearch/ingest_pipeline/default.yml @@ -0,0 +1,286 @@ +--- +description: Pipeline for processing Pi-hole summary statistics +processors: + # Set event fields + - set: + field: event.kind + value: metric + - set: + field: event.category + value: ["network"] + - set: + field: event.type + value: ["info"] + - set: + field: event.module + value: pihole + - set: + field: event.dataset + value: pihole.pihole_summary + + # Set observer fields + - set: + field: observer.vendor + value: Pi-hole + - set: + field: observer.product + value: Pi-hole + - set: + field: observer.type + value: dns + + # Rename metric fields to pihole.summary namespace + - rename: + field: dns_queries_today + target_field: pihole.summary.dns_queries_today + ignore_missing: true + - rename: + field: ads_blocked_today + target_field: pihole.summary.ads_blocked_today + ignore_missing: true + - rename: + field: ads_percentage_today + target_field: pihole.summary.ads_percentage_today + ignore_missing: true + - rename: + field: domains_being_blocked + target_field: pihole.summary.domains_being_blocked + ignore_missing: true + - rename: + field: queries_forwarded + target_field: pihole.summary.queries_forwarded + ignore_missing: true + - rename: + field: queries_cached + target_field: pihole.summary.queries_cached + ignore_missing: true + - rename: + field: clients_ever_seen + target_field: pihole.summary.clients_ever_seen + ignore_missing: true + - rename: + field: unique_clients + target_field: pihole.summary.unique_clients + ignore_missing: true + - rename: + field: unique_domains + target_field: pihole.summary.unique_domains + ignore_missing: true + - rename: + field: status + target_field: pihole.summary.status + ignore_missing: true + - rename: + field: query_frequency + target_field: pihole.summary.query_frequency + ignore_missing: true + - rename: + field: gravity_last_update + target_field: pihole.summary.gravity_last_update + ignore_missing: true + - rename: + field: query_types_a + target_field: pihole.summary.query_types.a + ignore_missing: true + - rename: + field: query_types_aaaa + target_field: pihole.summary.query_types.aaaa + ignore_missing: true + - rename: + field: query_types_any + target_field: pihole.summary.query_types.any + ignore_missing: true + - rename: + field: query_types_srv + target_field: pihole.summary.query_types.srv + ignore_missing: true + - rename: + field: query_types_soa + target_field: pihole.summary.query_types.soa + ignore_missing: true + - rename: + field: query_types_ptr + target_field: pihole.summary.query_types.ptr + ignore_missing: true + - rename: + field: query_types_txt + target_field: pihole.summary.query_types.txt + ignore_missing: true + - rename: + field: query_types_naptr + target_field: pihole.summary.query_types.naptr + ignore_missing: true + - rename: + field: query_types_mx + target_field: pihole.summary.query_types.mx + ignore_missing: true + - rename: + field: query_types_ds + target_field: pihole.summary.query_types.ds + ignore_missing: true + - rename: + field: query_types_rrsig + target_field: pihole.summary.query_types.rrsig + ignore_missing: true + - rename: + field: query_types_dnskey + target_field: pihole.summary.query_types.dnskey + ignore_missing: true + - rename: + field: query_types_ns + target_field: pihole.summary.query_types.ns + ignore_missing: true + - rename: + field: query_types_svcb + target_field: pihole.summary.query_types.svcb + ignore_missing: true + - rename: + field: query_types_https + target_field: pihole.summary.query_types.https + ignore_missing: true + - rename: + field: query_types_other + target_field: pihole.summary.query_types.other + ignore_missing: true + - rename: + field: query_status_unknown + target_field: pihole.summary.query_status.unknown + ignore_missing: true + - rename: + field: query_status_gravity + target_field: pihole.summary.query_status.gravity + ignore_missing: true + - rename: + field: query_status_forwarded + target_field: pihole.summary.query_status.forwarded + ignore_missing: true + - rename: + field: query_status_cache + target_field: pihole.summary.query_status.cache + ignore_missing: true + - rename: + field: query_status_regex + target_field: pihole.summary.query_status.regex + ignore_missing: true + - rename: + field: query_status_denylist + target_field: pihole.summary.query_status.denylist + ignore_missing: true + - rename: + field: query_status_external_blocked_ip + target_field: pihole.summary.query_status.external_blocked_ip + ignore_missing: true + - rename: + field: query_status_external_blocked_null + target_field: pihole.summary.query_status.external_blocked_null + ignore_missing: true + - rename: + field: query_status_external_blocked_nxra + target_field: pihole.summary.query_status.external_blocked_nxra + ignore_missing: true + - rename: + field: query_status_gravity_cname + target_field: pihole.summary.query_status.gravity_cname + ignore_missing: true + - rename: + field: query_status_regex_cname + target_field: pihole.summary.query_status.regex_cname + ignore_missing: true + - rename: + field: query_status_denylist_cname + target_field: pihole.summary.query_status.denylist_cname + ignore_missing: true + - rename: + field: query_status_retried + target_field: pihole.summary.query_status.retried + ignore_missing: true + - rename: + field: query_status_retried_dnssec + target_field: pihole.summary.query_status.retried_dnssec + ignore_missing: true + - rename: + field: query_status_in_progress + target_field: pihole.summary.query_status.in_progress + ignore_missing: true + - rename: + field: query_status_dbbusy + target_field: pihole.summary.query_status.dbbusy + ignore_missing: true + - rename: + field: query_status_special_domain + target_field: pihole.summary.query_status.special_domain + ignore_missing: true + - rename: + field: query_status_cache_stale + target_field: pihole.summary.query_status.cache_stale + ignore_missing: true + - rename: + field: query_status_external_blocked_ede15 + target_field: pihole.summary.query_status.external_blocked_ede15 + ignore_missing: true + - rename: + field: query_replies_unknown + target_field: pihole.summary.query_replies.unknown + ignore_missing: true + - rename: + field: query_replies_nodata + target_field: pihole.summary.query_replies.nodata + ignore_missing: true + - rename: + field: query_replies_nxdomain + target_field: pihole.summary.query_replies.nxdomain + ignore_missing: true + - rename: + field: query_replies_cname + target_field: pihole.summary.query_replies.cname + ignore_missing: true + - rename: + field: query_replies_ip + target_field: pihole.summary.query_replies.ip + ignore_missing: true + - rename: + field: query_replies_domain + target_field: pihole.summary.query_replies.domain + ignore_missing: true + - rename: + field: query_replies_rrname + target_field: pihole.summary.query_replies.rrname + ignore_missing: true + - rename: + field: query_replies_servfail + target_field: pihole.summary.query_replies.servfail + ignore_missing: true + - rename: + field: query_replies_refused + target_field: pihole.summary.query_replies.refused + ignore_missing: true + - rename: + field: query_replies_notimp + target_field: pihole.summary.query_replies.notimp + ignore_missing: true + - rename: + field: query_replies_other + target_field: pihole.summary.query_replies.other + ignore_missing: true + - rename: + field: query_replies_dnssec + target_field: pihole.summary.query_replies.dnssec + ignore_missing: true + - rename: + field: query_replies_none + target_field: pihole.summary.query_replies.none + ignore_missing: true + - rename: + field: query_replies_blob + target_field: pihole.summary.query_replies.blob + ignore_missing: true + + # Remove any error fields if present (from successful requests) + - remove: + field: error + ignore_missing: true + +on_failure: + - set: + field: error.message + value: "{{ _ingest.on_failure_message }}" diff --git a/packages/pihole/data_stream/pihole_summary/fields/base-fields.yml b/packages/pihole/data_stream/pihole_summary/fields/base-fields.yml new file mode 100644 index 00000000000..07d416e1b31 --- /dev/null +++ b/packages/pihole/data_stream/pihole_summary/fields/base-fields.yml @@ -0,0 +1,39 @@ +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: data_stream.dataset + type: constant_keyword + description: Data stream dataset. +- name: data_stream.namespace + type: constant_keyword + description: Data stream namespace. +- name: '@timestamp' + type: date + description: Event timestamp. +- name: event.kind + type: keyword + description: Event kind (metric). +- name: event.category + type: keyword + description: Event category. +- name: event.type + type: keyword + description: Event type. +- name: event.module + type: keyword + description: Event module name. +- name: event.dataset + type: keyword + description: Event dataset name. +- name: observer.vendor + type: keyword + description: Observer vendor name. + dimension: true +- name: observer.product + type: keyword + description: Observer product name. + dimension: true +- name: observer.type + type: keyword + description: Observer type. + dimension: true diff --git a/packages/pihole/data_stream/pihole_summary/fields/fields.yml b/packages/pihole/data_stream/pihole_summary/fields/fields.yml new file mode 100644 index 00000000000..89f3059c475 --- /dev/null +++ b/packages/pihole/data_stream/pihole_summary/fields/fields.yml @@ -0,0 +1,244 @@ +- name: pihole.summary.dns_queries_today + type: long + description: Total number of DNS queries today + metric_type: gauge +- name: pihole.summary.ads_blocked_today + type: long + description: Number of ads blocked today + metric_type: gauge +- name: pihole.summary.ads_percentage_today + type: double + description: Percentage of queries blocked today + metric_type: gauge +- name: pihole.summary.domains_being_blocked + type: long + description: Total number of domains in blocklist + metric_type: gauge +- name: pihole.summary.queries_forwarded + type: long + description: Number of queries forwarded to upstream DNS + metric_type: gauge +- name: pihole.summary.queries_cached + type: long + description: Number of queries answered from cache + metric_type: gauge +- name: pihole.summary.clients_ever_seen + type: long + description: Total number of clients ever seen + metric_type: gauge +- name: pihole.summary.unique_clients + type: long + description: Number of unique clients seen today + metric_type: gauge +- name: pihole.summary.unique_domains + type: long + description: Number of unique domains queried today + metric_type: gauge +- name: pihole.summary.query_frequency + type: double + description: Query frequency per minute + metric_type: gauge +- name: pihole.summary.gravity_last_update + type: long + description: Timestamp of last gravity list update + metric_type: gauge +- name: pihole.summary.status + type: keyword + description: Pi-hole status (enabled/disabled) + dimension: true +- name: pihole.summary.query_types.a + type: long + description: Number of A record queries + metric_type: gauge +- name: pihole.summary.query_types.aaaa + type: long + description: Number of AAAA record queries + metric_type: gauge +- name: pihole.summary.query_types.any + type: long + description: Number of ANY record queries + metric_type: gauge +- name: pihole.summary.query_types.srv + type: long + description: Number of SRV record queries + metric_type: gauge +- name: pihole.summary.query_types.soa + type: long + description: Number of SOA record queries + metric_type: gauge +- name: pihole.summary.query_types.ptr + type: long + description: Number of PTR record queries + metric_type: gauge +- name: pihole.summary.query_types.txt + type: long + description: Number of TXT record queries + metric_type: gauge +- name: pihole.summary.query_types.naptr + type: long + description: Number of NAPTR record queries + metric_type: gauge +- name: pihole.summary.query_types.mx + type: long + description: Number of MX record queries + metric_type: gauge +- name: pihole.summary.query_types.ds + type: long + description: Number of DS record queries + metric_type: gauge +- name: pihole.summary.query_types.rrsig + type: long + description: Number of RRSIG record queries + metric_type: gauge +- name: pihole.summary.query_types.dnskey + type: long + description: Number of DNSKEY record queries + metric_type: gauge +- name: pihole.summary.query_types.ns + type: long + description: Number of NS record queries + metric_type: gauge +- name: pihole.summary.query_types.svcb + type: long + description: Number of SVCB record queries + metric_type: gauge +- name: pihole.summary.query_types.https + type: long + description: Number of HTTPS record queries + metric_type: gauge +- name: pihole.summary.query_types.other + type: long + description: Number of other record type queries + metric_type: gauge +- name: pihole.summary.query_status.unknown + type: long + description: Number of queries with unknown status + metric_type: gauge +- name: pihole.summary.query_status.gravity + type: long + description: Number of queries blocked by gravity + metric_type: gauge +- name: pihole.summary.query_status.forwarded + type: long + description: Number of queries forwarded to upstream + metric_type: gauge +- name: pihole.summary.query_status.cache + type: long + description: Number of queries answered from cache + metric_type: gauge +- name: pihole.summary.query_status.regex + type: long + description: Number of queries blocked by regex + metric_type: gauge +- name: pihole.summary.query_status.denylist + type: long + description: Number of queries blocked by denylist + metric_type: gauge +- name: pihole.summary.query_status.external_blocked_ip + type: long + description: Number of queries blocked by external IP blocking + metric_type: gauge +- name: pihole.summary.query_status.external_blocked_null + type: long + description: Number of queries blocked by external null blocking + metric_type: gauge +- name: pihole.summary.query_status.external_blocked_nxra + type: long + description: Number of queries blocked by external NXRA blocking + metric_type: gauge +- name: pihole.summary.query_status.gravity_cname + type: long + description: Number of queries blocked by gravity CNAME + metric_type: gauge +- name: pihole.summary.query_status.regex_cname + type: long + description: Number of queries blocked by regex CNAME + metric_type: gauge +- name: pihole.summary.query_status.denylist_cname + type: long + description: Number of queries blocked by denylist CNAME + metric_type: gauge +- name: pihole.summary.query_status.retried + type: long + description: Number of retried queries + metric_type: gauge +- name: pihole.summary.query_status.retried_dnssec + type: long + description: Number of retried DNSSEC queries + metric_type: gauge +- name: pihole.summary.query_status.in_progress + type: long + description: Number of queries in progress + metric_type: gauge +- name: pihole.summary.query_status.dbbusy + type: long + description: Number of queries delayed due to database busy + metric_type: gauge +- name: pihole.summary.query_status.special_domain + type: long + description: Number of queries for special domains + metric_type: gauge +- name: pihole.summary.query_status.cache_stale + type: long + description: Number of queries answered from stale cache + metric_type: gauge +- name: pihole.summary.query_status.external_blocked_ede15 + type: long + description: Number of queries blocked by external EDE15 + metric_type: gauge +- name: pihole.summary.query_replies.unknown + type: long + description: Number of queries with unknown reply + metric_type: gauge +- name: pihole.summary.query_replies.nodata + type: long + description: Number of NODATA replies + metric_type: gauge +- name: pihole.summary.query_replies.nxdomain + type: long + description: Number of NXDOMAIN replies + metric_type: gauge +- name: pihole.summary.query_replies.cname + type: long + description: Number of CNAME replies + metric_type: gauge +- name: pihole.summary.query_replies.ip + type: long + description: Number of IP address replies + metric_type: gauge +- name: pihole.summary.query_replies.domain + type: long + description: Number of domain replies + metric_type: gauge +- name: pihole.summary.query_replies.rrname + type: long + description: Number of RRNAME replies + metric_type: gauge +- name: pihole.summary.query_replies.servfail + type: long + description: Number of SERVFAIL replies + metric_type: gauge +- name: pihole.summary.query_replies.refused + type: long + description: Number of REFUSED replies + metric_type: gauge +- name: pihole.summary.query_replies.notimp + type: long + description: Number of NOTIMP replies + metric_type: gauge +- name: pihole.summary.query_replies.other + type: long + description: Number of other reply types + metric_type: gauge +- name: pihole.summary.query_replies.dnssec + type: long + description: Number of DNSSEC replies + metric_type: gauge +- name: pihole.summary.query_replies.none + type: long + description: Number of queries with no reply + metric_type: gauge +- name: pihole.summary.query_replies.blob + type: long + description: Number of BLOB replies + metric_type: gauge diff --git a/packages/pihole/data_stream/pihole_summary/manifest.yml b/packages/pihole/data_stream/pihole_summary/manifest.yml new file mode 100644 index 00000000000..57b1acfc15c --- /dev/null +++ b/packages/pihole/data_stream/pihole_summary/manifest.yml @@ -0,0 +1,68 @@ +title: "Summary Statistics" +type: metrics +streams: + - input: cel + title: Collect Pi-hole summary statistics + description: Collect overall DNS statistics and Pi-hole status + template_path: cel.yml.hbs + vars: + - name: interval + type: text + title: Collection Interval + description: How often to collect summary statistics (default 60s) + default: 60s + required: false + show_user: true + - name: enable_request_tracer + type: bool + title: Enable Request Tracer + description: Enable request tracing for debugging + default: false + required: false + show_user: false + - name: proxy_url + type: text + title: Proxy URL + description: Optional HTTP proxy for connecting to Pi-hole + required: false + show_user: false + - name: ssl + type: yaml + title: SSL Configuration + description: Custom SSL/TLS settings for HTTPS connections + required: false + show_user: false + - name: http_client_timeout + type: text + title: HTTP Client Timeout + description: Request timeout duration (default 30s) + default: 30s + required: false + show_user: false + - name: preserve_original_event + type: bool + title: Preserve Original Event + description: Preserve the original event data for debugging + default: false + required: false + show_user: false + - name: tags + type: text + title: Tags + multi: true + default: + - pihole-summary + required: false + show_user: false + - name: processors + type: yaml + title: Processors + description: Custom processors for data processing + required: false + show_user: false +elasticsearch: + source_mode: synthetic + index_mode: time_series + index_template: + mappings: + subobjects: false diff --git a/packages/pihole/data_stream/pihole_summary/sample_event.json b/packages/pihole/data_stream/pihole_summary/sample_event.json new file mode 100644 index 00000000000..6c82a7c1234 --- /dev/null +++ b/packages/pihole/data_stream/pihole_summary/sample_event.json @@ -0,0 +1,98 @@ +{ + "@timestamp": "2024-12-30T16:00:00.000Z", + "data_stream": { + "type": "metrics", + "dataset": "pihole.pihole_summary", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "metric", + "category": [ + "network" + ], + "type": [ + "info" + ], + "module": "pihole", + "dataset": "pihole.pihole_summary" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "pihole": { + "summary": { + "dns_queries_today": 579557, + "ads_blocked_today": 33293, + "ads_percentage_today": 5.74, + "domains_being_blocked": 86832, + "queries_forwarded": 16036, + "queries_cached": 527297, + "clients_ever_seen": 117, + "unique_clients": 72, + "unique_domains": 17410, + "query_frequency": 6.33, + "gravity_last_update": 1767139800, + "status": "enabled", + "query_types": { + "a": 342193, + "aaaa": 186269, + "any": 0, + "srv": 133, + "soa": 417, + "ptr": 15328, + "txt": 1310, + "naptr": 56, + "mx": 640, + "ds": 0, + "rrsig": 0, + "dnskey": 0, + "ns": 0, + "svcb": 2819, + "https": 30383, + "other": 9 + }, + "query_status": { + "unknown": 1, + "gravity": 5240, + "forwarded": 16025, + "cache": 440217, + "regex": 11594, + "denylist": 0, + "external_blocked_ip": 0, + "external_blocked_null": 0, + "external_blocked_nxra": 0, + "gravity_cname": 41, + "regex_cname": 0, + "denylist_cname": 0, + "retried": 11, + "retried_dnssec": 0, + "in_progress": 2930, + "dbbusy": 0, + "special_domain": 16418, + "cache_stale": 87080, + "external_blocked_ede15": 0 + }, + "query_replies": { + "unknown": 150, + "nodata": 203929, + "nxdomain": 28966, + "cname": 50400, + "ip": 292083, + "domain": 1717, + "rrname": 26, + "servfail": 24, + "refused": 0, + "notimp": 0, + "other": 0, + "dnssec": 0, + "none": 0, + "blob": 2262 + } + } + } +} diff --git a/packages/pihole/data_stream/query_history/agent/stream/cel.yml.hbs b/packages/pihole/data_stream/query_history/agent/stream/cel.yml.hbs new file mode 100644 index 00000000000..0945b50145b --- /dev/null +++ b/packages/pihole/data_stream/query_history/agent/stream/cel.yml.hbs @@ -0,0 +1,136 @@ +config_version: 2 +interval: {{interval}} +resource.tracer: + enabled: {{enable_request_tracer}} + filename: "../../logs/cel/http-request-trace-*.ndjson" + maxbackups: 5 +{{#if proxy_url}} +resource.proxy_url: {{proxy_url}} +{{/if}} +{{#if ssl}} +resource.ssl: {{ssl}} +{{/if}} +{{#if http_client_timeout}} +resource.timeout: {{http_client_timeout}} +{{/if}} +resource.url: {{url}} +state: + url: {{url}} +redact: + fields: + - api_password +program: | + // Authenticate with Pi-hole API + post_request( + state.url.trim_right("/") + "/api/auth", + "application/json", + { + "password": "{{api_password}}" + }.encode_json() + ).do_request().as(auth_resp, + auth_resp.StatusCode != 200 ? + { + "events": [{ + "error": { + "message": "Authentication failed: HTTP " + string(auth_resp.StatusCode) + } + }], + "want_more": false + } + : + auth_resp.Body.decode_json().as(auth_body, + !has(auth_body.session) || !has(auth_body.session.sid) ? + { + "events": [{ + "error": { + "message": "Authentication response missing session ID" + } + }], + "want_more": false + } + : + // Get query history metrics using session ID + request( + "GET", + state.url.trim_right("/") + "/api/history" + ).with({ + "Header": { + "X-FTL-SID": [auth_body.session.sid] + } + }).do_request().as(history_resp, + // Process history response first + history_resp.StatusCode != 200 ? + // History request failed, still try to logout before returning error + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(logout_resp, { + "events": [{ + "error": { + "message": "Failed to fetch history: HTTP " + string(history_resp.StatusCode) + } + }], + "want_more": false + }) + : + // History request succeeded, process response + history_resp.Body.decode_json().as(history_body, + // Logout after successful collection + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(logout_resp, + // Force logout completion by checking status + logout_resp.StatusCode == 204 ? + (!has(history_body.history) ? + { + "events": [], + "want_more": false + } + : + { + "events": history_body.history.map(item, { + "timestamp": int(item.timestamp), + "total": int(item.total), + "cached": int(item.cached), + "blocked": int(item.blocked), + "forwarded": int(item.forwarded) + }), + "want_more": false + } + ) + : + (!has(history_body.history) ? + { + "events": [], + "want_more": false + } + : + { + "events": history_body.history.map(item, { + "timestamp": int(item.timestamp), + "total": int(item.total), + "cached": int(item.cached), + "blocked": int(item.blocked), + "forwarded": int(item.forwarded), + "_logout_failed": "HTTP " + string(logout_resp.StatusCode) + }), + "want_more": false + } + ) + ) + ) + ) + ) + ) +tags: +{{#if preserve_original_event}} + - preserve_original_event +{{/if}} +{{#if tags}} +{{#each tags as |tag|}} + - {{tag}} +{{/each}} +{{/if}} +{{#if processors}} +processors: +{{processors}} +{{/if}} diff --git a/packages/pihole/data_stream/query_history/elasticsearch/ingest_pipeline/default.yml b/packages/pihole/data_stream/query_history/elasticsearch/ingest_pipeline/default.yml new file mode 100644 index 00000000000..a30ba4e7802 --- /dev/null +++ b/packages/pihole/data_stream/query_history/elasticsearch/ingest_pipeline/default.yml @@ -0,0 +1,71 @@ +--- +description: Pipeline for processing Pi-hole query history metrics +processors: + # Convert Unix timestamp to @timestamp + - date: + field: timestamp + target_field: '@timestamp' + formats: + - UNIX + if: ctx.timestamp != null + + # Set event fields + - set: + field: event.kind + value: metric + - set: + field: event.category + value: ["network"] + - set: + field: event.type + value: ["info"] + - set: + field: event.module + value: pihole + - set: + field: event.dataset + value: pihole.query_history + + # Set observer fields + - set: + field: observer.vendor + value: Pi-hole + - set: + field: observer.product + value: Pi-hole + - set: + field: observer.type + value: dns + + # Rename metric fields to pihole.query_history namespace + - rename: + field: total + target_field: pihole.query_history.total + ignore_missing: true + - rename: + field: cached + target_field: pihole.query_history.cached + ignore_missing: true + - rename: + field: blocked + target_field: pihole.query_history.blocked + ignore_missing: true + - rename: + field: forwarded + target_field: pihole.query_history.forwarded + ignore_missing: true + + # Remove the raw timestamp field + - remove: + field: timestamp + ignore_missing: true + + # Remove any error fields if present (from successful requests) + - remove: + field: error + ignore_missing: true + +on_failure: + - set: + field: error.message + value: "{{ _ingest.on_failure_message }}" diff --git a/packages/pihole/data_stream/query_history/fields/base-fields.yml b/packages/pihole/data_stream/query_history/fields/base-fields.yml new file mode 100644 index 00000000000..07d416e1b31 --- /dev/null +++ b/packages/pihole/data_stream/query_history/fields/base-fields.yml @@ -0,0 +1,39 @@ +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: data_stream.dataset + type: constant_keyword + description: Data stream dataset. +- name: data_stream.namespace + type: constant_keyword + description: Data stream namespace. +- name: '@timestamp' + type: date + description: Event timestamp. +- name: event.kind + type: keyword + description: Event kind (metric). +- name: event.category + type: keyword + description: Event category. +- name: event.type + type: keyword + description: Event type. +- name: event.module + type: keyword + description: Event module name. +- name: event.dataset + type: keyword + description: Event dataset name. +- name: observer.vendor + type: keyword + description: Observer vendor name. + dimension: true +- name: observer.product + type: keyword + description: Observer product name. + dimension: true +- name: observer.type + type: keyword + description: Observer type. + dimension: true diff --git a/packages/pihole/data_stream/query_history/fields/fields.yml b/packages/pihole/data_stream/query_history/fields/fields.yml new file mode 100644 index 00000000000..81b5d0c9292 --- /dev/null +++ b/packages/pihole/data_stream/query_history/fields/fields.yml @@ -0,0 +1,16 @@ +- name: pihole.query_history.total + type: long + description: Total number of DNS queries in this time period + metric_type: gauge +- name: pihole.query_history.cached + type: long + description: Number of queries answered from cache in this time period + metric_type: gauge +- name: pihole.query_history.blocked + type: long + description: Number of queries blocked in this time period + metric_type: gauge +- name: pihole.query_history.forwarded + type: long + description: Number of queries forwarded to upstream DNS servers in this time period + metric_type: gauge diff --git a/packages/pihole/data_stream/query_history/manifest.yml b/packages/pihole/data_stream/query_history/manifest.yml new file mode 100644 index 00000000000..659dcedd9ca --- /dev/null +++ b/packages/pihole/data_stream/query_history/manifest.yml @@ -0,0 +1,68 @@ +title: "Query History Metrics" +type: metrics +streams: + - input: cel + title: Collect Pi-hole query history metrics + description: Collect time-series metrics showing query counts over time from Pi-hole + template_path: cel.yml.hbs + vars: + - name: interval + type: text + title: Collection Interval + description: How often to collect query history metrics (default 10m) + default: 10m + required: false + show_user: true + - name: enable_request_tracer + type: bool + title: Enable Request Tracer + description: Enable request tracing for debugging + default: false + required: false + show_user: false + - name: proxy_url + type: text + title: Proxy URL + description: Optional HTTP proxy for connecting to Pi-hole + required: false + show_user: false + - name: ssl + type: yaml + title: SSL Configuration + description: Custom SSL/TLS settings for HTTPS connections + required: false + show_user: false + - name: http_client_timeout + type: text + title: HTTP Client Timeout + description: Request timeout duration (default 30s) + default: 30s + required: false + show_user: false + - name: preserve_original_event + type: bool + title: Preserve Original Event + description: Preserve the original event data for debugging + default: false + required: false + show_user: false + - name: tags + type: text + title: Tags + multi: true + default: + - pihole-query_history + required: false + show_user: false + - name: processors + type: yaml + title: Processors + description: Custom processors for data processing + required: false + show_user: false +elasticsearch: + source_mode: synthetic + index_mode: time_series + index_template: + mappings: + subobjects: false diff --git a/packages/pihole/data_stream/query_history/sample_event.json b/packages/pihole/data_stream/query_history/sample_event.json new file mode 100644 index 00000000000..95e8ad255e9 --- /dev/null +++ b/packages/pihole/data_stream/query_history/sample_event.json @@ -0,0 +1,35 @@ +{ + "@timestamp": "2024-12-30T14:15:00.000Z", + "data_stream": { + "type": "metrics", + "dataset": "pihole.query_history", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "metric", + "category": [ + "network" + ], + "type": [ + "info" + ], + "module": "pihole", + "dataset": "pihole.query_history" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "pihole": { + "query_history": { + "total": 487, + "cached": 245, + "blocked": 98, + "forwarded": 144 + } + } +} diff --git a/packages/pihole/data_stream/top_clients/agent/stream/cel.yml.hbs b/packages/pihole/data_stream/top_clients/agent/stream/cel.yml.hbs new file mode 100644 index 00000000000..e06269dae78 --- /dev/null +++ b/packages/pihole/data_stream/top_clients/agent/stream/cel.yml.hbs @@ -0,0 +1,153 @@ +config_version: 2 +interval: {{interval}} +resource.tracer: + enabled: {{enable_request_tracer}} + filename: "../../logs/cel/http-request-trace-*.ndjson" + maxbackups: 5 +{{#if proxy_url}} +resource.proxy_url: {{proxy_url}} +{{/if}} +{{#if ssl}} +resource.ssl: {{ssl}} +{{/if}} +{{#if http_client_timeout}} +resource.timeout: {{http_client_timeout}} +{{/if}} +resource.url: {{url}} +state: + url: {{url}} +redact: + fields: + - api_password +program: | + // Authenticate with Pi-hole API + post_request( + state.url.trim_right("/") + "/api/auth", + "application/json", + { + "password": "{{api_password}}" + }.encode_json() + ).do_request().as(auth_resp, + auth_resp.StatusCode != 200 ? + { + "events": [{ + "error": { + "message": "Authentication failed: HTTP " + string(auth_resp.StatusCode) + } + }], + "want_more": false + } + : + auth_resp.Body.decode_json().as(auth_body, + !has(auth_body.session) || !has(auth_body.session.sid) ? + { + "events": [{ + "error": { + "message": "Authentication response missing session ID" + } + }], + "want_more": false + } + : + // Get top allowed clients using session ID + request( + "GET", + state.url.trim_right("/") + "/api/stats/top_clients?blocked=false" + ).with({ + "Header": { + "X-FTL-SID": [auth_body.session.sid] + } + }).do_request().as(allowed_resp, + // Check if first request failed - if so, logout immediately + allowed_resp.StatusCode != 200 ? + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(cleanup, { + "events": [{ + "error": { + "message": "Failed to fetch top allowed clients: HTTP " + string(allowed_resp.StatusCode) + } + }], + "want_more": false + }) + : + allowed_resp.Body.decode_json().as(allowed_body, + // Get top blocked clients using session ID + request( + "GET", + state.url.trim_right("/") + "/api/stats/top_clients?blocked=true" + ).with({ + "Header": { + "X-FTL-SID": [auth_body.session.sid] + } + }).do_request().as(blocked_resp, + // Process blocked response first + blocked_resp.StatusCode != 200 ? + // Blocked request failed, still try to logout before returning error + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(logout_resp, { + "events": [{ + "error": { + "message": "Failed to fetch top blocked clients: HTTP " + string(blocked_resp.StatusCode) + } + }], + "want_more": false + }) + : + // Blocked request succeeded, process both responses + blocked_resp.Body.decode_json().as(blocked_body, + // Logout after successful collection + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(logout_resp, + // Force logout completion by checking status + logout_resp.StatusCode == 204 ? { + "events": (has(allowed_body.clients) ? allowed_body.clients.map(client, { + "client_name": has(client.name) && client.name != "" ? string(client.name) : "", + "client_ip": has(client.ip) ? string(client.ip) : "", + "query_count": has(client.count) ? int(client.count) : 0, + "query_action": "allowed" + }) : []) + (has(blocked_body.clients) ? blocked_body.clients.map(client, { + "client_name": has(client.name) && client.name != "" ? string(client.name) : "", + "client_ip": has(client.ip) ? string(client.ip) : "", + "query_count": has(client.count) ? int(client.count) : 0, + "query_action": "blocked" + }) : []), + "want_more": false + } : { + "events": (has(allowed_body.clients) ? allowed_body.clients.map(client, { + "client_name": has(client.name) && client.name != "" ? string(client.name) : "", + "client_ip": has(client.ip) ? string(client.ip) : "", + "query_count": has(client.count) ? int(client.count) : 0, + "query_action": "allowed", + "_logout_failed": "HTTP " + string(logout_resp.StatusCode) + }) : []) + (has(blocked_body.clients) ? blocked_body.clients.map(client, { + "client_name": has(client.name) && client.name != "" ? string(client.name) : "", + "client_ip": has(client.ip) ? string(client.ip) : "", + "query_count": has(client.count) ? int(client.count) : 0, + "query_action": "blocked", + "_logout_failed": "HTTP " + string(logout_resp.StatusCode) + }) : []), + "want_more": false + } + ) + ) + ) + ) + ) + ) + ) +tags: +{{#if preserve_original_event}} + - preserve_original_event +{{/if}} +{{#if tags}} +{{#each tags as |tag|}} + - {{tag}} +{{/each}} +{{/if}} +{{#if processors}} +processors: +{{processors}} +{{/if}} diff --git a/packages/pihole/data_stream/top_clients/elasticsearch/ingest_pipeline/default.yml b/packages/pihole/data_stream/top_clients/elasticsearch/ingest_pipeline/default.yml new file mode 100644 index 00000000000..f06fb52759a --- /dev/null +++ b/packages/pihole/data_stream/top_clients/elasticsearch/ingest_pipeline/default.yml @@ -0,0 +1,62 @@ +--- +description: Pipeline for processing Pi-hole top clients statistics +processors: + # Set event fields + - set: + field: event.kind + value: metric + - set: + field: event.category + value: ["network"] + - set: + field: event.type + value: ["info"] + - set: + field: event.module + value: pihole + - set: + field: event.dataset + value: pihole.top_clients + + # Set observer fields + - set: + field: observer.vendor + value: Pi-hole + - set: + field: observer.product + value: Pi-hole + - set: + field: observer.type + value: dns + + # Map client fields to ECS + - rename: + field: client_ip + target_field: client.ip + ignore_missing: true + - rename: + field: client_name + target_field: client.domain + ignore_missing: true + + # Map query count to pihole namespace + - rename: + field: query_count + target_field: pihole.top_clients.query_count + ignore_missing: true + + # Map query action to pihole namespace + - rename: + field: query_action + target_field: pihole.top_clients.query_action + ignore_missing: true + + # Remove any error fields if present (from successful requests) + - remove: + field: error + ignore_missing: true + +on_failure: + - set: + field: error.message + value: "{{ _ingest.on_failure_message }}" diff --git a/packages/pihole/data_stream/top_clients/fields/base-fields.yml b/packages/pihole/data_stream/top_clients/fields/base-fields.yml new file mode 100644 index 00000000000..0b9c4f50d0a --- /dev/null +++ b/packages/pihole/data_stream/top_clients/fields/base-fields.yml @@ -0,0 +1,47 @@ +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: data_stream.dataset + type: constant_keyword + description: Data stream dataset. +- name: data_stream.namespace + type: constant_keyword + description: Data stream namespace. +- name: '@timestamp' + type: date + description: Event timestamp. +- name: event.kind + type: keyword + description: Event kind (metric). +- name: event.category + type: keyword + description: Event category. +- name: event.type + type: keyword + description: Event type. +- name: event.module + type: keyword + description: Event module name. +- name: event.dataset + type: keyword + description: Event dataset name. +- name: observer.vendor + type: keyword + description: Observer vendor name. + dimension: true +- name: observer.product + type: keyword + description: Observer product name. + dimension: true +- name: observer.type + type: keyword + description: Observer type. + dimension: true +- name: client.ip + type: ip + description: Client IP address. + dimension: true +- name: client.domain + type: keyword + description: Client hostname/domain. + dimension: true diff --git a/packages/pihole/data_stream/top_clients/fields/fields.yml b/packages/pihole/data_stream/top_clients/fields/fields.yml new file mode 100644 index 00000000000..28cfdd02be2 --- /dev/null +++ b/packages/pihole/data_stream/top_clients/fields/fields.yml @@ -0,0 +1,8 @@ +- name: pihole.top_clients.query_action + type: keyword + description: Action taken on queries (allowed or blocked) + dimension: true +- name: pihole.top_clients.query_count + type: long + description: Number of DNS queries from this client + metric_type: gauge diff --git a/packages/pihole/data_stream/top_clients/manifest.yml b/packages/pihole/data_stream/top_clients/manifest.yml new file mode 100644 index 00000000000..98c5e228fd5 --- /dev/null +++ b/packages/pihole/data_stream/top_clients/manifest.yml @@ -0,0 +1,68 @@ +title: "Top Clients" +type: metrics +streams: + - input: cel + title: Collect Pi-hole top clients statistics + description: Collect statistics showing which clients are making the most DNS queries + template_path: cel.yml.hbs + vars: + - name: interval + type: text + title: Collection Interval + description: How often to collect top clients statistics (default 1h) + default: 1h + required: false + show_user: true + - name: enable_request_tracer + type: bool + title: Enable Request Tracer + description: Enable request tracing for debugging + default: false + required: false + show_user: false + - name: proxy_url + type: text + title: Proxy URL + description: Optional HTTP proxy for connecting to Pi-hole + required: false + show_user: false + - name: ssl + type: yaml + title: SSL Configuration + description: Custom SSL/TLS settings for HTTPS connections + required: false + show_user: false + - name: http_client_timeout + type: text + title: HTTP Client Timeout + description: Request timeout duration (default 30s) + default: 30s + required: false + show_user: false + - name: preserve_original_event + type: bool + title: Preserve Original Event + description: Preserve the original event data for debugging + default: false + required: false + show_user: false + - name: tags + type: text + title: Tags + multi: true + default: + - pihole-top_clients + required: false + show_user: false + - name: processors + type: yaml + title: Processors + description: Custom processors for data processing + required: false + show_user: false +elasticsearch: + source_mode: synthetic + index_mode: time_series + index_template: + mappings: + subobjects: false diff --git a/packages/pihole/data_stream/top_clients/sample_event.json b/packages/pihole/data_stream/top_clients/sample_event.json new file mode 100644 index 00000000000..7e5bd179368 --- /dev/null +++ b/packages/pihole/data_stream/top_clients/sample_event.json @@ -0,0 +1,37 @@ +{ + "@timestamp": "2024-12-30T16:30:00.000Z", + "data_stream": { + "type": "metrics", + "dataset": "pihole.top_clients", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "metric", + "category": [ + "network" + ], + "type": [ + "info" + ], + "module": "pihole", + "dataset": "pihole.top_clients" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "client": { + "ip": "192.168.1.100", + "domain": "workstation.example.com" + }, + "pihole": { + "top_clients": { + "query_action": "allowed", + "query_count": 126051 + } + } +} diff --git a/packages/pihole/data_stream/top_domains/agent/stream/cel.yml.hbs b/packages/pihole/data_stream/top_domains/agent/stream/cel.yml.hbs new file mode 100644 index 00000000000..ed2100fe129 --- /dev/null +++ b/packages/pihole/data_stream/top_domains/agent/stream/cel.yml.hbs @@ -0,0 +1,157 @@ +config_version: 2 +interval: {{interval}} +resource.tracer: + enabled: {{enable_request_tracer}} + filename: "../../logs/cel/http-request-trace-*.ndjson" + maxbackups: 5 +{{#if proxy_url}} +resource.proxy_url: {{proxy_url}} +{{/if}} +{{#if ssl}} +resource.ssl: {{ssl}} +{{/if}} +{{#if http_client_timeout}} +resource.timeout: {{http_client_timeout}} +{{/if}} +resource.url: {{url}} +state: + url: {{url}} +redact: + fields: + - api_password +program: | + // Authenticate with Pi-hole API + post_request( + state.url.trim_right("/") + "/api/auth", + "application/json", + { + "password": "{{api_password}}" + }.encode_json() + ).do_request().as(auth_resp, + auth_resp.StatusCode != 200 ? + { + "events": [{ + "error": { + "message": "Authentication failed: HTTP " + string(auth_resp.StatusCode) + } + }], + "want_more": false + } + : + auth_resp.Body.decode_json().as(auth_body, + !has(auth_body.session) || !has(auth_body.session.sid) ? + { + "events": [{ + "error": { + "message": "Authentication response missing session ID" + } + }], + "want_more": false + } + : + // Get top allowed domains using session ID + request( + "GET", + state.url.trim_right("/") + "/api/stats/top_domains?blocked=false" + ).with({ + "Header": { + "X-FTL-SID": [auth_body.session.sid] + } + }).do_request().as(allowed_resp, + // Check if first request failed - if so, logout immediately + allowed_resp.StatusCode != 200 ? + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(cleanup, { + "events": [{ + "error": { + "message": "Failed to fetch top allowed domains: HTTP " + string(allowed_resp.StatusCode) + } + }], + "want_more": false + }) + : + allowed_resp.Body.decode_json().as(allowed_body, + // Get top blocked domains using session ID + request( + "GET", + state.url.trim_right("/") + "/api/stats/top_domains?blocked=true" + ).with({ + "Header": { + "X-FTL-SID": [auth_body.session.sid] + } + }).do_request().as(blocked_resp, + // Process blocked response first + blocked_resp.StatusCode != 200 ? + // Blocked request failed, still try to logout before returning error + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(logout_resp, { + "events": [{ + "error": { + "message": "Failed to fetch top blocked domains: HTTP " + string(blocked_resp.StatusCode) + } + }], + "want_more": false + }) + : + // Blocked request succeeded, process both responses + blocked_resp.Body.decode_json().as(blocked_body, + // Logout after successful collection + request("DELETE", state.url.trim_right("/") + "/api/auth").with({ + "Header": {"X-FTL-SID": [auth_body.session.sid]} + }).do_request().as(logout_resp, + // Force logout completion by checking status + logout_resp.StatusCode == 204 ? { + "events": (has(allowed_body.domains) ? allowed_body.domains.map(domain_item, { + "domain_name": has(domain_item.domain) ? string(domain_item.domain) : "", + "query_count": has(domain_item.count) ? int(domain_item.count) : 0, + "total_queries": has(allowed_body.total_queries) ? int(allowed_body.total_queries) : 0, + "blocked_queries": has(allowed_body.blocked_queries) ? int(allowed_body.blocked_queries) : 0, + "query_action": "allowed" + }) : []) + (has(blocked_body.domains) ? blocked_body.domains.map(domain_item, { + "domain_name": has(domain_item.domain) ? string(domain_item.domain) : "", + "query_count": has(domain_item.count) ? int(domain_item.count) : 0, + "total_queries": has(blocked_body.total_queries) ? int(blocked_body.total_queries) : 0, + "blocked_queries": has(blocked_body.blocked_queries) ? int(blocked_body.blocked_queries) : 0, + "query_action": "blocked" + }) : []), + "want_more": false + } : { + "events": (has(allowed_body.domains) ? allowed_body.domains.map(domain_item, { + "domain_name": has(domain_item.domain) ? string(domain_item.domain) : "", + "query_count": has(domain_item.count) ? int(domain_item.count) : 0, + "total_queries": has(allowed_body.total_queries) ? int(allowed_body.total_queries) : 0, + "blocked_queries": has(allowed_body.blocked_queries) ? int(allowed_body.blocked_queries) : 0, + "query_action": "allowed", + "_logout_failed": "HTTP " + string(logout_resp.StatusCode) + }) : []) + (has(blocked_body.domains) ? blocked_body.domains.map(domain_item, { + "domain_name": has(domain_item.domain) ? string(domain_item.domain) : "", + "query_count": has(domain_item.count) ? int(domain_item.count) : 0, + "total_queries": has(blocked_body.total_queries) ? int(blocked_body.total_queries) : 0, + "blocked_queries": has(blocked_body.blocked_queries) ? int(blocked_body.blocked_queries) : 0, + "query_action": "blocked", + "_logout_failed": "HTTP " + string(logout_resp.StatusCode) + }) : []), + "want_more": false + } + ) + ) + ) + ) + ) + ) + ) +tags: +{{#if preserve_original_event}} + - preserve_original_event +{{/if}} +{{#if tags}} +{{#each tags as |tag|}} + - {{tag}} +{{/each}} +{{/if}} +{{#if processors}} +processors: +{{processors}} +{{/if}} diff --git a/packages/pihole/data_stream/top_domains/elasticsearch/ingest_pipeline/default.yml b/packages/pihole/data_stream/top_domains/elasticsearch/ingest_pipeline/default.yml new file mode 100644 index 00000000000..9fdd9a39c58 --- /dev/null +++ b/packages/pihole/data_stream/top_domains/elasticsearch/ingest_pipeline/default.yml @@ -0,0 +1,70 @@ +--- +description: Pipeline for processing Pi-hole top domains statistics +processors: + # Set event fields + - set: + field: event.kind + value: metric + - set: + field: event.category + value: ["network"] + - set: + field: event.type + value: ["info"] + - set: + field: event.module + value: pihole + - set: + field: event.dataset + value: pihole.top_domains + + # Set observer fields + - set: + field: observer.vendor + value: Pi-hole + - set: + field: observer.product + value: Pi-hole + - set: + field: observer.type + value: dns + + # Map domain name to ECS dns.question.name + - rename: + field: domain_name + target_field: dns.question.name + ignore_missing: true + + # Map query count to pihole namespace + - rename: + field: query_count + target_field: pihole.top_domains.query_count + ignore_missing: true + + # Map total queries to pihole namespace + - rename: + field: total_queries + target_field: pihole.top_domains.total_queries + ignore_missing: true + + # Map blocked queries to pihole namespace + - rename: + field: blocked_queries + target_field: pihole.top_domains.blocked_queries + ignore_missing: true + + # Map query action to pihole namespace + - rename: + field: query_action + target_field: pihole.top_domains.query_action + ignore_missing: true + + # Remove any error fields if present (from successful requests) + - remove: + field: error + ignore_missing: true + +on_failure: + - set: + field: error.message + value: "{{ _ingest.on_failure_message }}" diff --git a/packages/pihole/data_stream/top_domains/fields/base-fields.yml b/packages/pihole/data_stream/top_domains/fields/base-fields.yml new file mode 100644 index 00000000000..6d7af9932e5 --- /dev/null +++ b/packages/pihole/data_stream/top_domains/fields/base-fields.yml @@ -0,0 +1,43 @@ +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: data_stream.dataset + type: constant_keyword + description: Data stream dataset. +- name: data_stream.namespace + type: constant_keyword + description: Data stream namespace. +- name: '@timestamp' + type: date + description: Event timestamp. +- name: event.kind + type: keyword + description: Event kind (metric). +- name: event.category + type: keyword + description: Event category. +- name: event.type + type: keyword + description: Event type. +- name: event.module + type: keyword + description: Event module name. +- name: event.dataset + type: keyword + description: Event dataset name. +- name: observer.vendor + type: keyword + description: Observer vendor name. + dimension: true +- name: observer.product + type: keyword + description: Observer product name. + dimension: true +- name: observer.type + type: keyword + description: Observer type. + dimension: true +- name: dns.question.name + type: keyword + description: Domain name being queried. + dimension: true diff --git a/packages/pihole/data_stream/top_domains/fields/fields.yml b/packages/pihole/data_stream/top_domains/fields/fields.yml new file mode 100644 index 00000000000..42bb66f47ef --- /dev/null +++ b/packages/pihole/data_stream/top_domains/fields/fields.yml @@ -0,0 +1,16 @@ +- name: pihole.top_domains.query_action + type: keyword + description: Action taken on queries (allowed or blocked) + dimension: true +- name: pihole.top_domains.query_count + type: long + description: Number of DNS queries for this domain + metric_type: gauge +- name: pihole.top_domains.total_queries + type: long + description: Total number of DNS queries across all domains + metric_type: gauge +- name: pihole.top_domains.blocked_queries + type: long + description: Total number of blocked DNS queries + metric_type: gauge diff --git a/packages/pihole/data_stream/top_domains/manifest.yml b/packages/pihole/data_stream/top_domains/manifest.yml new file mode 100644 index 00000000000..8ef93e1633c --- /dev/null +++ b/packages/pihole/data_stream/top_domains/manifest.yml @@ -0,0 +1,68 @@ +title: "Top Domains" +type: metrics +streams: + - input: cel + title: Collect Pi-hole top domains statistics + description: Collect statistics showing which domains are queried most frequently + template_path: cel.yml.hbs + vars: + - name: interval + type: text + title: Collection Interval + description: How often to collect top domains statistics (default 1h) + default: 1h + required: false + show_user: true + - name: enable_request_tracer + type: bool + title: Enable Request Tracer + description: Enable request tracing for debugging + default: false + required: false + show_user: false + - name: proxy_url + type: text + title: Proxy URL + description: Optional HTTP proxy for connecting to Pi-hole + required: false + show_user: false + - name: ssl + type: yaml + title: SSL Configuration + description: Custom SSL/TLS settings for HTTPS connections + required: false + show_user: false + - name: http_client_timeout + type: text + title: HTTP Client Timeout + description: Request timeout duration (default 30s) + default: 30s + required: false + show_user: false + - name: preserve_original_event + type: bool + title: Preserve Original Event + description: Preserve the original event data for debugging + default: false + required: false + show_user: false + - name: tags + type: text + title: Tags + multi: true + default: + - pihole-top_domains + required: false + show_user: false + - name: processors + type: yaml + title: Processors + description: Custom processors for data processing + required: false + show_user: false +elasticsearch: + source_mode: synthetic + index_mode: time_series + index_template: + mappings: + subobjects: false diff --git a/packages/pihole/data_stream/top_domains/sample_event.json b/packages/pihole/data_stream/top_domains/sample_event.json new file mode 100644 index 00000000000..dc02d8c5441 --- /dev/null +++ b/packages/pihole/data_stream/top_domains/sample_event.json @@ -0,0 +1,40 @@ +{ + "@timestamp": "2024-12-30T18:30:00.000Z", + "data_stream": { + "type": "metrics", + "dataset": "pihole.top_domains", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "metric", + "category": [ + "network" + ], + "type": [ + "info" + ], + "module": "pihole", + "dataset": "pihole.top_domains" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "dns": { + "question": { + "name": "api.example.com" + } + }, + "pihole": { + "top_domains": { + "query_action": "allowed", + "query_count": 235054, + "total_queries": 577849, + "blocked_queries": 30142 + } + } +} diff --git a/packages/pihole/docs/README.md b/packages/pihole/docs/README.md new file mode 100644 index 00000000000..fc95455dd39 --- /dev/null +++ b/packages/pihole/docs/README.md @@ -0,0 +1,718 @@ + + + +# Pi-hole Integration + +The Pi-hole Integration allows you to monitor DNS query activity from your Pi-hole instances. Pi-hole is a network-level DNS filtering application that blocks ads and trackers. + +Use the Pi-hole Integration to collect DNS query logs from your Pi-hole instances. Then visualize that data in Kibana, create alerts to notify you of DNS issues or suspicious activity, and analyze network traffic patterns. + +## Data streams + +The Pi-hole Integration collects both logs and metrics that provide insights into DNS activity on your network: + +### Logs +- **DNS Queries**: Individual DNS query records with query type, resolution status, client information, upstream server details, and DNSSEC status + +### Metrics +- **Query History**: Time-series metrics showing DNS query volume over time, broken down by total queries, cached responses, blocked queries, and forwarded queries +- **Pi-hole Summary**: Comprehensive statistics including total queries, blocking effectiveness, query type breakdowns (A, AAAA, PTR, etc.), query status breakdowns (cache hits, forwarded, blocked, etc.), and query reply types +- **Top Clients**: Metrics identifying the most active DNS clients on your network, with separate tracking for allowed and blocked queries +- **Top Domains**: Metrics showing the most frequently queried domains, with separate tracking for allowed and blocked domains + +## Requirements + +You need Elasticsearch for storing and searching your data and Kibana for visualizing and managing it. +You can use our hosted Elasticsearch Service on Elastic Cloud, which is recommended, or self-manage the Elastic Stack on your own hardware. + +### Pi-hole Requirements + +- Pi-hole instance (v6.0 or later recommended) with API access enabled +- Admin panel password for authentication +- Network connectivity from the Elastic Agent to the Pi-hole instance +- Pi-hole API endpoint accessible at `http(s):///api/` + +## Setup + +For step-by-step instructions on how to set up an integration, see the +[Getting started](https://www.elastic.co/guide/en/welcome-to-elastic/current/getting-started-observability.html) guide. + +### Pi-hole API Authentication + +This integration uses Pi-hole's session-based authentication mechanism: + +1. The integration POSTs the admin password to `/api/auth` to obtain a session ID (SID) +2. The SID is included in the `X-FTL-SID` header for all subsequent API requests +3. After data collection completes, the session is terminated with a DELETE request to `/api/auth` + +Each collection cycle obtains a fresh session ID and terminates it after use, preventing authentication errors from session expiration. + +### Configuration + +**Required Fields:** +- **Pi-hole URL**: The URL of your Pi-hole instance (e.g., `http://192.168.1.1` or `https://pihole.example.com`) +- **API Password**: Your Pi-hole admin panel password + +**Optional Fields:** +- **Collection Interval**: How often to collect DNS queries (default: 60s). For high-volume networks (>400 queries/minute), consider reducing to 10-15 seconds to avoid missing queries. +- **Proxy URL**: Optional HTTP proxy for connecting to Pi-hole +- **SSL Configuration**: Custom SSL/TLS settings for HTTPS connections +- **HTTP Client Timeout**: Request timeout duration (default: 30s) + +### Data Collection Strategy + +**DNS Queries (Logs):** +The integration uses timestamp-based pagination to avoid duplicate records: +- On the first run, it collects up to 1000 most recent queries +- On subsequent runs, it fetches only queries newer than the last collection using the `from` parameter +- The timestamp cursor persists across agent restarts and integration upgrades +- Maximum of 1000 queries per collection interval (adjust interval if you exceed this limit) + +**Metrics Data Streams:** +Query History, Pi-hole Summary, Top Clients, and Top Domains collect point-in-time snapshots at each collection interval: +- Default collection interval: 5 minutes (configurable per data stream) +- No pagination required - each collection captures the current state +- Query History provides 10-minute interval buckets for time-series analysis +- Top Clients and Top Domains each collect top 10 allowed and top 10 blocked items per collection + +## Logs reference + +### DNS Queries + +The `dns_queries` data stream collects individual DNS query records from Pi-hole with detailed information about each query. + +**Collected Information:** +- DNS query details (domain, query type, response code) +- Client information (IP address, hostname) +- Upstream DNS server +- Query status (cached, forwarded, blocked, etc.) +- DNSSEC validation status +- Reply time in seconds +- Extended DNS Error (EDE) codes and text +- CNAME records (if applicable) +- Gravity list ID (for blocked queries) + +#### Example Event + +An example event for `dns_queries` looks as following: + +```json +{ + "@timestamp": "2024-10-22T17:47:49.139Z", + "data_stream": { + "type": "logs", + "dataset": "pihole.dns_queries", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "event", + "category": [ + "network" + ], + "type": [ + "protocol" + ], + "module": "pihole", + "dataset": "pihole.dns_queries" + }, + "dns": { + "question": { + "name": "monitor.example.com", + "type": "AAAA" + }, + "response_code": "NODATA" + }, + "source": { + "ip": "192.168.1.100", + "domain": "workstation.example.com" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "pihole": { + "query": { + "id": 22438755, + "status": "CACHE", + "dnssec": "UNKNOWN", + "reply_time": 0.0000243 + }, + "ede": { + "code": -1 + } + } +} +``` + +#### ECS Field Mappings + +The integration maps Pi-hole data to the Elastic Common Schema (ECS): + +| Pi-hole Field | ECS Field | Description | +|--------------|-----------|-------------| +| `domain` | `dns.question.name` | The queried domain name | +| `type` | `dns.question.type` | DNS record type (A, AAAA, CNAME, etc.) | +| `reply_type` | `dns.response_code` | DNS response code | +| `client_ip` | `source.ip` | Client IP address | +| `client_name` | `source.domain` | Client hostname | +| `upstream_name` | `destination.ip` | Upstream DNS server | +| `time` | `@timestamp` | Query timestamp | + +#### Exported fields + +**Exported fields** + +| Field | Description | Type | +|---|---|---| +| @timestamp | Event timestamp. | date | +| data_stream.dataset | Data stream dataset. | constant_keyword | +| data_stream.namespace | Data stream namespace. | constant_keyword | +| data_stream.type | Data stream type. | constant_keyword | +| destination.ip | Upstream DNS server that handled the query | keyword | +| dns.question.name | The domain name being queried | keyword | +| dns.question.type | The type of DNS record being queried (A, AAAA, CNAME, etc.) | keyword | +| dns.response_code | The DNS response code | keyword | +| observer.product | Observer product name | keyword | +| observer.type | Observer type | keyword | +| observer.vendor | Observer vendor name | keyword | +| pihole.cname | CNAME if the query resulted in a CNAME | keyword | +| pihole.ede.code | Extended DNS Error code | long | +| pihole.ede.text | Extended DNS Error text | keyword | +| pihole.list_id | Gravity list ID if the query matched a blocklist | long | +| pihole.query.dnssec | DNSSEC status (UNKNOWN, SECURE, INSECURE, BOGUS) | keyword | +| pihole.query.id | Unique identifier for this query | long | +| pihole.query.reply_time | Query reply time in seconds | double | +| pihole.query.status | Status of the query (CACHE, FORWARDED, BLOCKED, ALLOWED, etc.) | keyword | +| source.domain | Domain name/hostname of the client | keyword | +| source.ip | IP address of the client | ip | + + +## Metrics reference + +### Query History + +The `query_history` data stream collects time-series metrics showing DNS query patterns over time with 10-minute intervals. + +**Collected Information:** +- Total DNS queries in the interval +- Queries answered from cache +- Blocked queries +- Queries forwarded to upstream DNS servers + +This data stream is ideal for tracking DNS traffic patterns, identifying peak usage times, and monitoring the effectiveness of Pi-hole's caching and blocking mechanisms. + +#### Example Event + +An example event for `query_history` looks as following: + +```json +{ + "@timestamp": "2024-12-30T14:15:00.000Z", + "data_stream": { + "type": "metrics", + "dataset": "pihole.query_history", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "metric", + "category": [ + "network" + ], + "type": [ + "info" + ], + "module": "pihole", + "dataset": "pihole.query_history" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "pihole": { + "query_history": { + "total": 487, + "cached": 245, + "blocked": 98, + "forwarded": 144 + } + } +} +``` + +#### Exported fields + +**Exported fields** + +| Field | Description | Type | Metric Type | +|---|---|---|---| +| @timestamp | Event timestamp. | date | | +| data_stream.dataset | Data stream dataset. | constant_keyword | | +| data_stream.namespace | Data stream namespace. | constant_keyword | | +| data_stream.type | Data stream type. | constant_keyword | | +| event.category | Event category. | keyword | | +| event.dataset | Event dataset name. | keyword | | +| event.kind | Event kind (metric). | keyword | | +| event.module | Event module name. | keyword | | +| event.type | Event type. | keyword | | +| observer.product | Observer product name. | keyword | | +| observer.type | Observer type. | keyword | | +| observer.vendor | Observer vendor name. | keyword | | +| pihole.query_history.blocked | Number of queries blocked in this time period | long | gauge | +| pihole.query_history.cached | Number of queries answered from cache in this time period | long | gauge | +| pihole.query_history.forwarded | Number of queries forwarded to upstream DNS servers in this time period | long | gauge | +| pihole.query_history.total | Total number of DNS queries in this time period | long | gauge | + + +### Pi-hole Summary + +The `pihole_summary` data stream collects comprehensive statistics about Pi-hole's overall performance and activity. + +**Collected Information:** +- Total queries and blocking statistics +- Query frequency (queries per minute) +- Unique domains and clients +- Gravity list information and last update timestamp +- Query type breakdown (A, AAAA, PTR, TXT, MX, HTTPS, SVCB, and 10+ other record types) +- Query status breakdown (cache hits, forwarded, blocked by gravity/regex, special domains, stale cache, and 10+ other statuses) +- Query reply type breakdown (NODATA, NXDOMAIN, CNAME, IP, SERVFAIL, and 10+ other reply types) + +This data stream provides a complete picture of Pi-hole's DNS filtering and caching behavior, making it invaluable for capacity planning, troubleshooting, and security monitoring. + +#### Example Event + +An example event for `pihole_summary` looks as following: + +```json +{ + "@timestamp": "2024-12-30T16:00:00.000Z", + "data_stream": { + "type": "metrics", + "dataset": "pihole.pihole_summary", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "metric", + "category": [ + "network" + ], + "type": [ + "info" + ], + "module": "pihole", + "dataset": "pihole.pihole_summary" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "pihole": { + "summary": { + "dns_queries_today": 579557, + "ads_blocked_today": 33293, + "ads_percentage_today": 5.74, + "domains_being_blocked": 86832, + "queries_forwarded": 16036, + "queries_cached": 527297, + "clients_ever_seen": 117, + "unique_clients": 72, + "unique_domains": 17410, + "query_frequency": 6.33, + "gravity_last_update": 1767139800, + "status": "enabled", + "query_types": { + "a": 342193, + "aaaa": 186269, + "any": 0, + "srv": 133, + "soa": 417, + "ptr": 15328, + "txt": 1310, + "naptr": 56, + "mx": 640, + "ds": 0, + "rrsig": 0, + "dnskey": 0, + "ns": 0, + "svcb": 2819, + "https": 30383, + "other": 9 + }, + "query_status": { + "unknown": 1, + "gravity": 5240, + "forwarded": 16025, + "cache": 440217, + "regex": 11594, + "denylist": 0, + "external_blocked_ip": 0, + "external_blocked_null": 0, + "external_blocked_nxra": 0, + "gravity_cname": 41, + "regex_cname": 0, + "denylist_cname": 0, + "retried": 11, + "retried_dnssec": 0, + "in_progress": 2930, + "dbbusy": 0, + "special_domain": 16418, + "cache_stale": 87080, + "external_blocked_ede15": 0 + }, + "query_replies": { + "unknown": 150, + "nodata": 203929, + "nxdomain": 28966, + "cname": 50400, + "ip": 292083, + "domain": 1717, + "rrname": 26, + "servfail": 24, + "refused": 0, + "notimp": 0, + "other": 0, + "dnssec": 0, + "none": 0, + "blob": 2262 + } + } + } +} +``` + +#### Exported fields + +**Exported fields** + +| Field | Description | Type | Metric Type | +|---|---|---|---| +| @timestamp | Event timestamp. | date | | +| data_stream.dataset | Data stream dataset. | constant_keyword | | +| data_stream.namespace | Data stream namespace. | constant_keyword | | +| data_stream.type | Data stream type. | constant_keyword | | +| event.category | Event category. | keyword | | +| event.dataset | Event dataset name. | keyword | | +| event.kind | Event kind (metric). | keyword | | +| event.module | Event module name. | keyword | | +| event.type | Event type. | keyword | | +| observer.product | Observer product name. | keyword | | +| observer.type | Observer type. | keyword | | +| observer.vendor | Observer vendor name. | keyword | | +| pihole.summary.ads_blocked_today | Number of ads blocked today | long | gauge | +| pihole.summary.ads_percentage_today | Percentage of queries blocked today | double | gauge | +| pihole.summary.clients_ever_seen | Total number of clients ever seen | long | gauge | +| pihole.summary.dns_queries_today | Total number of DNS queries today | long | gauge | +| pihole.summary.domains_being_blocked | Total number of domains in blocklist | long | gauge | +| pihole.summary.gravity_last_update | Timestamp of last gravity list update | long | gauge | +| pihole.summary.queries_cached | Number of queries answered from cache | long | gauge | +| pihole.summary.queries_forwarded | Number of queries forwarded to upstream DNS | long | gauge | +| pihole.summary.query_frequency | Query frequency per minute | double | gauge | +| pihole.summary.query_replies.blob | Number of BLOB replies | long | gauge | +| pihole.summary.query_replies.cname | Number of CNAME replies | long | gauge | +| pihole.summary.query_replies.dnssec | Number of DNSSEC replies | long | gauge | +| pihole.summary.query_replies.domain | Number of domain replies | long | gauge | +| pihole.summary.query_replies.ip | Number of IP address replies | long | gauge | +| pihole.summary.query_replies.nodata | Number of NODATA replies | long | gauge | +| pihole.summary.query_replies.none | Number of queries with no reply | long | gauge | +| pihole.summary.query_replies.notimp | Number of NOTIMP replies | long | gauge | +| pihole.summary.query_replies.nxdomain | Number of NXDOMAIN replies | long | gauge | +| pihole.summary.query_replies.other | Number of other reply types | long | gauge | +| pihole.summary.query_replies.refused | Number of REFUSED replies | long | gauge | +| pihole.summary.query_replies.rrname | Number of RRNAME replies | long | gauge | +| pihole.summary.query_replies.servfail | Number of SERVFAIL replies | long | gauge | +| pihole.summary.query_replies.unknown | Number of queries with unknown reply | long | gauge | +| pihole.summary.query_status.cache | Number of queries answered from cache | long | gauge | +| pihole.summary.query_status.cache_stale | Number of queries answered from stale cache | long | gauge | +| pihole.summary.query_status.dbbusy | Number of queries delayed due to database busy | long | gauge | +| pihole.summary.query_status.denylist | Number of queries blocked by denylist | long | gauge | +| pihole.summary.query_status.denylist_cname | Number of queries blocked by denylist CNAME | long | gauge | +| pihole.summary.query_status.external_blocked_ede15 | Number of queries blocked by external EDE15 | long | gauge | +| pihole.summary.query_status.external_blocked_ip | Number of queries blocked by external IP blocking | long | gauge | +| pihole.summary.query_status.external_blocked_null | Number of queries blocked by external null blocking | long | gauge | +| pihole.summary.query_status.external_blocked_nxra | Number of queries blocked by external NXRA blocking | long | gauge | +| pihole.summary.query_status.forwarded | Number of queries forwarded to upstream | long | gauge | +| pihole.summary.query_status.gravity | Number of queries blocked by gravity | long | gauge | +| pihole.summary.query_status.gravity_cname | Number of queries blocked by gravity CNAME | long | gauge | +| pihole.summary.query_status.in_progress | Number of queries in progress | long | gauge | +| pihole.summary.query_status.regex | Number of queries blocked by regex | long | gauge | +| pihole.summary.query_status.regex_cname | Number of queries blocked by regex CNAME | long | gauge | +| pihole.summary.query_status.retried | Number of retried queries | long | gauge | +| pihole.summary.query_status.retried_dnssec | Number of retried DNSSEC queries | long | gauge | +| pihole.summary.query_status.special_domain | Number of queries for special domains | long | gauge | +| pihole.summary.query_status.unknown | Number of queries with unknown status | long | gauge | +| pihole.summary.query_types.a | Number of A record queries | long | gauge | +| pihole.summary.query_types.aaaa | Number of AAAA record queries | long | gauge | +| pihole.summary.query_types.any | Number of ANY record queries | long | gauge | +| pihole.summary.query_types.dnskey | Number of DNSKEY record queries | long | gauge | +| pihole.summary.query_types.ds | Number of DS record queries | long | gauge | +| pihole.summary.query_types.https | Number of HTTPS record queries | long | gauge | +| pihole.summary.query_types.mx | Number of MX record queries | long | gauge | +| pihole.summary.query_types.naptr | Number of NAPTR record queries | long | gauge | +| pihole.summary.query_types.ns | Number of NS record queries | long | gauge | +| pihole.summary.query_types.other | Number of other record type queries | long | gauge | +| pihole.summary.query_types.ptr | Number of PTR record queries | long | gauge | +| pihole.summary.query_types.rrsig | Number of RRSIG record queries | long | gauge | +| pihole.summary.query_types.soa | Number of SOA record queries | long | gauge | +| pihole.summary.query_types.srv | Number of SRV record queries | long | gauge | +| pihole.summary.query_types.svcb | Number of SVCB record queries | long | gauge | +| pihole.summary.query_types.txt | Number of TXT record queries | long | gauge | +| pihole.summary.status | Pi-hole status (enabled/disabled) | keyword | | +| pihole.summary.unique_clients | Number of unique clients seen today | long | gauge | +| pihole.summary.unique_domains | Number of unique domains queried today | long | gauge | + + +### Top Clients + +The `top_clients` data stream identifies the most active DNS clients on your network. + +**Collected Information:** +- Client IP address and hostname +- Number of queries from each client +- Query action (allowed or blocked) +- Total queries and blocked queries across all clients + +Each collection gathers the top 10 clients for allowed queries and top 10 clients for blocked queries, providing visibility into both normal DNS usage patterns and potential security issues or misconfigured devices. + +#### Example Event + +An example event for `top_clients` looks as following: + +```json +{ + "@timestamp": "2024-12-30T16:30:00.000Z", + "data_stream": { + "type": "metrics", + "dataset": "pihole.top_clients", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "metric", + "category": [ + "network" + ], + "type": [ + "info" + ], + "module": "pihole", + "dataset": "pihole.top_clients" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "client": { + "ip": "192.168.1.100", + "domain": "workstation.example.com" + }, + "pihole": { + "top_clients": { + "query_action": "allowed", + "query_count": 126051 + } + } +} +``` + +#### Exported fields + +**Exported fields** + +| Field | Description | Type | Metric Type | +|---|---|---|---| +| @timestamp | Event timestamp. | date | | +| client.domain | Client hostname/domain. | keyword | | +| client.ip | Client IP address. | ip | | +| data_stream.dataset | Data stream dataset. | constant_keyword | | +| data_stream.namespace | Data stream namespace. | constant_keyword | | +| data_stream.type | Data stream type. | constant_keyword | | +| event.category | Event category. | keyword | | +| event.dataset | Event dataset name. | keyword | | +| event.kind | Event kind (metric). | keyword | | +| event.module | Event module name. | keyword | | +| event.type | Event type. | keyword | | +| observer.product | Observer product name. | keyword | | +| observer.type | Observer type. | keyword | | +| observer.vendor | Observer vendor name. | keyword | | +| pihole.top_clients.query_action | Action taken on queries (allowed or blocked) | keyword | | +| pihole.top_clients.query_count | Number of DNS queries from this client | long | gauge | + + +### Top Domains + +The `top_domains` data stream shows the most frequently queried domains. + +**Collected Information:** +- Domain name +- Number of queries for the domain +- Query action (allowed or blocked) +- Total queries and blocked queries across all domains + +Each collection gathers the top 10 allowed domains and top 10 blocked domains, helping identify popular services, potential data exfiltration attempts, and the effectiveness of blocklists. + +#### Example Event + +An example event for `top_domains` looks as following: + +```json +{ + "@timestamp": "2024-12-30T18:30:00.000Z", + "data_stream": { + "type": "metrics", + "dataset": "pihole.top_domains", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "metric", + "category": [ + "network" + ], + "type": [ + "info" + ], + "module": "pihole", + "dataset": "pihole.top_domains" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "dns": { + "question": { + "name": "api.example.com" + } + }, + "pihole": { + "top_domains": { + "query_action": "allowed", + "query_count": 235054, + "total_queries": 577849, + "blocked_queries": 30142 + } + } +} +``` + +#### Exported fields + +**Exported fields** + +| Field | Description | Type | Metric Type | +|---|---|---|---| +| @timestamp | Event timestamp. | date | | +| data_stream.dataset | Data stream dataset. | constant_keyword | | +| data_stream.namespace | Data stream namespace. | constant_keyword | | +| data_stream.type | Data stream type. | constant_keyword | | +| dns.question.name | Domain name being queried. | keyword | | +| event.category | Event category. | keyword | | +| event.dataset | Event dataset name. | keyword | | +| event.kind | Event kind (metric). | keyword | | +| event.module | Event module name. | keyword | | +| event.type | Event type. | keyword | | +| observer.product | Observer product name. | keyword | | +| observer.type | Observer type. | keyword | | +| observer.vendor | Observer vendor name. | keyword | | +| pihole.top_domains.blocked_queries | Total number of blocked DNS queries | long | gauge | +| pihole.top_domains.query_action | Action taken on queries (allowed or blocked) | keyword | | +| pihole.top_domains.query_count | Number of DNS queries for this domain | long | gauge | +| pihole.top_domains.total_queries | Total number of DNS queries across all domains | long | gauge | + + +## Troubleshooting + +### Common Issues + +**No data appearing in Elasticsearch:** +- Verify Pi-hole URL is correct and accessible from the Elastic Agent +- Check that the API password is correct +- Enable request tracing (`enable_request_tracer: true`) and check logs at `logs/cel/http-request-trace-*.ndjson` + +**Missing queries (less than expected):** +- If you have high query volume (>400 queries/minute), reduce the collection interval to 10-15 seconds +- Check Elastic Agent logs for errors or timeouts + +**Authentication errors:** +- Verify the admin password is correct +- Check Pi-hole API is accessible at `/api/auth` +- Ensure Pi-hole version supports session-based authentication (v6.0+) + +For help with Elastic ingest tools, check [Common problems](https://www.elastic.co/docs/troubleshoot/ingest/fleet/common-problems). + +## Scaling + +For high-volume networks with multiple Pi-hole instances: +- Deploy separate Elastic Agent instances for each Pi-hole +- Use namespace separation to distinguish between different Pi-hole instances +- Consider reducing collection interval for high-traffic instances + +For more information on architectures that can be used for scaling this integration, check the [Ingest Architectures](https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures) documentation. + +## Reference + +### Inputs used + +These inputs can be used with this integration: +
+cel + +## Setup + +For more details about the CEL input settings, check the [Filebeat documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-cel.html). + +Before configuring the CEL input, make sure you have: +- Network connectivity to the target API endpoint +- Valid authentication credentials (API keys, tokens, or certificates as required) +- Appropriate permissions to read from the target data source + +### Collecting logs from CEL + +To configure the CEL input, you must specify the `request.url` value pointing to the API endpoint. The interval parameter controls how frequently requests are made and is the primary way to balance data freshness with API rate limits and costs. Authentication is often configured through the `request.headers` section using the appropriate method for the service. + +NOTE: To access the API service, make sure you have the necessary API credentials and that the Filebeat instance can reach the endpoint URL. Some services may require IP whitelisting or VPN access. + +To collect logs via API endpoint, configure the following parameters: + +- API Endpoint URL +- API credentials (tokens, keys, or username/password) +- Request interval (how often to fetch data) +
+ + +### API usage + +This integration uses the following Pi-hole API endpoints: + +**Authentication:** +- `POST /api/auth` - Authenticates and obtains a session ID +- `DELETE /api/auth` - Terminates the session + +**Data Collection:** +- `GET /api/queries?length=1000&from=` - Retrieves DNS query logs with timestamp-based filtering (dns_queries data stream) +- `GET /api/history` - Retrieves time-series query metrics (query_history data stream) +- `GET /api/stats/summary` - Retrieves comprehensive Pi-hole statistics (pihole_summary data stream) +- `GET /api/stats/top_clients?blocked=false` - Retrieves top allowed DNS clients (top_clients data stream) +- `GET /api/stats/top_clients?blocked=true` - Retrieves top blocked DNS clients (top_clients data stream) +- `GET /api/stats/top_domains?blocked=false` - Retrieves top allowed domains (top_domains data stream) +- `GET /api/stats/top_domains?blocked=true` - Retrieves top blocked domains (top_domains data stream) + +All data collection endpoints require authentication via the `X-FTL-SID` header. Each data stream manages its own session lifecycle independently. + +For more information about the Pi-hole API, see the [Pi-hole API documentation](https://docs.pi-hole.net/ftldns/api/). diff --git a/packages/pihole/img/pihole-dns-analysis.png b/packages/pihole/img/pihole-dns-analysis.png new file mode 100644 index 00000000000..43c4d9321cb Binary files /dev/null and b/packages/pihole/img/pihole-dns-analysis.png differ diff --git a/packages/pihole/img/pihole-logo.svg b/packages/pihole/img/pihole-logo.svg new file mode 100644 index 00000000000..a0db7e4edd9 --- /dev/null +++ b/packages/pihole/img/pihole-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/pihole/img/pihole-overview.png b/packages/pihole/img/pihole-overview.png new file mode 100644 index 00000000000..36cf249054e Binary files /dev/null and b/packages/pihole/img/pihole-overview.png differ diff --git a/packages/pihole/img/pihole-security.png b/packages/pihole/img/pihole-security.png new file mode 100644 index 00000000000..7a3d2b2537f Binary files /dev/null and b/packages/pihole/img/pihole-security.png differ diff --git a/packages/pihole/kibana/dashboard/pihole-3f0f0fc8-adda-43e0-92c3-8294f193be56.json b/packages/pihole/kibana/dashboard/pihole-3f0f0fc8-adda-43e0-92c3-8294f193be56.json new file mode 100644 index 00000000000..0867b145819 --- /dev/null +++ b/packages/pihole/kibana/dashboard/pihole-3f0f0fc8-adda-43e0-92c3-8294f193be56.json @@ -0,0 +1,97 @@ +{ + "attributes": { + "controlGroupInput": { + "chainingSystem": "HIERARCHICAL", + "controlStyle": "oneLine", + "ignoreParentSettingsJSON": "{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}", + "panelsJSON": "{}", + "showApplySelections": false + }, + "description": "Overview of Pi-hole DNS query activity, blocking statistics, and top clients/domains", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\": [{\"meta\": {\"index\": \"logs-*,metrics-*\", \"type\": \"custom\", \"disabled\": false, \"negate\": false, \"alias\": \"Pi-hole Data\", \"key\": \"data_stream.dataset\"}, \"query\": {\"bool\": {\"should\": [{\"match_phrase\": {\"data_stream.dataset\": \"pihole.dns_queries\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.query_history\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.pihole_summary\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.top_clients\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.top_domains\"}}], \"minimum_should_match\": 1}}, \"$state\": {\"store\": \"appState\"}}], \"query\": {\"query\": \"\", \"language\": \"kuery\"}}" + }, + "optionsJSON": "{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[{\"meta\":{\"index\":\"f092d8ae-d494-41b4-9d09-1ff359d33474\",\"type\":\"exists\",\"key\":\"pihole.summary.dns_queries_today\",\"value\":\"exists\",\"disabled\":false,\"negate\":false},\"query\":{\"exists\":{\"field\":\"pihole.summary.dns_queries_today\"}},\"$state\":{\"store\":\"appState\"}}],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-32c53408-60ad-4b98-b802-41fa3a0ddae4\"},{\"type\":\"index-pattern\",\"name\":\"f092d8ae-d494-41b4-9d09-1ff359d33474\",\"id\":\"metrics-pihole.pihole_summary-*\"}],\"state\":{\"visualization\":{\"layerId\":\"32c53408-60ad-4b98-b802-41fa3a0ddae4\",\"layerType\":\"data\",\"metricAccessor\":\"39766fb6-d4b6-4c26-99e6-53416edf31b6\",\"secondaryTrend\":{\"type\":\"none\"}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"index\":\"f092d8ae-d494-41b4-9d09-1ff359d33474\",\"type\":\"exists\",\"key\":\"pihole.summary.dns_queries_today\",\"value\":\"exists\",\"disabled\":false,\"negate\":false},\"query\":{\"exists\":{\"field\":\"pihole.summary.dns_queries_today\"}},\"$state\":{\"store\":\"appState\"}}],\"datasourceStates\":{\"formBased\":{\"layers\":{\"32c53408-60ad-4b98-b802-41fa3a0ddae4\":{\"columns\":{\"39766fb6-d4b6-4c26-99e6-53416edf31b6\":{\"label\":\"Total Queries (24h)\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.dns_queries_today\",\"filter\":{\"query\":\"\\\"pihole.summary.dns_queries_today\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"39766fb6-d4b6-4c26-99e6-53416edf31b6\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"483e68eb-36aa-4831-8a6d-8940712bb98f\",\"gridData\":{\"i\":\"483e68eb-36aa-4831-8a6d-8940712bb98f\",\"y\":0,\"x\":0,\"w\":12,\"h\":12}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[{\"meta\":{\"index\":\"e62cea79-5ab3-46a6-95b2-39f27f9cf049\",\"type\":\"exists\",\"key\":\"pihole.summary.ads_blocked_today\",\"value\":\"exists\",\"disabled\":false,\"negate\":false},\"query\":{\"exists\":{\"field\":\"pihole.summary.ads_blocked_today\"}},\"$state\":{\"store\":\"appState\"}}],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-04eca1e7-166f-49ab-a947-bbec4f9c6c7f\"},{\"type\":\"index-pattern\",\"name\":\"e62cea79-5ab3-46a6-95b2-39f27f9cf049\",\"id\":\"metrics-pihole.pihole_summary-*\"}],\"state\":{\"visualization\":{\"layerId\":\"04eca1e7-166f-49ab-a947-bbec4f9c6c7f\",\"layerType\":\"data\",\"metricAccessor\":\"71777823-b0ef-431e-ac18-ab88399e6844\",\"secondaryTrend\":{\"type\":\"none\"}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"index\":\"e62cea79-5ab3-46a6-95b2-39f27f9cf049\",\"type\":\"exists\",\"key\":\"pihole.summary.ads_blocked_today\",\"value\":\"exists\",\"disabled\":false,\"negate\":false},\"query\":{\"exists\":{\"field\":\"pihole.summary.ads_blocked_today\"}},\"$state\":{\"store\":\"appState\"}}],\"datasourceStates\":{\"formBased\":{\"layers\":{\"04eca1e7-166f-49ab-a947-bbec4f9c6c7f\":{\"columns\":{\"71777823-b0ef-431e-ac18-ab88399e6844\":{\"label\":\"Blocked Queries (24h)\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.ads_blocked_today\",\"filter\":{\"query\":\"\\\"pihole.summary.ads_blocked_today\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"71777823-b0ef-431e-ac18-ab88399e6844\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"55cebe03-401f-4b5f-bdf6-ab499eb11b2b\",\"gridData\":{\"i\":\"55cebe03-401f-4b5f-bdf6-ab499eb11b2b\",\"y\":0,\"x\":12,\"w\":12,\"h\":12}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-28af376f-f87c-461f-8eb3-e5f9d266f786\"}],\"state\":{\"visualization\":{\"layerId\":\"28af376f-f87c-461f-8eb3-e5f9d266f786\",\"layerType\":\"data\",\"metricAccessor\":\"ef11862c-ed2b-46e1-8065-292cefd44b32\",\"secondaryTrend\":{\"type\":\"none\"}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"28af376f-f87c-461f-8eb3-e5f9d266f786\":{\"columns\":{\"ef11862c-ed2b-46e1-8065-292cefd44b32\":{\"label\":\"Blocking % (24h)\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.ads_percentage_today\",\"filter\":{\"query\":\"\\\"pihole.summary.ads_percentage_today\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"ef11862c-ed2b-46e1-8065-292cefd44b32\"],\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"24deab7c-f105-4095-9a9f-497f821e33ad\",\"gridData\":{\"i\":\"24deab7c-f105-4095-9a9f-497f821e33ad\",\"y\":0,\"x\":24,\"w\":12,\"h\":12}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-1aafa3e7-7690-4cde-90ca-9804e38b3354\"}],\"state\":{\"visualization\":{\"layerId\":\"1aafa3e7-7690-4cde-90ca-9804e38b3354\",\"layerType\":\"data\",\"metricAccessor\":\"b976fbf9-14d7-4e71-a985-182496b6d1ff\",\"secondaryTrend\":{\"type\":\"none\"}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"1aafa3e7-7690-4cde-90ca-9804e38b3354\":{\"columns\":{\"b976fbf9-14d7-4e71-a985-182496b6d1ff\":{\"label\":\"Active Clients\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.unique_clients\",\"filter\":{\"query\":\"\\\"pihole.summary.unique_clients\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"b976fbf9-14d7-4e71-a985-182496b6d1ff\"],\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"006be49c-422d-410a-8d3b-f3771a306a33\",\"gridData\":{\"i\":\"006be49c-422d-410a-8d3b-f3771a306a33\",\"y\":0,\"x\":36,\"w\":12,\"h\":12}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"Query Volume over Time\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.query_history-*\",\"name\":\"indexpattern-datasource-layer-5e776265-59eb-41c9-a046-c14507205373\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"Linear\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"area\",\"layers\":[{\"layerId\":\"5e776265-59eb-41c9-a046-c14507205373\",\"accessors\":[\"8841aa0a-5a22-4cc1-bdb1-46fcc9b92645\"],\"position\":\"top\",\"seriesType\":\"area\",\"showGridlines\":false,\"layerType\":\"data\",\"colorMapping\":{\"assignments\":[],\"specialAssignments\":[{\"rules\":[{\"type\":\"other\"}],\"color\":{\"type\":\"loop\"},\"touched\":false}],\"paletteId\":\"default\",\"colorMode\":{\"type\":\"categorical\"}},\"xAccessor\":\"708a91ef-82a4-42ed-a03f-98d1879d81de\"}],\"xTitle\":\"Time\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"5e776265-59eb-41c9-a046-c14507205373\":{\"columns\":{\"708a91ef-82a4-42ed-a03f-98d1879d81de\":{\"label\":\"Time\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"params\":{\"interval\":\"15m\",\"includeEmptyRows\":true,\"dropPartials\":false},\"customLabel\":true},\"8841aa0a-5a22-4cc1-bdb1-46fcc9b92645\":{\"label\":\"DNS Queries\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.query_history.total\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"708a91ef-82a4-42ed-a03f-98d1879d81de\",\"8841aa0a-5a22-4cc1-bdb1-46fcc9b92645\"],\"incompleteColumns\":{},\"sampling\":1,\"indexPatternId\":\"metrics-pihole.query_history-*\"}},\"currentIndexPatternId\":\"metrics-pihole.query_history-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.query_history-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.query_history-*\",\"title\":\"metrics-pihole.query_history-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"9be5d068-a201-40f3-8795-2f35b971a1d7\",\"gridData\":{\"i\":\"9be5d068-a201-40f3-8795-2f35b971a1d7\",\"y\":12,\"x\":0,\"w\":48,\"h\":13}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"Query Type Breakdown\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-32e8c550-489b-4906-922a-b019e5df53f4\"}],\"state\":{\"visualization\":{\"title\":\"Empty XY chart\",\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"show\",\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"32e8c550-489b-4906-922a-b019e5df53f4\",\"accessors\":[\"9943c874-9c1a-4960-9b6e-797739ef04bd\",\"ca57e44d-4ffe-4f59-a55d-2d0290cac81d\",\"74a5695b-0179-4017-b023-f6e5bd3cdc39\",\"6fa885d0-a53a-4671-ade2-2db03a85ffa7\",\"9957b585-7104-4824-94e3-44d082b19ac1\",\"5764fd57-4e5c-41ab-aa6f-73c32bd3af5e\",\"264251c3-462f-417f-af5b-3562b014f27f\",\"bee93f82-2a8a-487e-b12d-69129c3727a1\",\"3974ef93-26b6-4b22-bd60-2673ff903eaa\",\"4cb1b396-f1d6-48a4-a24d-169091090154\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"colorMapping\":{\"assignments\":[],\"specialAssignments\":[{\"rules\":[{\"type\":\"other\"}],\"color\":{\"type\":\"loop\"},\"touched\":false}],\"paletteId\":\"default\",\"colorMode\":{\"type\":\"categorical\"}},\"xAccessor\":\"76aee0d6-c405-4f8d-9982-aa2d16df246d\"}],\"yTitle\":\"Record Count\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"32e8c550-489b-4906-922a-b019e5df53f4\":{\"indexPatternId\":\"metrics-pihole.pihole_summary-*\",\"columns\":{\"9943c874-9c1a-4960-9b6e-797739ef04bd\":{\"label\":\"A\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.a\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.a\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"ca57e44d-4ffe-4f59-a55d-2d0290cac81d\":{\"label\":\"AAAA\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.aaaa\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.aaaa\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"9957b585-7104-4824-94e3-44d082b19ac1\":{\"label\":\"PTR\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.ptr\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.ptr\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"74a5695b-0179-4017-b023-f6e5bd3cdc39\":{\"label\":\"SOA\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.soa\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.soa\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"6fa885d0-a53a-4671-ade2-2db03a85ffa7\":{\"label\":\"HTTPS\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.https\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.https\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"5764fd57-4e5c-41ab-aa6f-73c32bd3af5e\":{\"label\":\"MX\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.mx\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.mx\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"264251c3-462f-417f-af5b-3562b014f27f\":{\"label\":\"NAPTR\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.naptr\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.naptr\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"bee93f82-2a8a-487e-b12d-69129c3727a1\":{\"label\":\"SRV\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.srv\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.srv\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"3974ef93-26b6-4b22-bd60-2673ff903eaa\":{\"label\":\"SVCB\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.svcb\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.svcb\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"4cb1b396-f1d6-48a4-a24d-169091090154\":{\"label\":\"TXT\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_types.txt\",\"filter\":{\"query\":\"\\\"pihole.summary.query_types.txt\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"76aee0d6-c405-4f8d-9982-aa2d16df246d\":{\"label\":\"Total\",\"dataType\":\"string\",\"operationType\":\"filters\",\"isBucketed\":true,\"params\":{\"filters\":[{\"input\":{\"query\":\"\",\"language\":\"kuery\"},\"label\":\"\"}]},\"customLabel\":true}},\"columnOrder\":[\"76aee0d6-c405-4f8d-9982-aa2d16df246d\",\"9943c874-9c1a-4960-9b6e-797739ef04bd\",\"ca57e44d-4ffe-4f59-a55d-2d0290cac81d\",\"6fa885d0-a53a-4671-ade2-2db03a85ffa7\",\"9957b585-7104-4824-94e3-44d082b19ac1\",\"74a5695b-0179-4017-b023-f6e5bd3cdc39\",\"5764fd57-4e5c-41ab-aa6f-73c32bd3af5e\",\"264251c3-462f-417f-af5b-3562b014f27f\",\"bee93f82-2a8a-487e-b12d-69129c3727a1\",\"3974ef93-26b6-4b22-bd60-2673ff903eaa\",\"4cb1b396-f1d6-48a4-a24d-169091090154\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}},\"currentIndexPatternId\":\"metrics-pihole.pihole_summary-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.pihole_summary-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.pihole_summary-*\",\"title\":\"metrics-pihole.pihole_summary-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"343f2f9b-0f29-4d9d-88bc-11e4b40fe206\",\"gridData\":{\"i\":\"343f2f9b-0f29-4d9d-88bc-11e4b40fe206\",\"y\":25,\"x\":0,\"w\":24,\"h\":17}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"Query Response Breakdown\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-32e8c550-489b-4906-922a-b019e5df53f4\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"title\":\"Empty XY chart\",\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"show\",\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"32e8c550-489b-4906-922a-b019e5df53f4\",\"accessors\":[\"0e01d51e-29ae-4dd3-aac2-b74659b97f6f\",\"bb82d9f0-5967-4867-9629-9c03c623e304\",\"9bb0ae2e-d734-4dd9-bf50-c7e3ba0de0b6\",\"e707f503-b90d-4cd6-8d4a-6474d60c2d30\",\"ed2d173d-fca1-4699-ae55-47878ad77506\",\"1e8e9379-d225-4a6a-b1fe-fb081343c031\",\"dd14db3c-ae0f-4b03-851c-f61565865aa3\",\"0e59ef0c-bedf-40b9-9508-9686db0406e0\",\"80ddf0d8-1e66-46c0-b26b-d7f0d380041c\",\"4f9e9f3f-95ae-445b-a42d-c5b547bc95dc\",\"90920a28-9f2a-4c0d-8de8-9584c1eca7a7\",\"916db165-48db-4c3b-87cb-929e36423800\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"colorMapping\":{\"assignments\":[],\"specialAssignments\":[{\"rules\":[{\"type\":\"other\"}],\"color\":{\"type\":\"loop\"},\"touched\":false}],\"paletteId\":\"default\",\"colorMode\":{\"type\":\"categorical\"}},\"xAccessor\":\"76aee0d6-c405-4f8d-9982-aa2d16df246d\"}],\"yTitle\":\"Record Count\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"32e8c550-489b-4906-922a-b019e5df53f4\":{\"indexPatternId\":\"metrics-pihole.pihole_summary-*\",\"columns\":{\"76aee0d6-c405-4f8d-9982-aa2d16df246d\":{\"label\":\"Total\",\"dataType\":\"string\",\"operationType\":\"filters\",\"isBucketed\":true,\"params\":{\"filters\":[{\"input\":{\"query\":\"\",\"language\":\"kuery\"},\"label\":\"\"}]},\"customLabel\":true},\"0e01d51e-29ae-4dd3-aac2-b74659b97f6f\":{\"label\":\"Cache\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_status.cache\",\"filter\":{\"query\":\"\\\"pihole.summary.query_status.cache\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"916db165-48db-4c3b-87cb-929e36423800\":{\"label\":\"Forwarded\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_status.forwarded\",\"filter\":{\"query\":\"\\\"pihole.summary.query_status.forwarded\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"bb82d9f0-5967-4867-9629-9c03c623e304\":{\"label\":\"Denylist\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_status.denylist\",\"filter\":{\"query\":\"\\\"pihole.summary.query_status.denylist\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"90920a28-9f2a-4c0d-8de8-9584c1eca7a7\":{\"label\":\"Gravity\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_status.gravity\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"0e59ef0c-bedf-40b9-9508-9686db0406e0\":{\"label\":\"Regex\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_status.regex\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"9bb0ae2e-d734-4dd9-bf50-c7e3ba0de0b6\":{\"label\":\"Cache (stale)\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_status.cache_stale\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"e707f503-b90d-4cd6-8d4a-6474d60c2d30\":{\"label\":\"DB Busy\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_status.dbbusy\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"ed2d173d-fca1-4699-ae55-47878ad77506\":{\"label\":\"Unknown\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_status.unknown\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"1e8e9379-d225-4a6a-b1fe-fb081343c031\":{\"label\":\"Gravity (cname)\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_status.gravity_cname\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"dd14db3c-ae0f-4b03-851c-f61565865aa3\":{\"label\":\"In Progress\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_status.in_progress\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"80ddf0d8-1e66-46c0-b26b-d7f0d380041c\":{\"label\":\"Retried\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_status.retried\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"4f9e9f3f-95ae-445b-a42d-c5b547bc95dc\":{\"label\":\"Special Domain\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_status.special_domain\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"76aee0d6-c405-4f8d-9982-aa2d16df246d\",\"0e01d51e-29ae-4dd3-aac2-b74659b97f6f\",\"9bb0ae2e-d734-4dd9-bf50-c7e3ba0de0b6\",\"4f9e9f3f-95ae-445b-a42d-c5b547bc95dc\",\"916db165-48db-4c3b-87cb-929e36423800\",\"0e59ef0c-bedf-40b9-9508-9686db0406e0\",\"90920a28-9f2a-4c0d-8de8-9584c1eca7a7\",\"dd14db3c-ae0f-4b03-851c-f61565865aa3\",\"bb82d9f0-5967-4867-9629-9c03c623e304\",\"e707f503-b90d-4cd6-8d4a-6474d60c2d30\",\"ed2d173d-fca1-4699-ae55-47878ad77506\",\"1e8e9379-d225-4a6a-b1fe-fb081343c031\",\"80ddf0d8-1e66-46c0-b26b-d7f0d380041c\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}},\"currentIndexPatternId\":\"metrics-pihole.pihole_summary-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.pihole_summary-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.pihole_summary-*\",\"title\":\"metrics-pihole.pihole_summary-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"80f0e1a5-d4b7-439f-b421-93d248a0e992\",\"gridData\":{\"i\":\"80f0e1a5-d4b7-439f-b421-93d248a0e992\",\"y\":25,\"x\":24,\"w\":24,\"h\":17}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.top_clients-*\",\"name\":\"indexpattern-datasource-layer-5a0881b8-8b61-4805-816f-30a98edfb814\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"9775af7b-f72f-4c4e-ac43-415021233bb3\",\"isTransposed\":false,\"isMetric\":false,\"width\":247.5},{\"columnId\":\"a5128e21-06f8-45a9-9fac-de7cc282d671\",\"isTransposed\":false,\"isMetric\":true}],\"layerId\":\"5a0881b8-8b61-4805-816f-30a98edfb814\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"5a0881b8-8b61-4805-816f-30a98edfb814\":{\"columns\":{\"9775af7b-f72f-4c4e-ac43-415021233bb3\":{\"label\":\"Top 10 Allowed Clients\",\"dataType\":\"ip\",\"operationType\":\"terms\",\"sourceField\":\"client.ip\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"a5128e21-06f8-45a9-9fac-de7cc282d671\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false},\"customLabel\":true},\"a5128e21-06f8-45a9-9fac-de7cc282d671\":{\"label\":\"Total Allowed Queries\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.top_clients.query_count\",\"isBucketed\":false,\"filter\":{\"query\":\"\\\"pihole.top_clients.query_action\\\": \\\"allowed\\\" \",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"9775af7b-f72f-4c4e-ac43-415021233bb3\",\"a5128e21-06f8-45a9-9fac-de7cc282d671\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{},\"indexPatternId\":\"metrics-pihole.top_clients-*\"}},\"currentIndexPatternId\":\"metrics-pihole.top_clients-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.top_clients-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.top_clients-*\",\"title\":\"metrics-pihole.top_clients-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"8922706e-1138-490a-a723-bebd6cd5807b\",\"gridData\":{\"i\":\"8922706e-1138-490a-a723-bebd6cd5807b\",\"y\":42,\"x\":0,\"w\":12,\"h\":14}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.top_clients-*\",\"name\":\"indexpattern-datasource-layer-5a0881b8-8b61-4805-816f-30a98edfb814\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"9775af7b-f72f-4c4e-ac43-415021233bb3\",\"isTransposed\":false,\"isMetric\":false,\"width\":245.5},{\"columnId\":\"a5128e21-06f8-45a9-9fac-de7cc282d671\",\"isTransposed\":false,\"isMetric\":true}],\"layerId\":\"5a0881b8-8b61-4805-816f-30a98edfb814\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"5a0881b8-8b61-4805-816f-30a98edfb814\":{\"columns\":{\"9775af7b-f72f-4c4e-ac43-415021233bb3\":{\"label\":\"Top 10 Blocked Clients\",\"dataType\":\"ip\",\"operationType\":\"terms\",\"sourceField\":\"client.ip\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"a5128e21-06f8-45a9-9fac-de7cc282d671\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false},\"customLabel\":true},\"a5128e21-06f8-45a9-9fac-de7cc282d671\":{\"label\":\"Total Blocked Queries\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.top_clients.query_count\",\"isBucketed\":false,\"filter\":{\"query\":\"\\\"pihole.top_clients.query_action\\\": \\\"blocked\\\" \",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"9775af7b-f72f-4c4e-ac43-415021233bb3\",\"a5128e21-06f8-45a9-9fac-de7cc282d671\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{},\"indexPatternId\":\"metrics-pihole.top_clients-*\"}},\"currentIndexPatternId\":\"metrics-pihole.top_clients-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.top_clients-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.top_clients-*\",\"title\":\"metrics-pihole.top_clients-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"bbc31e10-8dde-4e01-9445-4438c069f7dd\",\"gridData\":{\"i\":\"bbc31e10-8dde-4e01-9445-4438c069f7dd\",\"y\":42,\"x\":12,\"w\":12,\"h\":14}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.top_domains-*\",\"name\":\"indexpattern-datasource-layer-80b3ec18-ef17-4396-807a-5df2be023acd\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"fe7c9e26-f8a1-4448-a14c-20f0c5b4c1b3\",\"isTransposed\":false,\"isMetric\":false,\"width\":272.5},{\"columnId\":\"7e3ee216-4275-4763-9b1e-f3878d712026\",\"isTransposed\":false,\"isMetric\":true}],\"layerId\":\"80b3ec18-ef17-4396-807a-5df2be023acd\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"80b3ec18-ef17-4396-807a-5df2be023acd\":{\"columns\":{\"fe7c9e26-f8a1-4448-a14c-20f0c5b4c1b3\":{\"label\":\"Top 10 Blocked Domains\",\"dataType\":\"string\",\"operationType\":\"terms\",\"sourceField\":\"dns.question.name\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"7e3ee216-4275-4763-9b1e-f3878d712026\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false},\"customLabel\":true},\"7e3ee216-4275-4763-9b1e-f3878d712026\":{\"label\":\"Total Blocked Queries\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.top_domains.query_count\",\"isBucketed\":false,\"filter\":{\"query\":\"\\\"pihole.top_domains.query_action\\\": \\\"blocked\\\" \",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"fe7c9e26-f8a1-4448-a14c-20f0c5b4c1b3\",\"7e3ee216-4275-4763-9b1e-f3878d712026\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{},\"indexPatternId\":\"metrics-pihole.top_domains-*\"}},\"currentIndexPatternId\":\"metrics-pihole.top_domains-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.top_domains-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.top_domains-*\",\"title\":\"metrics-pihole.top_domains-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"44116716-ac6d-4294-b0b9-fafce6e06886\",\"gridData\":{\"i\":\"44116716-ac6d-4294-b0b9-fafce6e06886\",\"y\":42,\"x\":36,\"w\":12,\"h\":14}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.top_domains-*\",\"name\":\"indexpattern-datasource-layer-80b3ec18-ef17-4396-807a-5df2be023acd\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"fe7c9e26-f8a1-4448-a14c-20f0c5b4c1b3\",\"isTransposed\":false,\"isMetric\":false,\"width\":254.5},{\"columnId\":\"7e3ee216-4275-4763-9b1e-f3878d712026\",\"isTransposed\":false,\"isMetric\":true}],\"layerId\":\"80b3ec18-ef17-4396-807a-5df2be023acd\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"80b3ec18-ef17-4396-807a-5df2be023acd\":{\"columns\":{\"fe7c9e26-f8a1-4448-a14c-20f0c5b4c1b3\":{\"label\":\"Top 10 Permitted Domains\",\"dataType\":\"string\",\"operationType\":\"terms\",\"sourceField\":\"dns.question.name\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"7e3ee216-4275-4763-9b1e-f3878d712026\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false},\"customLabel\":true},\"7e3ee216-4275-4763-9b1e-f3878d712026\":{\"label\":\"Total Allowed Queries\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.top_domains.query_count\",\"isBucketed\":false,\"filter\":{\"query\":\"\\\"pihole.top_domains.query_action\\\": \\\"allowed\\\"\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"fe7c9e26-f8a1-4448-a14c-20f0c5b4c1b3\",\"7e3ee216-4275-4763-9b1e-f3878d712026\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{},\"indexPatternId\":\"metrics-pihole.top_domains-*\"}},\"currentIndexPatternId\":\"metrics-pihole.top_domains-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.top_domains-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.top_domains-*\",\"title\":\"metrics-pihole.top_domains-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"da8d4291-5d1e-42c6-b801-618deeaebda2\",\"gridData\":{\"i\":\"da8d4291-5d1e-42c6-b801-618deeaebda2\",\"y\":42,\"x\":24,\"w\":12,\"h\":14}}]", + "timeRestore": false, + "title": "[Pi-Hole] Overview", + "version": 3 + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-12-31T19:18:03.724Z", + "created_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "id": "pihole-3f0f0fc8-adda-43e0-92c3-8294f193be56", + "managed": false, + "references": [ + { + "id": "metrics-pihole.pihole_summary-*", + "name": "483e68eb-36aa-4831-8a6d-8940712bb98f:indexpattern-datasource-layer-32c53408-60ad-4b98-b802-41fa3a0ddae4", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "483e68eb-36aa-4831-8a6d-8940712bb98f:f092d8ae-d494-41b4-9d09-1ff359d33474", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "55cebe03-401f-4b5f-bdf6-ab499eb11b2b:indexpattern-datasource-layer-04eca1e7-166f-49ab-a947-bbec4f9c6c7f", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "55cebe03-401f-4b5f-bdf6-ab499eb11b2b:e62cea79-5ab3-46a6-95b2-39f27f9cf049", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "24deab7c-f105-4095-9a9f-497f821e33ad:indexpattern-datasource-layer-28af376f-f87c-461f-8eb3-e5f9d266f786", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "006be49c-422d-410a-8d3b-f3771a306a33:indexpattern-datasource-layer-1aafa3e7-7690-4cde-90ca-9804e38b3354", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.query_history-*", + "name": "9be5d068-a201-40f3-8795-2f35b971a1d7:indexpattern-datasource-layer-5e776265-59eb-41c9-a046-c14507205373", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "343f2f9b-0f29-4d9d-88bc-11e4b40fe206:indexpattern-datasource-layer-32e8c550-489b-4906-922a-b019e5df53f4", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "80f0e1a5-d4b7-439f-b421-93d248a0e992:indexpattern-datasource-layer-32e8c550-489b-4906-922a-b019e5df53f4", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.top_clients-*", + "name": "8922706e-1138-490a-a723-bebd6cd5807b:indexpattern-datasource-layer-5a0881b8-8b61-4805-816f-30a98edfb814", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.top_clients-*", + "name": "bbc31e10-8dde-4e01-9445-4438c069f7dd:indexpattern-datasource-layer-5a0881b8-8b61-4805-816f-30a98edfb814", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.top_domains-*", + "name": "44116716-ac6d-4294-b0b9-fafce6e06886:indexpattern-datasource-layer-80b3ec18-ef17-4396-807a-5df2be023acd", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.top_domains-*", + "name": "da8d4291-5d1e-42c6-b801-618deeaebda2:indexpattern-datasource-layer-80b3ec18-ef17-4396-807a-5df2be023acd", + "type": "index-pattern" + } + ], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-12-31T19:20:14.605Z", + "updated_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "version": "WzI3NCwxXQ==" +} diff --git a/packages/pihole/kibana/dashboard/pihole-81a7f84a-b9be-4a53-8d8c-eebbd279eeda.json b/packages/pihole/kibana/dashboard/pihole-81a7f84a-b9be-4a53-8d8c-eebbd279eeda.json new file mode 100644 index 00000000000..28dc395fb22 --- /dev/null +++ b/packages/pihole/kibana/dashboard/pihole-81a7f84a-b9be-4a53-8d8c-eebbd279eeda.json @@ -0,0 +1,72 @@ +{ + "attributes": { + "controlGroupInput": { + "chainingSystem": "HIERARCHICAL", + "controlStyle": "oneLine", + "ignoreParentSettingsJSON": "{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}", + "panelsJSON": "{}", + "showApplySelections": false + }, + "description": "Pi-hole blocking effectiveness, top blocked domains and clients, and security analysis", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\": [{\"meta\": {\"index\": \"logs-*,metrics-*\", \"type\": \"custom\", \"disabled\": false, \"negate\": false, \"alias\": \"Pi-hole Data\", \"key\": \"data_stream.dataset\"}, \"query\": {\"bool\": {\"should\": [{\"match_phrase\": {\"data_stream.dataset\": \"pihole.dns_queries\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.query_history\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.pihole_summary\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.top_clients\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.top_domains\"}}], \"minimum_should_match\": 1}}, \"$state\": {\"store\": \"appState\"}}], \"query\": {\"query\": \"\", \"language\": \"kuery\"}}" + }, + "optionsJSON": "{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-b46d4222-435c-493c-8338-ae3f11dbabef\"}],\"state\":{\"visualization\":{\"layerId\":\"b46d4222-435c-493c-8338-ae3f11dbabef\",\"layerType\":\"data\",\"metricAccessor\":\"f2ab8ec6-b533-4dc5-9e0b-5c796dd3cfb3\",\"secondaryTrend\":{\"type\":\"none\"}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"b46d4222-435c-493c-8338-ae3f11dbabef\":{\"columns\":{\"f2ab8ec6-b533-4dc5-9e0b-5c796dd3cfb3\":{\"label\":\"Blocking %\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.ads_percentage_today\",\"filter\":{\"query\":\"\\\"pihole.summary.ads_percentage_today\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"f2ab8ec6-b533-4dc5-9e0b-5c796dd3cfb3\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"81dc7832-9e3e-4bac-82a3-b4b1bb67a3e7\",\"gridData\":{\"i\":\"81dc7832-9e3e-4bac-82a3-b4b1bb67a3e7\",\"y\":0,\"x\":0,\"w\":12,\"h\":13}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-678620c2-56d0-4a11-85a7-cf6b1af2819e\"}],\"state\":{\"visualization\":{\"layerId\":\"678620c2-56d0-4a11-85a7-cf6b1af2819e\",\"layerType\":\"data\",\"metricAccessor\":\"b192da34-3b69-4cbe-85d6-52eae6f717f4\",\"secondaryTrend\":{\"type\":\"none\"}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"678620c2-56d0-4a11-85a7-cf6b1af2819e\":{\"columns\":{\"b192da34-3b69-4cbe-85d6-52eae6f717f4\":{\"label\":\"Total Blocked (24h)\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.ads_blocked_today\",\"filter\":{\"query\":\"\\\"pihole.summary.ads_blocked_today\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"b192da34-3b69-4cbe-85d6-52eae6f717f4\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"1af20c33-2bed-4525-8911-841aca8c5573\",\"gridData\":{\"i\":\"1af20c33-2bed-4525-8911-841aca8c5573\",\"y\":0,\"x\":12,\"w\":12,\"h\":13}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-1bb572ec-2328-4fdd-814f-a1e6c365ed0a\"}],\"state\":{\"visualization\":{\"layerId\":\"1bb572ec-2328-4fdd-814f-a1e6c365ed0a\",\"layerType\":\"data\",\"metricAccessor\":\"1711aa2c-3126-45c5-af3a-c74ab04dff51\",\"secondaryTrend\":{\"type\":\"none\"}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"1bb572ec-2328-4fdd-814f-a1e6c365ed0a\":{\"columns\":{\"1711aa2c-3126-45c5-af3a-c74ab04dff51\":{\"label\":\"Forwarded\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.queries_forwarded\",\"filter\":{\"query\":\"\\\"pihole.summary.queries_forwarded\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"1711aa2c-3126-45c5-af3a-c74ab04dff51\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"a5f3234d-5cc6-4a5a-9a8d-9963f3ce38bb\",\"gridData\":{\"i\":\"a5f3234d-5cc6-4a5a-9a8d-9963f3ce38bb\",\"y\":0,\"x\":24,\"w\":12,\"h\":13}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-0a172871-06df-4086-b3d8-d368a76044d5\"}],\"state\":{\"visualization\":{\"layerId\":\"0a172871-06df-4086-b3d8-d368a76044d5\",\"layerType\":\"data\",\"metricAccessor\":\"14d3803a-8bfc-4d75-97c6-69907e5bd626\",\"secondaryTrend\":{\"type\":\"none\"}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"0a172871-06df-4086-b3d8-d368a76044d5\":{\"columns\":{\"14d3803a-8bfc-4d75-97c6-69907e5bd626\":{\"label\":\"Cached\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.queries_cached\",\"filter\":{\"query\":\"\\\"pihole.summary.queries_cached\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"14d3803a-8bfc-4d75-97c6-69907e5bd626\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"194e2515-f8d2-4d83-b12b-7a0e6d77cc99\",\"gridData\":{\"i\":\"194e2515-f8d2-4d83-b12b-7a0e6d77cc99\",\"y\":0,\"x\":36,\"w\":12,\"h\":13}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.top_domains-*\",\"name\":\"indexpattern-datasource-layer-57c8a2eb-d08d-46a2-9149-3c9e899b5cbd\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"c0422b1c-f2fb-40fc-a208-cd17cb294e8e\",\"isTransposed\":false,\"isMetric\":true},{\"columnId\":\"8316d80a-867c-45b9-8b4e-dbbe8a88d9c4\",\"isTransposed\":false,\"isMetric\":false}],\"layerId\":\"57c8a2eb-d08d-46a2-9149-3c9e899b5cbd\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"57c8a2eb-d08d-46a2-9149-3c9e899b5cbd\":{\"columns\":{\"c0422b1c-f2fb-40fc-a208-cd17cb294e8e\":{\"label\":\"Total Blocked Queries\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.top_domains.query_count\",\"isBucketed\":false,\"filter\":{\"query\":\"pihole.top_domains.query_action : \\\"blocked\\\" \",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"8316d80a-867c-45b9-8b4e-dbbe8a88d9c4\":{\"label\":\"Top 10 Blocked Domains\",\"dataType\":\"string\",\"operationType\":\"terms\",\"sourceField\":\"dns.question.name\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"c0422b1c-f2fb-40fc-a208-cd17cb294e8e\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false},\"customLabel\":true}},\"columnOrder\":[\"8316d80a-867c-45b9-8b4e-dbbe8a88d9c4\",\"c0422b1c-f2fb-40fc-a208-cd17cb294e8e\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{},\"indexPatternId\":\"metrics-pihole.top_domains-*\"}},\"currentIndexPatternId\":\"metrics-pihole.top_domains-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.top_domains-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.top_domains-*\",\"title\":\"metrics-pihole.top_domains-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"524d9f03-e6f7-4afd-8f3c-3cfaa6a861bb\",\"gridData\":{\"i\":\"524d9f03-e6f7-4afd-8f3c-3cfaa6a861bb\",\"y\":13,\"x\":0,\"w\":24,\"h\":15}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.top_clients-*\",\"name\":\"indexpattern-datasource-layer-57c8a2eb-d08d-46a2-9149-3c9e899b5cbd\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"70b972c1-eb92-4817-9929-e2d06c443cf0\",\"isTransposed\":false,\"isMetric\":false},{\"columnId\":\"70d2e28e-5d77-44e9-af8a-f07fd3345112\",\"isTransposed\":false,\"isMetric\":true}],\"layerId\":\"57c8a2eb-d08d-46a2-9149-3c9e899b5cbd\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"57c8a2eb-d08d-46a2-9149-3c9e899b5cbd\":{\"columns\":{\"70b972c1-eb92-4817-9929-e2d06c443cf0\":{\"label\":\"Top 10 Blocked Clients\",\"dataType\":\"ip\",\"operationType\":\"terms\",\"sourceField\":\"client.ip\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"70d2e28e-5d77-44e9-af8a-f07fd3345112\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false},\"customLabel\":true},\"70d2e28e-5d77-44e9-af8a-f07fd3345112\":{\"label\":\"Total Blocked Queries\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.top_clients.query_count\",\"isBucketed\":false,\"filter\":{\"query\":\"pihole.top_clients.query_action : \\\"blocked\\\" \",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"70b972c1-eb92-4817-9929-e2d06c443cf0\",\"70d2e28e-5d77-44e9-af8a-f07fd3345112\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{},\"indexPatternId\":\"metrics-pihole.top_clients-*\"}},\"currentIndexPatternId\":\"metrics-pihole.top_clients-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.top_domains-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.top_domains-*\",\"title\":\"metrics-pihole.top_domains-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"47b06768-4786-40c3-bd45-015ec3c82c43\",\"gridData\":{\"i\":\"47b06768-4786-40c3-bd45-015ec3c82c43\",\"y\":13,\"x\":24,\"w\":24,\"h\":15}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"Block Reason Breakdown\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-9a979eda-eeff-45e7-a2c9-2f8a46ca9c8c\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"show\",\"fittingFunction\":\"Linear\",\"xTitle\":\"Block Reason\",\"yTitle\":\"Records Blocked\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"9a979eda-eeff-45e7-a2c9-2f8a46ca9c8c\",\"seriesType\":\"bar_horizontal\",\"xAccessor\":\"605523cc-0a4b-4c94-9340-0abfccc3ad45\",\"accessors\":[\"3372222f-c67d-4eb2-bb63-2354858a7d06\",\"ed32c69d-6c51-4654-9cd2-97a612537808\",\"4ce0cee4-cc16-43d2-b169-b1a1e2f3fc0d\",\"3ab85560-7c4c-4bc1-a8d0-e97f02ee8c6f\",\"83e6667b-4224-4524-942a-cc8b4aa902a1\",\"0258eb0c-b1b8-4f6a-810e-803f21ddeaaa\"],\"layerType\":\"data\",\"colorMapping\":{\"assignments\":[],\"specialAssignments\":[{\"rules\":[{\"type\":\"other\"}],\"color\":{\"type\":\"loop\"},\"touched\":false}],\"paletteId\":\"default\",\"colorMode\":{\"type\":\"categorical\"}}}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"9a979eda-eeff-45e7-a2c9-2f8a46ca9c8c\":{\"columns\":{\"3372222f-c67d-4eb2-bb63-2354858a7d06\":{\"label\":\"Gravity\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_status.gravity\",\"filter\":{\"query\":\"\\\"pihole.summary.query_status.gravity\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\",\"showArrayValues\":false},\"customLabel\":true},\"ed32c69d-6c51-4654-9cd2-97a612537808\":{\"label\":\"Regex\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_status.regex\",\"filter\":{\"query\":\"\\\"pihole.summary.query_status.regex\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"4ce0cee4-cc16-43d2-b169-b1a1e2f3fc0d\":{\"label\":\"Denylist\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_status.denylist\",\"filter\":{\"query\":\"\\\"pihole.summary.query_status.denylist\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"3ab85560-7c4c-4bc1-a8d0-e97f02ee8c6f\":{\"label\":\"Gravity CNAME\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_status.gravity_cname\",\"filter\":{\"query\":\"\\\"pihole.summary.query_status.gravity_cname\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"83e6667b-4224-4524-942a-cc8b4aa902a1\":{\"label\":\"Regex CNAME\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_status.regex_cname\",\"filter\":{\"query\":\"\\\"pihole.summary.query_status.regex_cname\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"0258eb0c-b1b8-4f6a-810e-803f21ddeaaa\":{\"label\":\"Denylist CNAME\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_status.denylist_cname\",\"filter\":{\"query\":\"\\\"pihole.summary.query_status.denylist_cname\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"605523cc-0a4b-4c94-9340-0abfccc3ad45\":{\"label\":\"Filters\",\"dataType\":\"string\",\"operationType\":\"filters\",\"isBucketed\":true,\"params\":{\"filters\":[{\"input\":{\"query\":\"\",\"language\":\"kuery\"},\"label\":\"\"}]}}},\"columnOrder\":[\"605523cc-0a4b-4c94-9340-0abfccc3ad45\",\"3372222f-c67d-4eb2-bb63-2354858a7d06\",\"ed32c69d-6c51-4654-9cd2-97a612537808\",\"4ce0cee4-cc16-43d2-b169-b1a1e2f3fc0d\",\"3ab85560-7c4c-4bc1-a8d0-e97f02ee8c6f\",\"83e6667b-4224-4524-942a-cc8b4aa902a1\",\"0258eb0c-b1b8-4f6a-810e-803f21ddeaaa\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"b8a46e5f-d734-4b25-b443-ee5f91f23182\",\"gridData\":{\"x\":0,\"y\":28,\"w\":24,\"h\":15,\"i\":\"b8a46e5f-d734-4b25-b443-ee5f91f23182\"}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"Blocked v. Allowed Over Time\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.query_history-*\",\"name\":\"indexpattern-datasource-layer-be2ba941-fdc4-4fa0-900e-54e8c97c6837\"}],\"state\":{\"visualization\":{\"title\":\"Empty XY chart\",\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"preferredSeriesType\":\"area\",\"layers\":[{\"layerId\":\"be2ba941-fdc4-4fa0-900e-54e8c97c6837\",\"accessors\":[\"d499a839-1ebf-497e-abf5-13158469bbf3\",\"4165f79c-0172-4ef7-9a97-f97181fb4fda\"],\"position\":\"top\",\"seriesType\":\"area\",\"showGridlines\":false,\"layerType\":\"data\",\"colorMapping\":{\"assignments\":[],\"specialAssignments\":[{\"rules\":[{\"type\":\"other\"}],\"color\":{\"type\":\"loop\"},\"touched\":false}],\"paletteId\":\"default\",\"colorMode\":{\"type\":\"categorical\"}},\"xAccessor\":\"00044024-b651-444d-b3c4-43ddb2407c3c\",\"yConfig\":[{\"forAccessor\":\"d499a839-1ebf-497e-abf5-13158469bbf3\",\"color\":\"#f6726a\"}]}],\"yTitle\":\"Number of Records\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"xTitle\":\"Time\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"be2ba941-fdc4-4fa0-900e-54e8c97c6837\":{\"columns\":{\"00044024-b651-444d-b3c4-43ddb2407c3c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"params\":{\"interval\":\"h\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"d499a839-1ebf-497e-abf5-13158469bbf3\":{\"label\":\"Blocked\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.query_history.blocked\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX0\":{\"label\":\"Part of max(pihole.query_history.cached)+max(pihole.query_history.forwarded)\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.query_history.cached\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX1\":{\"label\":\"Part of max(pihole.query_history.cached)+max(pihole.query_history.forwarded)\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.query_history.forwarded\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX2\":{\"label\":\"Part of max(pihole.query_history.cached)+max(pihole.query_history.forwarded)\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"add\",\"args\":[\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX0\",\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX1\"],\"location\":{\"min\":0,\"max\":68},\"text\":\"max(pihole.query_history.cached)+max(pihole.query_history.forwarded)\"}},\"references\":[\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX0\",\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX1\"],\"customLabel\":true},\"4165f79c-0172-4ef7-9a97-f97181fb4fda\":{\"label\":\"Allowed\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"params\":{\"formula\":\"max(pihole.query_history.cached)+max(pihole.query_history.forwarded)\",\"isFormulaBroken\":false},\"references\":[\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX2\"],\"customLabel\":true}},\"columnOrder\":[\"00044024-b651-444d-b3c4-43ddb2407c3c\",\"d499a839-1ebf-497e-abf5-13158469bbf3\",\"4165f79c-0172-4ef7-9a97-f97181fb4fda\",\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX0\",\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX2\",\"4165f79c-0172-4ef7-9a97-f97181fb4fdaX1\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"a7742fcd-8826-4fbb-a21b-cf9e06d8dc5c\",\"gridData\":{\"x\":24,\"y\":28,\"w\":24,\"h\":15,\"i\":\"a7742fcd-8826-4fbb-a21b-cf9e06d8dc5c\"}}]", + "timeRestore": false, + "title": "[Pi-Hole] Security & Blocking", + "version": 3 + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-12-31T19:18:03.724Z", + "created_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "id": "pihole-81a7f84a-b9be-4a53-8d8c-eebbd279eeda", + "managed": false, + "references": [ + { + "id": "metrics-pihole.pihole_summary-*", + "name": "81dc7832-9e3e-4bac-82a3-b4b1bb67a3e7:indexpattern-datasource-layer-b46d4222-435c-493c-8338-ae3f11dbabef", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "1af20c33-2bed-4525-8911-841aca8c5573:indexpattern-datasource-layer-678620c2-56d0-4a11-85a7-cf6b1af2819e", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "a5f3234d-5cc6-4a5a-9a8d-9963f3ce38bb:indexpattern-datasource-layer-1bb572ec-2328-4fdd-814f-a1e6c365ed0a", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "194e2515-f8d2-4d83-b12b-7a0e6d77cc99:indexpattern-datasource-layer-0a172871-06df-4086-b3d8-d368a76044d5", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.top_domains-*", + "name": "524d9f03-e6f7-4afd-8f3c-3cfaa6a861bb:indexpattern-datasource-layer-57c8a2eb-d08d-46a2-9149-3c9e899b5cbd", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.top_clients-*", + "name": "47b06768-4786-40c3-bd45-015ec3c82c43:indexpattern-datasource-layer-57c8a2eb-d08d-46a2-9149-3c9e899b5cbd", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "b8a46e5f-d734-4b25-b443-ee5f91f23182:indexpattern-datasource-layer-9a979eda-eeff-45e7-a2c9-2f8a46ca9c8c", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.query_history-*", + "name": "a7742fcd-8826-4fbb-a21b-cf9e06d8dc5c:indexpattern-datasource-layer-be2ba941-fdc4-4fa0-900e-54e8c97c6837", + "type": "index-pattern" + } + ], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-12-31T19:18:03.724Z", + "updated_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "version": "WzI2OSwxXQ==" +} diff --git a/packages/pihole/kibana/dashboard/pihole-8548345f-39c8-4990-bbae-d4f25d7b6335.json b/packages/pihole/kibana/dashboard/pihole-8548345f-39c8-4990-bbae-d4f25d7b6335.json new file mode 100644 index 00000000000..4bbd708c839 --- /dev/null +++ b/packages/pihole/kibana/dashboard/pihole-8548345f-39c8-4990-bbae-d4f25d7b6335.json @@ -0,0 +1,57 @@ +{ + "attributes": { + "controlGroupInput": { + "chainingSystem": "HIERARCHICAL", + "controlStyle": "oneLine", + "ignoreParentSettingsJSON": "{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}", + "panelsJSON": "{}", + "showApplySelections": false + }, + "description": "DNS query types, reply types, response times, and client activity analysis", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\": [{\"meta\": {\"index\": \"logs-*,metrics-*\", \"type\": \"custom\", \"disabled\": false, \"negate\": false, \"alias\": \"Pi-hole Data\", \"key\": \"data_stream.dataset\"}, \"query\": {\"bool\": {\"should\": [{\"match_phrase\": {\"data_stream.dataset\": \"pihole.dns_queries\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.query_history\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.pihole_summary\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.top_clients\"}}, {\"match_phrase\": {\"data_stream.dataset\": \"pihole.top_domains\"}}], \"minimum_should_match\": 1}}, \"$state\": {\"store\": \"appState\"}}], \"query\": {\"query\": \"\", \"language\": \"kuery\"}}" + }, + "optionsJSON": "{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"Query Type over Time\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-8b9293d0-99d5-4ee0-add7-d3fc3001c9d9\"}],\"state\":{\"visualization\":{\"title\":\"Empty XY chart\",\"legend\":{\"isVisible\":true,\"position\":\"right\",\"legendSize\":\"small\",\"isInside\":false},\"valueLabels\":\"hide\",\"preferredSeriesType\":\"area_stacked\",\"layers\":[{\"layerId\":\"8b9293d0-99d5-4ee0-add7-d3fc3001c9d9\",\"accessors\":[\"3c88878e-8d18-456e-bd98-14ad6e712e49\",\"655c1958-99ca-4cca-83f2-b11325dc57cc\",\"f9e2914b-55ae-454c-8f7e-2541b2d556f6\",\"8987c4ad-e826-482a-94d6-f1fd56d26fb0\",\"f2de1b8c-3126-449d-8ae7-3849c63194e7\"],\"position\":\"top\",\"seriesType\":\"area_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"colorMapping\":{\"assignments\":[],\"specialAssignments\":[{\"rules\":[{\"type\":\"other\"}],\"color\":{\"type\":\"loop\"},\"touched\":false}],\"paletteId\":\"default\",\"colorMode\":{\"type\":\"categorical\"}},\"xAccessor\":\"b255a041-02f4-4e8b-a902-8a3f581e699c\"}],\"yTitle\":\"Record Count\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"xTitle\":\"Time\",\"showCurrentTimeMarker\":false,\"hideEndzones\":false},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"8b9293d0-99d5-4ee0-add7-d3fc3001c9d9\":{\"columns\":{\"b255a041-02f4-4e8b-a902-8a3f581e699c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"3c88878e-8d18-456e-bd98-14ad6e712e49\":{\"label\":\"A\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_types.a\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"655c1958-99ca-4cca-83f2-b11325dc57cc\":{\"label\":\"AAAA\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_types.aaaa\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"f9e2914b-55ae-454c-8f7e-2541b2d556f6\":{\"label\":\"PTR\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_types.ptr\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"8987c4ad-e826-482a-94d6-f1fd56d26fb0\":{\"label\":\"MX\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_types.mx\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"f2de1b8c-3126-449d-8ae7-3849c63194e7\":{\"label\":\"TXT\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.summary.query_types.txt\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"b255a041-02f4-4e8b-a902-8a3f581e699c\",\"3c88878e-8d18-456e-bd98-14ad6e712e49\",\"655c1958-99ca-4cca-83f2-b11325dc57cc\",\"f9e2914b-55ae-454c-8f7e-2541b2d556f6\",\"8987c4ad-e826-482a-94d6-f1fd56d26fb0\",\"f2de1b8c-3126-449d-8ae7-3849c63194e7\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{},\"indexPatternId\":\"metrics-pihole.pihole_summary-*\"}},\"currentIndexPatternId\":\"metrics-pihole.pihole_summary-*\"},\"indexpattern\":{\"layers\":{},\"currentIndexPatternId\":\"metrics-pihole.pihole_summary-*\"},\"textBased\":{\"layers\":{},\"indexPatternRefs\":[{\"id\":\"metrics-pihole.pihole_summary-*\",\"title\":\"metrics-pihole.pihole_summary-*\",\"timeField\":\"@timestamp\"}]}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"e3dd9bc4-40db-4cdc-a66c-5181bff14eb8\",\"gridData\":{\"i\":\"e3dd9bc4-40db-4cdc-a66c-5181bff14eb8\",\"y\":0,\"x\":0,\"w\":12,\"h\":10}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsGauge\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"logs-pihole.dns_queries-*\",\"name\":\"indexpattern-datasource-layer-83e55828-1f1f-4db1-b8f0-048b68e8c6fe\"}],\"state\":{\"visualization\":{\"layerId\":\"83e55828-1f1f-4db1-b8f0-048b68e8c6fe\",\"layerType\":\"data\",\"shape\":\"semiCircle\",\"palette\":{\"name\":\"custom\",\"type\":\"palette\",\"params\":{\"steps\":4,\"name\":\"custom\",\"reverse\":false,\"rangeType\":\"percent\",\"rangeMin\":null,\"rangeMax\":null,\"progression\":\"fixed\",\"stops\":[{\"color\":\"#F6726A\",\"stop\":25},{\"color\":\"#EAAE01\",\"stop\":50},{\"color\":\"#FCD883\",\"stop\":75},{\"color\":\"#73f66a\",\"stop\":100}],\"colorStops\":[{\"color\":\"#F6726A\",\"stop\":null},{\"color\":\"#EAAE01\",\"stop\":25},{\"color\":\"#FCD883\",\"stop\":50},{\"color\":\"#73f66a\",\"stop\":75}],\"continuity\":\"all\",\"maxSteps\":5}},\"ticksPosition\":\"bands\",\"labelMajorMode\":\"auto\",\"metricAccessor\":\"6c8606cd-0222-4d35-b880-4e357cd64b1d\",\"colorMode\":\"palette\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"83e55828-1f1f-4db1-b8f0-048b68e8c6fe\":{\"columns\":{\"6c8606cd-0222-4d35-b880-4e357cd64b1dX0\":{\"label\":\"Part of Queries Under 1ms\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"sourceField\":\"___records___\",\"filter\":{\"query\":\"pihole.query.reply_time < 0.001\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"6c8606cd-0222-4d35-b880-4e357cd64b1dX1\":{\"label\":\"Part of Queries Under 1ms\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"sourceField\":\"___records___\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"6c8606cd-0222-4d35-b880-4e357cd64b1dX2\":{\"label\":\"Part of Queries Under 1ms\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"6c8606cd-0222-4d35-b880-4e357cd64b1dX0\",\"6c8606cd-0222-4d35-b880-4e357cd64b1dX1\"],\"location\":{\"min\":0,\"max\":54},\"text\":\"count(kql='pihole.query.reply_time < 0.001') / count()\"}},\"references\":[\"6c8606cd-0222-4d35-b880-4e357cd64b1dX0\",\"6c8606cd-0222-4d35-b880-4e357cd64b1dX1\"],\"customLabel\":true},\"6c8606cd-0222-4d35-b880-4e357cd64b1d\":{\"label\":\"Queries Under 1ms\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"params\":{\"formula\":\"count(kql='pihole.query.reply_time < 0.001') / count()\",\"isFormulaBroken\":false,\"format\":{\"id\":\"percent\",\"params\":{\"decimals\":2}}},\"references\":[\"6c8606cd-0222-4d35-b880-4e357cd64b1dX2\"],\"customLabel\":true}},\"columnOrder\":[\"6c8606cd-0222-4d35-b880-4e357cd64b1d\",\"6c8606cd-0222-4d35-b880-4e357cd64b1dX0\",\"6c8606cd-0222-4d35-b880-4e357cd64b1dX2\",\"6c8606cd-0222-4d35-b880-4e357cd64b1dX1\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"748ec93c-4c41-4d02-9047-8fea15f6f382\",\"gridData\":{\"i\":\"748ec93c-4c41-4d02-9047-8fea15f6f382\",\"y\":0,\"x\":12,\"w\":12,\"h\":10}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"Query Response Time Distribution (sec)\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"logs-pihole.dns_queries-*\",\"name\":\"indexpattern-datasource-layer-a7d1c2cf-2a62-45b9-9339-140c2df9d487\"}],\"state\":{\"visualization\":{\"layerId\":\"a7d1c2cf-2a62-45b9-9339-140c2df9d487\",\"layerType\":\"data\",\"metricAccessor\":\"e9748496-eb59-487d-9a72-94f9dde7d2ed\",\"breakdownByAccessor\":\"7b5d4264-f109-44b3-9550-d381d6be3ddb\",\"secondaryTrend\":{\"type\":\"none\"}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a7d1c2cf-2a62-45b9-9339-140c2df9d487\":{\"columns\":{\"7b5d4264-f109-44b3-9550-d381d6be3ddb\":{\"label\":\"Query Response Time (sec)\",\"dataType\":\"string\",\"operationType\":\"range\",\"sourceField\":\"pihole.query.reply_time\",\"isBucketed\":true,\"params\":{\"type\":\"range\",\"ranges\":[{\"from\":0,\"to\":0.01,\"label\":\"\"},{\"from\":0.01,\"to\":0.02,\"label\":\"\"},{\"from\":0.02,\"to\":0.03,\"label\":\"\"},{\"from\":0.03,\"to\":0.04,\"label\":\"\"},{\"from\":0.04,\"to\":0.05,\"label\":\"\"},{\"from\":0.05,\"to\":null,\"label\":\"\"}],\"maxBars\":499.5,\"format\":{\"id\":\"number\",\"params\":{\"decimals\":3}},\"parentFormat\":{\"id\":\"range\",\"params\":{\"template\":\"arrow_right\",\"replaceInfinity\":true}}},\"scale\":\"ordinal\",\"customLabel\":true},\"e9748496-eb59-487d-9a72-94f9dde7d2ed\":{\"label\":\"Count of Records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"sourceField\":\"___records___\",\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"7b5d4264-f109-44b3-9550-d381d6be3ddb\",\"e9748496-eb59-487d-9a72-94f9dde7d2ed\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"ca0985ec-b85f-49e2-bd24-a47d3db3041d\",\"gridData\":{\"i\":\"ca0985ec-b85f-49e2-bd24-a47d3db3041d\",\"y\":10,\"x\":24,\"w\":24,\"h\":15}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"title\":\"DNS Response Types (24h)\",\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.pihole_summary-*\",\"name\":\"indexpattern-datasource-layer-a264923b-f35c-4a12-9600-30af8a8d8361\"}],\"state\":{\"visualization\":{\"columns\":[{\"isTransposed\":false,\"columnId\":\"aa6236e3-9f27-4b98-a891-b8c1ed047f60\",\"isMetric\":true},{\"isTransposed\":false,\"columnId\":\"c49b28d2-1078-452c-a023-a053afb161ff\"},{\"isTransposed\":false,\"columnId\":\"b47a096a-23cb-4fe0-893a-247dd7d4e8f9\"},{\"isTransposed\":false,\"columnId\":\"6b099d5a-2c45-4930-a76c-c31a44ea27c6\"},{\"isTransposed\":false,\"columnId\":\"8698515d-ee1f-48be-b4e0-9bfdb9beeccc\",\"isMetric\":true},{\"isTransposed\":false,\"columnId\":\"2bcff047-4f14-48f8-9c28-0838693b0207\"},{\"columnId\":\"6021ef96-3d6e-4ca0-a91c-8bf04ac35ad9\",\"isTransposed\":false,\"isMetric\":true},{\"columnId\":\"186d55f0-fd29-4f95-9cc6-8e0eaff9acf7\",\"isTransposed\":false,\"isMetric\":true},{\"columnId\":\"b3bdb73c-928e-4b61-b06a-b8bb16c3546e\",\"isTransposed\":false,\"isMetric\":true}],\"layerId\":\"a264923b-f35c-4a12-9600-30af8a8d8361\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a264923b-f35c-4a12-9600-30af8a8d8361\":{\"columns\":{\"aa6236e3-9f27-4b98-a891-b8c1ed047f60\":{\"label\":\"CNAME\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_replies.cname\",\"filter\":{\"query\":\"\\\"pihole.summary.query_replies.cname\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"c49b28d2-1078-452c-a023-a053afb161ff\":{\"label\":\"IP\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_replies.ip\",\"filter\":{\"query\":\"\\\"pihole.summary.query_replies.ip\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"b47a096a-23cb-4fe0-893a-247dd7d4e8f9\":{\"label\":\"NODATA\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_replies.nodata\",\"filter\":{\"query\":\"\\\"pihole.summary.query_replies.nodata\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"6b099d5a-2c45-4930-a76c-c31a44ea27c6\":{\"label\":\"NXDOMAIN\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_replies.nxdomain\",\"filter\":{\"query\":\"\\\"pihole.summary.query_replies.nxdomain\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"8698515d-ee1f-48be-b4e0-9bfdb9beeccc\":{\"label\":\"RRNAME\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_replies.rrname\",\"filter\":{\"query\":\"\\\"pihole.summary.query_replies.rrname\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"2bcff047-4f14-48f8-9c28-0838693b0207\":{\"label\":\"SERVFAIL\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_replies.servfail\",\"filter\":{\"query\":\"\\\"pihole.summary.query_replies.servfail\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"6021ef96-3d6e-4ca0-a91c-8bf04ac35ad9\":{\"label\":\"UNKNOWN\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_replies.unknown\",\"filter\":{\"query\":\"\\\"pihole.summary.query_replies.unknown\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"186d55f0-fd29-4f95-9cc6-8e0eaff9acf7\":{\"label\":\"DOMAIN\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_replies.domain\",\"filter\":{\"query\":\"\\\"pihole.summary.query_replies.domain\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"b3bdb73c-928e-4b61-b06a-b8bb16c3546e\":{\"label\":\"DNSSEC\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"sourceField\":\"pihole.summary.query_replies.dnssec\",\"filter\":{\"query\":\"\\\"pihole.summary.query_replies.dnssec\\\": *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"c49b28d2-1078-452c-a023-a053afb161ff\",\"b47a096a-23cb-4fe0-893a-247dd7d4e8f9\",\"aa6236e3-9f27-4b98-a891-b8c1ed047f60\",\"6b099d5a-2c45-4930-a76c-c31a44ea27c6\",\"186d55f0-fd29-4f95-9cc6-8e0eaff9acf7\",\"6021ef96-3d6e-4ca0-a91c-8bf04ac35ad9\",\"8698515d-ee1f-48be-b4e0-9bfdb9beeccc\",\"2bcff047-4f14-48f8-9c28-0838693b0207\",\"b3bdb73c-928e-4b61-b06a-b8bb16c3546e\"],\"incompleteColumns\":{},\"sampling\":1}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"9546bb0e-4498-47ee-8fd9-6b14bfe4d5bc\",\"gridData\":{\"i\":\"9546bb0e-4498-47ee-8fd9-6b14bfe4d5bc\",\"y\":10,\"x\":0,\"w\":24,\"h\":15}},{\"type\":\"lens\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"filters\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"metrics-pihole.top_domains-*\",\"name\":\"indexpattern-datasource-layer-056ca56d-93d0-4bf2-8bc9-1af3638fa847\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"7e6ee7e2-aae9-4acc-8e9e-fe318a582dd9\",\"isTransposed\":false,\"isMetric\":true},{\"columnId\":\"21a492ca-8113-4319-83a1-041454f45299\",\"isTransposed\":false,\"isMetric\":false}],\"layerId\":\"056ca56d-93d0-4bf2-8bc9-1af3638fa847\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"056ca56d-93d0-4bf2-8bc9-1af3638fa847\":{\"columns\":{\"7e6ee7e2-aae9-4acc-8e9e-fe318a582dd9\":{\"label\":\"Total Requests\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"pihole.top_domains.query_count\",\"isBucketed\":false,\"params\":{\"emptyAsNull\":true},\"customLabel\":true},\"21a492ca-8113-4319-83a1-041454f45299\":{\"label\":\"Top 10 Requested Domains\",\"dataType\":\"string\",\"operationType\":\"terms\",\"sourceField\":\"dns.question.name\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"7e6ee7e2-aae9-4acc-8e9e-fe318a582dd9\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false},\"customLabel\":true}},\"columnOrder\":[\"21a492ca-8113-4319-83a1-041454f45299\",\"7e6ee7e2-aae9-4acc-8e9e-fe318a582dd9\"],\"sampling\":1,\"ignoreGlobalFilters\":false,\"incompleteColumns\":{}}}},\"indexpattern\":{\"layers\":{}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}}},\"panelIndex\":\"bb6c77f1-9e1f-432b-8685-1ab19ccc847d\",\"gridData\":{\"i\":\"bb6c77f1-9e1f-432b-8685-1ab19ccc847d\",\"y\":0,\"x\":24,\"w\":24,\"h\":10}}]", + "timeRestore": false, + "title": "[Pi-Hole] DNS Analysis", + "version": 3 + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-12-31T19:18:03.724Z", + "created_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "id": "pihole-8548345f-39c8-4990-bbae-d4f25d7b6335", + "managed": false, + "references": [ + { + "id": "metrics-pihole.pihole_summary-*", + "name": "e3dd9bc4-40db-4cdc-a66c-5181bff14eb8:indexpattern-datasource-layer-8b9293d0-99d5-4ee0-add7-d3fc3001c9d9", + "type": "index-pattern" + }, + { + "id": "logs-pihole.dns_queries-*", + "name": "748ec93c-4c41-4d02-9047-8fea15f6f382:indexpattern-datasource-layer-83e55828-1f1f-4db1-b8f0-048b68e8c6fe", + "type": "index-pattern" + }, + { + "id": "logs-pihole.dns_queries-*", + "name": "ca0985ec-b85f-49e2-bd24-a47d3db3041d:indexpattern-datasource-layer-a7d1c2cf-2a62-45b9-9339-140c2df9d487", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.pihole_summary-*", + "name": "9546bb0e-4498-47ee-8fd9-6b14bfe4d5bc:indexpattern-datasource-layer-a264923b-f35c-4a12-9600-30af8a8d8361", + "type": "index-pattern" + }, + { + "id": "metrics-pihole.top_domains-*", + "name": "bb6c77f1-9e1f-432b-8685-1ab19ccc847d:indexpattern-datasource-layer-056ca56d-93d0-4bf2-8bc9-1af3638fa847", + "type": "index-pattern" + } + ], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-12-31T19:18:03.724Z", + "updated_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "version": "WzI2NiwxXQ==" +} diff --git a/packages/pihole/kibana/index_pattern/logs-pihole.dns_queries-*.json b/packages/pihole/kibana/index_pattern/logs-pihole.dns_queries-*.json new file mode 100644 index 00000000000..a55cf8b438e --- /dev/null +++ b/packages/pihole/kibana/index_pattern/logs-pihole.dns_queries-*.json @@ -0,0 +1,19 @@ +{ + "attributes": { + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "logs-pihole.dns_queries", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "@timestamp", + "title": "logs-pihole.dns_queries-*", + "typeMeta": "{}" + }, + "id": "logs-pihole.dns_queries-*", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern" +} diff --git a/packages/pihole/kibana/index_pattern/metrics-pihole.pihole_summary-*.json b/packages/pihole/kibana/index_pattern/metrics-pihole.pihole_summary-*.json new file mode 100644 index 00000000000..34f976cf6c7 --- /dev/null +++ b/packages/pihole/kibana/index_pattern/metrics-pihole.pihole_summary-*.json @@ -0,0 +1,19 @@ +{ + "attributes": { + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "metrics-pihole.pihole_summary", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "@timestamp", + "title": "metrics-pihole.pihole_summary-*", + "typeMeta": "{}" + }, + "id": "metrics-pihole.pihole_summary-*", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern" +} diff --git a/packages/pihole/kibana/index_pattern/metrics-pihole.query_history-*.json b/packages/pihole/kibana/index_pattern/metrics-pihole.query_history-*.json new file mode 100644 index 00000000000..f4704760aee --- /dev/null +++ b/packages/pihole/kibana/index_pattern/metrics-pihole.query_history-*.json @@ -0,0 +1,19 @@ +{ + "attributes": { + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "metrics-pihole.query_history", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "@timestamp", + "title": "metrics-pihole.query_history-*", + "typeMeta": "{}" + }, + "id": "metrics-pihole.query_history-*", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern" +} diff --git a/packages/pihole/kibana/index_pattern/metrics-pihole.top_clients-*.json b/packages/pihole/kibana/index_pattern/metrics-pihole.top_clients-*.json new file mode 100644 index 00000000000..8a17c4995d1 --- /dev/null +++ b/packages/pihole/kibana/index_pattern/metrics-pihole.top_clients-*.json @@ -0,0 +1,19 @@ +{ + "attributes": { + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "metrics-pihole.top_clients", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "@timestamp", + "title": "metrics-pihole.top_clients-*", + "typeMeta": "{}" + }, + "id": "metrics-pihole.top_clients-*", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern" +} diff --git a/packages/pihole/kibana/index_pattern/metrics-pihole.top_domains-*.json b/packages/pihole/kibana/index_pattern/metrics-pihole.top_domains-*.json new file mode 100644 index 00000000000..31017d7f766 --- /dev/null +++ b/packages/pihole/kibana/index_pattern/metrics-pihole.top_domains-*.json @@ -0,0 +1,19 @@ +{ + "attributes": { + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "metrics-pihole.top_domains", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "@timestamp", + "title": "metrics-pihole.top_domains-*", + "typeMeta": "{}" + }, + "id": "metrics-pihole.top_domains-*", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern" +} diff --git a/packages/pihole/kibana/search/pihole-blocked-queries.json b/packages/pihole/kibana/search/pihole-blocked-queries.json new file mode 100644 index 00000000000..97e4d821ce6 --- /dev/null +++ b/packages/pihole/kibana/search/pihole-blocked-queries.json @@ -0,0 +1,36 @@ +{ + "id": "pihole-blocked-queries", + "type": "search", + "attributes": { + "title": "[Pi-hole] Blocked Queries", + "description": "DNS queries blocked by Pi-hole (gravity, regex, or denylist)", + "hits": 0, + "columns": [ + "@timestamp", + "dns.question.name", + "dns.question.type", + "source.ip", + "source.domain", + "pihole.query.status" + ], + "sort": [ + [ + "@timestamp", + "desc" + ] + ], + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"pihole.query.status: (GRAVITY OR REGEX OR DENYLIST OR GRAVITY_CNAME OR REGEX_CNAME OR DENYLIST_CNAME)\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + } + }, + "references": [ + { + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + "id": "logs-pihole.dns_queries-*" + } + ], + "migrationVersion": { + "search": "9.1.0" + } +} diff --git a/packages/pihole/kibana/search/pihole-dnssec-failures.json b/packages/pihole/kibana/search/pihole-dnssec-failures.json new file mode 100644 index 00000000000..e316f794d80 --- /dev/null +++ b/packages/pihole/kibana/search/pihole-dnssec-failures.json @@ -0,0 +1,37 @@ +{ + "id": "pihole-dnssec-failures", + "type": "search", + "attributes": { + "title": "[Pi-hole] DNSSEC Failures", + "description": "DNS queries with DNSSEC validation failures (BOGUS status)", + "hits": 0, + "columns": [ + "@timestamp", + "dns.question.name", + "dns.question.type", + "source.ip", + "source.domain", + "pihole.query.dnssec", + "destination.ip" + ], + "sort": [ + [ + "@timestamp", + "desc" + ] + ], + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"pihole.query.dnssec: BOGUS\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + } + }, + "references": [ + { + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + "id": "logs-pihole.dns_queries-*" + } + ], + "migrationVersion": { + "search": "9.1.0" + } +} diff --git a/packages/pihole/kibana/search/pihole-slow-queries.json b/packages/pihole/kibana/search/pihole-slow-queries.json new file mode 100644 index 00000000000..b6170989d30 --- /dev/null +++ b/packages/pihole/kibana/search/pihole-slow-queries.json @@ -0,0 +1,37 @@ +{ + "id": "pihole-slow-queries", + "type": "search", + "attributes": { + "title": "[Pi-hole] Slow DNS Queries", + "description": "DNS queries with reply time greater than 100ms", + "hits": 0, + "columns": [ + "@timestamp", + "dns.question.name", + "dns.question.type", + "pihole.query.reply_time", + "pihole.query.status", + "destination.ip", + "source.domain" + ], + "sort": [ + [ + "pihole.query.reply_time", + "desc" + ] + ], + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"pihole.query.reply_time > 0.1\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + } + }, + "references": [ + { + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + "id": "logs-pihole.dns_queries-*" + } + ], + "migrationVersion": { + "search": "9.1.0" + } +} diff --git a/packages/pihole/kibana/tags.yml b/packages/pihole/kibana/tags.yml new file mode 100644 index 00000000000..47f20a8f551 --- /dev/null +++ b/packages/pihole/kibana/tags.yml @@ -0,0 +1,4 @@ +- text: Security Solution + asset_types: + - dashboard + - search diff --git a/packages/pihole/manifest.yml b/packages/pihole/manifest.yml new file mode 100644 index 00000000000..d75f2cdc3c2 --- /dev/null +++ b/packages/pihole/manifest.yml @@ -0,0 +1,60 @@ +format_version: 3.5.5 +name: pihole +title: "PiHole" +version: 0.1.23 +source: + license: "Apache-2.0" +description: "Collect logs and metrics from PiHole DNS server" +type: integration +categories: + - security + - custom + - network +conditions: + kibana: + version: "9.1.0" + elastic: + subscription: "basic" +screenshots: + - src: /img/pihole-overview.png + title: Pi-hole Overview Dashboard + size: 1920x1080 + type: image/png + - src: /img/pihole-security.png + title: Pi-hole Security & Blocking Dashboard + size: 1920x1080 + type: image/png + - src: /img/pihole-dns-analysis.png + title: Pi-hole DNS Analysis Dashboard + size: 1920x1080 + type: image/png +icons: + - src: /img/pihole-logo.svg + title: Pi-hole logo + size: 32x32 + type: image/svg+xml +vars: + - name: url + type: text + title: Pi-hole URL + description: The URL of the Pi-hole instance (e.g., http://192.168.1.1 or https://pihole.example.com) + required: true + show_user: true + - name: api_password + type: password + title: API Password + description: Pi-hole admin panel password for authentication + required: true + secret: true + show_user: true +policy_templates: + - name: pihole + title: Pi-hole DNS + description: Collect DNS query logs and metrics from Pi-hole + inputs: + - type: cel + title: Collect Pi-hole data + description: Collect DNS query logs and metrics from Pi-hole API +owner: + github: elastic/integrations + type: community diff --git a/packages/pihole/sample_event.json b/packages/pihole/sample_event.json new file mode 100644 index 00000000000..7e01f6593f6 --- /dev/null +++ b/packages/pihole/sample_event.json @@ -0,0 +1,49 @@ +{ + "@timestamp": "2024-10-22T17:47:49.139Z", + "data_stream": { + "type": "logs", + "dataset": "pihole.dns_queries", + "namespace": "default" + }, + "ecs": { + "version": "8.11.0" + }, + "event": { + "kind": "event", + "category": [ + "network" + ], + "type": [ + "protocol" + ], + "module": "pihole", + "dataset": "pihole.dns_queries" + }, + "dns": { + "question": { + "name": "monitor.example.com", + "type": "AAAA" + }, + "response_code": "NODATA" + }, + "source": { + "ip": "192.168.1.100", + "domain": "workstation.example.com" + }, + "observer": { + "vendor": "Pi-hole", + "product": "Pi-hole", + "type": "dns" + }, + "pihole": { + "query": { + "id": 22438755, + "status": "CACHE", + "dnssec": "UNKNOWN", + "reply_time": 0.0000243 + }, + "ede": { + "code": -1 + } + } +}