43 Commits
v0.0.8 ... main

Author SHA1 Message Date
fab
64ec42435a Merge pull request #86 from fabriziosalmi/copilot/fix-validation-test-failure
Fix gci import ordering in caddywaf.go and types.go
2026-01-06 12:17:18 +01:00
copilot-swe-agent[bot]
65ec50cd81 Fix import formatting in caddywaf.go and types.go for gci linter
Co-authored-by: fabriziosalmi <1569108+fabriziosalmi@users.noreply.github.com>
2026-01-06 10:47:34 +00:00
copilot-swe-agent[bot]
c5b2af3e1a Initial plan 2026-01-06 10:43:24 +00:00
fab
e973c0212a Merge pull request #85 from fabriziosalmi/copilot/add-caddy-waf-plugin-installation
Document caddy add-package installation method
2026-01-06 11:41:02 +01:00
copilot-swe-agent[bot]
80ff7aa50f docs: Address code review feedback - update version references and platform-specific commands
Co-authored-by: fabriziosalmi <1569108+fabriziosalmi@users.noreply.github.com>
2026-01-06 10:36:51 +00:00
copilot-swe-agent[bot]
1e6dd5d09c docs: Add comprehensive add-package guide and update documentation index
Co-authored-by: fabriziosalmi <1569108+fabriziosalmi@users.noreply.github.com>
2026-01-06 10:34:12 +00:00
copilot-swe-agent[bot]
dc5d08134d docs: Add caddy add-package installation method to documentation
Co-authored-by: fabriziosalmi <1569108+fabriziosalmi@users.noreply.github.com>
2026-01-06 10:32:44 +00:00
copilot-swe-agent[bot]
1e55a54a04 Initial plan 2026-01-06 10:26:36 +00:00
fab
8acca6ee2e Merge pull request #84 from fabriziosalmi/copilot/fix-log-severity-issue
Fix file logger ignoring log_severity configuration
2026-01-06 11:26:06 +01:00
copilot-swe-agent[bot]
e6ce0282b2 Fix log severity filtering for file logger
The file logger was using hardcoded zap.DebugLevel instead of respecting
the configured log_severity setting. This caused all log messages
(including DEBUG level) to be written to the log file regardless of the
user's log_severity configuration (info, warn, error).

Changed line 135 in caddywaf.go to use the configurable logLevel variable
instead of zap.DebugLevel, ensuring the file logger respects the
log_severity setting just like the console logger does.

Fixes #81

Co-authored-by: fabriziosalmi <1569108+fabriziosalmi@users.noreply.github.com>
2026-01-06 10:20:24 +00:00
copilot-swe-agent[bot]
8ac72dca0b Initial plan 2026-01-06 10:14:01 +00:00
fab
609a861c90 Merge pull request #79 from fabriziosalmi/dependabot/go_modules/go_modules-15eab1e1d8
build(deps): bump github.com/quic-go/quic-go from 0.54.0 to 0.57.0 in the go_modules group across 1 directory
2026-01-06 11:13:24 +01:00
fab
c2fd6a39c3 Merge pull request #83 from Bestigor89/fix/xff-country-asn-blocking
fix: use X-Forwarded-For for country/ASN blocking behind proxies
2026-01-06 11:13:04 +01:00
igor
4c18165aac fix: use X-Forwarded-For for country/ASN blocking behind proxies
Country whitelist, country blacklist, and ASN blocking now correctly
extract the real client IP from X-Forwarded-For header when behind
reverse proxies like Cloudflare, nginx, etc.
2026-01-05 18:13:01 +00:00
fab
cf45542c7e Update README.md 2025-12-29 10:16:34 +01:00
dependabot[bot]
72c4b9c363 build(deps): bump github.com/quic-go/quic-go
Bumps the go_modules group with 1 update in the / directory: [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go).


Updates `github.com/quic-go/quic-go` from 0.54.0 to 0.57.0
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.54.0...v0.57.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.57.0
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-11 16:53:09 +00:00
Fabrizio Salmi
571d095028 fix: restore full request body for large payloads (closes #76) 2025-12-08 07:30:22 +01:00
Fabrizio Salmi
d3f918c4c4 build: upgrade to Go 1.25 and Caddy v2.10.2 (security fix) 2025-12-06 23:15:40 +01:00
Fabrizio Salmi
5c5f32741c docs: release v0.1.4 preparation (changelog, security, readme) 2025-12-06 23:13:17 +01:00
Fabrizio Salmi
0a96f22563 style: fix imports ordering for gci linter 2025-12-06 23:08:54 +01:00
Fabrizio Salmi
12d70c0eec fix: use Go 1.24 and compatible quic-go v0.48.2 2025-12-06 23:03:23 +01:00
Fabrizio Salmi
83a4df7e65 fix: downgrade Caddy to v2.9.1 to resolve Go 1.25 requirement 2025-12-06 23:00:53 +01:00
Fabrizio Salmi
05152510f5 ci: fix release workflow (go 1.23 + gh cli) 2025-12-06 22:59:32 +01:00
Fabrizio Salmi
5928ff4210 ci: fix go version and bump to v0.1.3 2025-12-06 22:55:55 +01:00
Fabrizio Salmi
78f0066cb8 docs: update documentation for v0.1.2 (ASN, SOTA, Issues fixed) 2025-12-06 22:53:33 +01:00
Fabrizio Salmi
00c547e2a3 refactor: apply SOTA patterns (Atomic HitCount, Zero-Copy Body, Low-Lock RateLimit) 2025-12-06 22:52:01 +01:00
Fabrizio Salmi
c29a7ce9aa chore: bump version to v0.1.0 2025-12-06 22:47:03 +01:00
Fabrizio Salmi
eea39d253b Security: Implement hardening improvements (LimitReader, GeoIP Fail-Open, UI Decoupling, Go Version) 2025-12-06 22:46:11 +01:00
Fabrizio Salmi
5d57051169 Style: Fix Code Formatting (go fmt) 2025-12-06 22:39:53 +01:00
Fabrizio Salmi
47e05e907e Fix: Update CI to use test.caddyfile, block Nikto, and use stable GeoIP URL 2025-12-06 22:37:32 +01:00
Fabrizio Salmi
1c9b6a287d Refactor: Fix absolute path in test.caddyfile and resolve TODO in .golangci.yml 2025-12-06 22:28:18 +01:00
Fabrizio Salmi
b3d3d5692c Fix: Address security alerts and bump version to v0.0.9 2025-12-06 22:26:16 +01:00
fab
a179255b3f Merge pull request #72 from fabriziosalmi/dependabot/go_modules/go_modules-eb6ae95bef
Bump github.com/smallstep/certificates from 0.28.4 to 0.29.0 in the go_modules group across 1 directory
2025-12-06 22:19:13 +01:00
Fabrizio Salmi
1da1fea22b Feat: Implement ASN Blocking (#73) 2025-12-06 22:18:10 +01:00
Fabrizio Salmi
34d7a29119 Fix: restore request body after reading (#76) 2025-12-06 22:14:28 +01:00
fab
66685526e5 Merge pull request #75 from cyqlelabs/main
fix: config initialization
2025-12-06 22:11:33 +01:00
Nicolas Iglesias
971bc53f8a fix: config initialization 2025-12-06 15:48:28 -03:00
dependabot[bot]
937808048b Bump github.com/smallstep/certificates
Bumps the go_modules group with 1 update in the / directory: [github.com/smallstep/certificates](https://github.com/smallstep/certificates).


Updates `github.com/smallstep/certificates` from 0.28.4 to 0.29.0
- [Release notes](https://github.com/smallstep/certificates/releases)
- [Changelog](https://github.com/smallstep/certificates/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smallstep/certificates/compare/v0.28.4...v0.29.0)

---
updated-dependencies:
- dependency-name: github.com/smallstep/certificates
  dependency-version: 0.29.0
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-04 00:56:01 +00:00
fab
b9fe9ddbb3 Merge pull request #71 from fabriziosalmi/dependabot/go_modules/go_modules-dd7da38a6b
Bump golang.org/x/crypto from 0.43.0 to 0.45.0 in the go_modules group across 1 directory
2025-12-03 22:32:11 +01:00
dependabot[bot]
db95a9b2ed Bump golang.org/x/crypto in the go_modules group across 1 directory
Bumps the go_modules group with 1 update in the / directory: [golang.org/x/crypto](https://github.com/golang/crypto).


Updates `golang.org/x/crypto` from 0.43.0 to 0.45.0
- [Commits](https://github.com/golang/crypto/compare/v0.43.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 02:43:20 +00:00
fab
e98fd16392 Merge pull request #70 from drev74/fix/lint
feat(ci): add golangci-lint to flow
2025-10-23 15:13:37 +02:00
drev74
65f8c8a62f refactor(ci): add linter to flow 2025-10-22 23:11:44 +03:00
drev74
c8c0fed9e2 fix: lint errors 2025-10-22 23:04:48 +03:00
40 changed files with 1130 additions and 516 deletions

View File

@@ -27,10 +27,10 @@ jobs:
sudo apt update
sudo apt install -y wget git build-essential curl python3 python3-pip
- name: Install Go 1.24.2
- name: Install Go 1.25
uses: actions/setup-go@v4
with:
go-version: '1.24.2'
go-version: '1.25'
- name: Clone caddy-waf Repository
run: |
@@ -53,7 +53,7 @@ jobs:
- name: Download GeoLite2 Country Database
run: |
cd caddy-waf
wget https://git.io/GeoLite2-Country.mmdb
wget https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-Country.mmdb
- name: Validate GeoLite2 Download
run: |
@@ -131,7 +131,7 @@ jobs:
run: |
cd caddy-waf
chmod +x caddy
./caddy run > caddy_output.log 2>&1 &
./caddy run --config test.caddyfile > caddy_output.log 2>&1 &
sleep 5
if ! pgrep -f "caddy run"; then

View File

@@ -26,11 +26,11 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24.2' # Use your desired go version
go-version: '1.25' # Use your desired go version
- name: Extract Tag Name
id: extract_tag
if: "!startsWith(github.ref, 'refs/heads/')"
if: startsWith(github.ref, 'refs/tags/')
run: echo "TAG_NAME=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT
- name: Build Binary
@@ -50,38 +50,31 @@ jobs:
create-release:
runs-on: ubuntu-latest
needs: build-and-release # Ensure all builds complete before creating release
permissions:
contents: write
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Extract Tag Name
id: extract_tag
if: "!startsWith(github.ref, 'refs/heads/')"
if: startsWith(github.ref, 'refs/tags/')
run: echo "TAG_NAME=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT
- name: Create Release
id: create_release
if: "!startsWith(github.ref, 'refs/heads/')"
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.extract_tag.outputs.TAG_NAME }}
release_name: ${{ steps.extract_tag.outputs.TAG_NAME }}
body: |
This is a release of the Caddy WAF middleware version ${{ steps.extract_tag.outputs.TAG_NAME }}. Please download the appropriate binary for your OS/Architecture.
draft: false
prerelease: false
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Upload Release Assets
if: "!startsWith(github.ref, 'refs/heads/')"
run: |
for asset in $(ls *.tar.gz); do
echo "Uploading ${asset}"
gh release upload ${{ steps.create_release.outputs.upload_url }} ${asset} --clobber
done
- name: Release via GH CLI
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ steps.extract_tag.outputs.TAG_NAME }}
run: |
# Flatten artifacts structure (download-artifact puts them in subdirs matches artifact name)
# But we want all .tar.gz in current dir
find . -name "*.tar.gz" -exec mv {} . \;
echo "Creating or updating release for $TAG_NAME..."
# Try to create release with assets. If it fails (exists), upload assets.
gh release create "$TAG_NAME" *.tar.gz --title "$TAG_NAME" --generate-notes || \
gh release upload "$TAG_NAME" *.tar.gz --clobber

View File

@@ -10,7 +10,8 @@ on:
- main
permissions:
contents: read
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
env:
# https://github.com/actions/setup-go/issues/491
@@ -33,10 +34,6 @@ jobs:
pull-requests: read
actions: write # to allow uploading artifacts and cache
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -51,13 +48,19 @@ jobs:
run: |
go get -v
- name: Run linter
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: latest
args: --timeout 5m
- name: Download MaxMindDB binary
run: |
wget https://git.io/GeoLite2-Country.mmdb
wget https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-Country.mmdb
# Commented bits below were useful to allow the job to continue
# even if the tests fail, so we can publish the report separately
# For info about set-output, see https://stackoverflow.com/questions/57850553/github-actions-check-steps-status
- name: Run tests
run: |
go test -v -failfast ./...
go test -v -failfast ./... -tags=it

View File

@@ -65,7 +65,7 @@ linters:
rules:
- linters:
- gosec
text: G115 # TODO: Either we should fix the issues or nuke the linter if it's bad
text: G115 # Excluded due to potential noisy integer overflow warnings
- linters:
- gosec
text: G107 # we aren't calling unknown URL

View File

@@ -43,6 +43,7 @@ This document outlines the requirements and steps for successfully registering t
- [x] **Test Suite**: Comprehensive test coverage across multiple files
- [x] **CI/CD Pipeline**: GitHub Actions workflows for testing and building
- [x] **Module Import**: Can be imported and used with `xcaddy build`
- [x] **caddy add-package**: Compatible with `caddy add-package` command
## 🔍 Potential Issues and Solutions
@@ -101,11 +102,17 @@ go doc -short
# Test module import
go list -m github.com/fabriziosalmi/caddy-waf
# Verify with xcaddy (if available)
# Test with caddy add-package (recommended)
caddy add-package github.com/fabriziosalmi/caddy-waf
# Verify with xcaddy (alternative method)
xcaddy build --with github.com/fabriziosalmi/caddy-waf
# Check latest version/tag
git describe --tags --abbrev=0
# Verify module is loaded
caddy list-modules | grep waf
```
## 📞 Support Information

34
CHANGELOG.md Normal file
View File

@@ -0,0 +1,34 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v0.1.5] - 2025-12-08
### Fixed
- Fixed critical bug where POST request bodies were lost or truncated by using `io.MultiReader` to restore the full body stream (fixes #76).
## [v0.1.4] - 2025-12-06
### Security
- Fixed Panic vulnerability in `quic-go` by upgrading to `v0.54.0` (requires Caddy v2.10.x and Go 1.25).
- Addressed Dependabot Alert #7.
### Changed
- Upgraded Caddy dependency to `v2.10.2`.
- Upgraded Go requirement to `1.25`.
- Improved CI workflows to use Go 1.25 for build and release.
## [v0.1.3] - 2025-12-06
### Fixed
- Downgraded `quic-go` to `v0.48.2` and Caddy to `v2.9.1` to temporarily resolve Go version conflicts (superseded by v0.1.4).
- Fixed import grouping for `gci` linter compliance.
- Fixed GitHub Actions release workflow.
## [v0.1.2] - 2025-12-06
### Added
- SOTA Engineering patterns (Zero-Copy headers, Wait-Free Ring Buffer, Circuit Breaker).
- ASN Blocking support.
- Configurable Request Body size limit.
- GeoIP Fail Open configuration.

29
GNUmakefile Normal file
View File

@@ -0,0 +1,29 @@
tidy:
@go mod tidy
@echo "Done!"
upd:
@go get -u ./...
@echo "Done!"
fmt:
@go fmt ./...
test:
@go test -v ./...
@echo "Done!"
it:
@go test -v ./... -tags=it
@echo "Done!"
lint:
@echo "==> Checking source code with golangci-lint..."
@golangci-lint run
lintfix:
@echo "==> Checking source code with golangci-lint..."
@golangci-lint run --fix
test-integration:
@docker run --rm -v $(PWD):/app -w /app python:3.9-slim python test.py

View File

@@ -7,7 +7,7 @@ A robust, highly customizable, and feature-rich **Web Application Firewall (WAF)
## 🛡️ Core Protections
* **Regex-Based Filtering:** Deep URL, data & header inspection using powerful regex rules.
* **Blacklisting:** Blocks malicious IPs, domains & optionally TOR exit nodes.
* **Blacklisting:** Blocks malicious IPs, domains, ASNs & optionally TOR exit nodes.
* **Geo-Blocking:** Restricts access by country using GeoIP.
* **Rate Limiting:** Prevents abuse via customizable IP request limits.
* **Anomaly Scoring:** Dynamically blocks requests based on cumulative rule matches.
@@ -18,11 +18,18 @@ A robust, highly customizable, and feature-rich **Web Application Firewall (WAF)
* **Dynamic Config Reloads:** Seamless updates without restarts.
* **File Watchers:** Automatic reloads on rule/blacklist changes.
* **Observability:** Seamless integration with ELK stack and Prometheus.
* **Rules generator**: powered by custom GPT, [try it here](https://chatgpt.com/g/g-677d07dd07e48191b799b9e5d6da7828-caddy-waf-ruler)
* **Rules generator**: [available here](https://chatgpt.com/g/g-677d07dd07e48191b799b9e5d6da7828-caddy-waf-ruler)
_Simple at a glance UI :)_
![demo](https://github.com/fabriziosalmi/caddy-waf/blob/main/docs/caddy-waf-ui.png?raw=true)
## Security & Performance
* **Zero-Copy Networking**: Uses `unsafe.String` to eliminate memory allocations during request body inspection.
* **Wait-Free Concurrency**: Atomic counters ensure accurate metrics and rule hit counting without lock contention.
* **Circuit Breaker**: `geoip_fail_open` prevents database failures from causing service outages.
* **DoS Protection**: `io.LimitReader` enforces strict request body limits to prevent memory exhaustion.
* **ReDoS Safety**: Built on top of Go's `regexp` (RE2), guaranteeing linear time execution for all regex rules.
## 🚀 Quick Start
```bash
@@ -55,6 +62,30 @@ curl -fsSL -H "Pragma: no-cache" https://raw.githubusercontent.com/fabriziosalmi
## 🚀 Installation
### Option 1: Using `caddy add-package` (Recommended)
If you already have Caddy installed, you can add this plugin directly using the `caddy add-package` command:
```bash
caddy add-package github.com/fabriziosalmi/caddy-waf
```
This command will:
- Download a new Caddy binary with the WAF module included
- Keep your existing modules intact
- Safely replace your current Caddy binary
- Create a backup of your previous binary (use `--keep-backup` to retain it)
**Note:** This is an experimental Caddy feature. The command uses Caddy's remote build service to compile and download a custom binary with the module included.
### Option 2: Build from Source
#### Prerequisites
- [Go](https://golang.org/dl/) **1.25** or higher
- [Caddy](https://caddyserver.com/docs/install) **v2.10.x** or higher (for building with this plugin)
- [xcaddy](https://github.com/caddyserver/xcaddy) (for building Caddy with plugins)
```bash
# Step 1: Clone the caddy-waf repository from GitHub
git clone https://github.com/fabriziosalmi/caddy-waf.git
@@ -145,19 +176,20 @@ For complete documentation, including configuration options, rule format details
### 📑 Table of Contents
1. [**Installation**](docs/installation.md) - *Instructions for installing the Caddy WAF middleware.*
2. [**Configuration Options**](docs/configuration.md) - *Detailed explanation of all available configuration settings.*
3. [**Rules Format (`rules.json`)**](docs/rules.md) - *A comprehensive guide to defining custom rules using the JSON format.*
4. [**Blacklist Formats**](docs/blacklists.md) - *Documentation of the formats used for defining IP and DNS blacklists.*
5. [**Rate Limiting**](docs/ratelimit.md) - *How to configure rate limiting, including parameters and usage.*
6. [**Country Blocking and Whitelisting**](docs/geoblocking.md) - *Details on how to configure country-based blocking and whitelisting.*
7. [**Protected Attack Types**](docs/attacks.md) - *An overview of the wide range of web-based threats that the Caddy WAF is designed to protect against.*
8. [**Dynamic Updates**](docs/dynamicupdates.md) - *How to dynamically update the WAF rules and other settings without downtime.*
9. [**Metrics**](docs/metrics.md) - *Details about the WAF's metrics endpoint and the different metrics collected.*
10. [**Prometheus Metrics**](docs/prometheus.md) - *Instructions on how to expose WAF metrics using the Prometheus format.*
11. [**ELK Observability**](https://github.com/fabriziosalmi/caddy-waf/blob/main/docs/caddy-waf-elk.md) - *Instructions on how to configure caddy-waf ELK stack observability.*
12. [**Rule/Blacklist Population Scripts**](docs/scripts.md) - *Documentation on the provided scripts to automatically fetch, update and generate rules and blacklists.*
13. [**Testing**](docs/testing.md) - *Guidance on how to test the WAF's effectiveness using the provided testing tools.*
14. [**Docker Support**](docs/docker.md) - *Instructions on how to build and run the WAF using Docker.*
2. [**Using `caddy add-package`**](docs/add-package-guide.md) - *Quick guide for installing with the `caddy add-package` command.*
3. [**Configuration Options**](docs/configuration.md) - *Detailed explanation of all available configuration settings.*
4. [**Rules Format (`rules.json`)**](docs/rules.md) - *A comprehensive guide to defining custom rules using the JSON format.*
5. [**Blacklist Formats**](docs/blacklists.md) - *Documentation of the formats used for defining IP and DNS blacklists.*
6. [**Rate Limiting**](docs/ratelimit.md) - *How to configure rate limiting, including parameters and usage.*
7. [**Country Blocking and Whitelisting**](docs/geoblocking.md) - *Details on how to configure country-based blocking and whitelisting.*
8. [**Protected Attack Types**](docs/attacks.md) - *An overview of the wide range of web-based threats that the Caddy WAF is designed to protect against.*
9. [**Dynamic Updates**](docs/dynamicupdates.md) - *How to dynamically update the WAF rules and other settings without downtime.*
10. [**Metrics**](docs/metrics.md) - *Details about the WAF's metrics endpoint and the different metrics collected.*
11. [**Prometheus Metrics**](docs/prometheus.md) - *Instructions on how to expose WAF metrics using the Prometheus format.*
12. [**ELK Observability**](https://github.com/fabriziosalmi/caddy-waf/blob/main/docs/caddy-waf-elk.md) - *Instructions on how to configure caddy-waf ELK stack observability.*
13. [**Rule/Blacklist Population Scripts**](docs/scripts.md) - *Documentation on the provided scripts to automatically fetch, update and generate rules and blacklists.*
14. [**Testing**](docs/testing.md) - *Guidance on how to test the WAF's effectiveness using the provided testing tools.*
15. [**Docker Support**](docs/docker.md) - *Instructions on how to build and run the WAF using Docker.*
---
@@ -176,7 +208,7 @@ If You like my projects, you may also like these ones:
- [proxmox-vm-autoscale](https://github.com/fabriziosalmi/proxmox-vm-autoscale) Automatically scale virtual machines resources on Proxmox hosts
- [UglyFeed](https://github.com/fabriziosalmi/UglyFeed) Retrieve, aggregate, filter, evaluate, rewrite and serve RSS feeds using Large Language Models for fun, research and learning purposes
- [proxmox-lxc-autoscale](https://github.com/fabriziosalmi/proxmox-lxc-autoscale) Automatically scale LXC containers resources on Proxmox hosts
- [DevGPT](https://github.com/fabriziosalmi/DevGPT) Code togheter, right now! GPT powered code assistant to build project in minutes
- [DevAssistant](https://github.com/fabriziosalmi/DevGPT) Code together, right now! AI powered code assistant to build project in minutes
- [websites-monitor](https://github.com/fabriziosalmi/websites-monitor) Websites monitoring via GitHub Actions (expiration, security, performances, privacy, SEO)
- [caddy-mib](https://github.com/fabriziosalmi/caddy-mib) Track and ban client IPs generating repetitive errors on Caddy
- [zonecontrol](https://github.com/fabriziosalmi/zonecontrol) Cloudflare Zones Settings Automation using GitHub Actions

View File

@@ -4,12 +4,35 @@
| Version | Supported |
| ------- | ------------------ |
| current | :white_check_mark: |
| v0.1.x | :white_check_mark: |
| < 0.1.0 | :x: |
## Reporting a Vulnerability
There is automated security code scanning in place provided by GitHub.
We take the security of `caddy-waf` seriously. If you find a vulnerability, please report it!
Please open an issue to report a vulnerability.
### How to Report
Please do **NOT** open a public issue on GitHub. Instead, report the vulnerability via:
1. **Email**: Send the details to the maintainer (fabrizio.salmi@gmail.com).
2. **GitHub Private Advisory**: Open a private advisory draft on this repository if you have permissions, or contact the maintainer to enable it.
### Required Information
When reporting a vulnerability, please include:
- A description of the vulnerability.
- Steps to reproduce the issue (PoC code is helpful).
- Impact of the vulnerability.
- Affected versions.
### Response Timeline
- We will acknowledge your report within 48 hours.
- We will provide an estimated timeline for the fix within 1 week.
- We will release a patch as soon as possible.
### Credit
We will credit you in the release notes and changelog for responsibly disclosing vulnerabilities, unless you prefer to remain anonymous.

8
assets.go Normal file
View File

@@ -0,0 +1,8 @@
//go:build with_ui
package caddywaf
import "embed"
//go:embed ui/*
var Assets embed.FS

7
assets_stub.go Normal file
View File

@@ -0,0 +1,7 @@
//go:build !with_ui
package caddywaf
import "embed"
var Assets embed.FS

View File

@@ -24,6 +24,7 @@ import (
"os"
"strings"
"sync"
"sync/atomic"
"github.com/fsnotify/fsnotify"
"github.com/oschwald/maxminddb-golang"
@@ -48,7 +49,7 @@ var (
)
// Add or update the version constant as needed
const wafVersion = "v0.0.8" // update this value to the new release version when tagging
const wafVersion = "v0.1.3" // update this value to the new release version when tagging
// ==================== Initialization and Setup ====================
@@ -132,7 +133,7 @@ func (m *Middleware) Provision(ctx caddy.Context) error {
// Create a multi-core logger for both console and file
core := zapcore.NewTee(
zapcore.NewCore(consoleEncoder, consoleSync, logLevel),
zapcore.NewCore(fileEncoder, zapcore.AddSync(fileSync), zap.DebugLevel),
zapcore.NewCore(fileEncoder, zapcore.AddSync(fileSync), logLevel),
)
m.logger = zap.New(core)
@@ -220,21 +221,40 @@ func (m *Middleware) Provision(ctx caddy.Context) error {
}
}
// Configure ASN blocking
if m.BlockASNs.Enabled {
if !fileExists(m.BlockASNs.GeoIPDBPath) {
m.logger.Warn("ASN GeoIP database not found. ASN blocking will be disabled", zap.String("path", m.BlockASNs.GeoIPDBPath))
} else {
reader, err := maxminddb.Open(m.BlockASNs.GeoIPDBPath)
if err != nil {
m.logger.Error("Failed to load ASN GeoIP database", zap.String("path", m.BlockASNs.GeoIPDBPath), zap.Error(err))
} else {
m.logger.Info("ASN GeoIP database loaded successfully", zap.String("path", m.BlockASNs.GeoIPDBPath))
m.BlockASNs.geoIP = reader
}
}
}
// Initialize config and blacklist loaders
m.configLoader = NewConfigLoader(m.logger)
m.blacklistLoader = NewBlacklistLoader(m.logger)
m.geoIPHandler = NewGeoIPHandler(m.logger)
m.requestValueExtractor = NewRequestValueExtractor(m.logger, m.RedactSensitiveData)
m.requestValueExtractor = NewRequestValueExtractor(m.logger, m.RedactSensitiveData, m.MaxRequestBodySize)
// Configure GeoIP handler
m.geoIPHandler.WithGeoIPCache(m.geoIPCacheTTL)
m.geoIPHandler.WithGeoIPLookupFallbackBehavior(m.geoIPLookupFallbackBehavior)
// Load configuration from Caddyfile
dispenser := caddyfile.NewDispenser([]caddyfile.Token{})
err = m.configLoader.UnmarshalCaddyfile(dispenser, m)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
// Initialize TorConfig with default values if not set
if m.Tor.TORIPBlacklistFile == "" {
m.Tor.TORIPBlacklistFile = "tor_blacklist.txt"
}
if m.Tor.UpdateInterval == "" {
m.Tor.UpdateInterval = "24h"
}
if m.Tor.RetryInterval == "" {
m.Tor.RetryInterval = "5m"
}
// Load IP blacklist
@@ -287,17 +307,13 @@ func (m *Middleware) Shutdown(ctx context.Context) error {
m.logger.Debug("Logging worker stopped.")
var firstError error
var errorOccurred bool
// Close GeoIP databases
if m.CountryBlacklist.geoIP != nil {
m.logger.Debug("Closing country blacklist GeoIP database...")
if err := m.CountryBlacklist.geoIP.Close(); err != nil {
m.logger.Error("Error encountered while closing country blacklist GeoIP database", zap.Error(err))
if !errorOccurred {
firstError = fmt.Errorf("error closing country blacklist GeoIP: %w", err)
errorOccurred = true
}
firstError = fmt.Errorf("error closing country blacklist GeoIP: %w", err)
} else {
m.logger.Debug("Country blacklist GeoIP database closed successfully.")
}
@@ -321,6 +337,19 @@ func (m *Middleware) Shutdown(ctx context.Context) error {
m.logger.Debug("Country whitelist GeoIP database was not open, skipping close.")
}
if m.BlockASNs.geoIP != nil {
m.logger.Debug("Closing ASN GeoIP database...")
if err := m.BlockASNs.geoIP.Close(); err != nil {
m.logger.Error("Error encountered while closing ASN GeoIP database", zap.Error(err))
if firstError == nil {
firstError = fmt.Errorf("error closing ASN GeoIP: %w", err)
}
} else {
m.logger.Debug("ASN GeoIP database closed successfully.")
}
m.BlockASNs.geoIP = nil
}
// Log rule hit statistics
m.logger.Info("Rule Hit Statistics:")
m.ruleHits.Range(func(key, value interface{}) bool {
@@ -330,15 +359,16 @@ func (m *Middleware) Shutdown(ctx context.Context) error {
return true
}
hitCount, ok := value.(HitCount)
atomicCounter, ok := value.(*atomic.Int64)
if !ok {
m.logger.Error("Invalid type for hit count in ruleHits map", zap.Any("value", value))
return true
}
hitCount := atomicCounter.Load()
m.logger.Info("Rule Hit",
zap.String("rule_id", string(ruleID)),
zap.Int("hits", int(hitCount)),
zap.Int64("hits", hitCount),
)
return true
})
@@ -501,12 +531,13 @@ func (m *Middleware) getRuleHitStats() map[string]int {
m.logger.Error("Invalid type for rule ID in ruleHits map", zap.Any("key", key))
return true // Continue iteration
}
hitCount, ok := value.(HitCount)
// SOTA Pattern: Wait-Free stats collection
atomicCounter, ok := value.(*atomic.Int64)
if !ok {
m.logger.Error("Invalid type for hit count in ruleHits map", zap.Any("value", value))
return true // Continue iteration
}
stats[string(ruleID)] = int(hitCount)
stats[string(ruleID)] = int(atomicCounter.Load())
return true
})
return stats

View File

@@ -145,6 +145,7 @@ func (cl *ConfigLoader) UnmarshalCaddyfile(d *caddyfile.Dispenser, m *Middleware
m.LogFilePath = "debug.json"
m.RedactSensitiveData = false
m.LogBuffer = 1000
m.BlockASNs.Enabled = false // Default to false
directiveHandlers := map[string]func(d *caddyfile.Dispenser, m *Middleware) error{
"metrics_endpoint": cl.parseMetricsEndpoint,
@@ -152,6 +153,7 @@ func (cl *ConfigLoader) UnmarshalCaddyfile(d *caddyfile.Dispenser, m *Middleware
"rate_limit": cl.parseRateLimit,
"block_countries": cl.parseCountryBlockDirective(true), // Use directive-specific helper
"whitelist_countries": cl.parseCountryBlockDirective(false), // Use directive-specific helper
"block_asns": cl.parseBlockASNsDirective, // Add ASN block directive
"log_severity": cl.parseLogSeverity,
"log_json": cl.parseLogJSON,
"rule_file": cl.parseRuleFile,
@@ -300,6 +302,31 @@ func (cl *ConfigLoader) parseCountryBlockDirective(isBlock bool) func(d *caddyfi
}
}
// parseBlockASNsDirective handles the block_asns directive
func (cl *ConfigLoader) parseBlockASNsDirective(d *caddyfile.Dispenser, m *Middleware) error {
target := &m.BlockASNs
target.Enabled = true
if !d.NextArg() {
return d.ArgErr()
}
target.GeoIPDBPath = d.Val()
target.BlockedASNs = []string{}
for d.NextArg() {
asn := d.Val()
target.BlockedASNs = append(target.BlockedASNs, asn)
}
cl.logger.Debug("ASN block list configured",
zap.Strings("asns", target.BlockedASNs),
zap.String("geoip_db_path", target.GeoIPDBPath),
zap.String("file", d.File()),
zap.Int("line", d.Line()),
)
return nil
}
func (cl *ConfigLoader) parseLogSeverity(d *caddyfile.Dispenser, m *Middleware) error {
if !d.NextArg() {
return d.ArgErr()

View File

@@ -5,6 +5,7 @@ import (
"net/http"
"os"
"strings"
"sync/atomic"
"time"
"go.uber.org/zap"
@@ -25,10 +26,11 @@ func (m *Middleware) DebugRequest(r *http.Request, state *WAFState, msg string)
if !ok {
return true
}
hitCount, ok := value.(HitCount)
atomicCounter, ok := value.(*atomic.Int64)
if !ok {
return true
}
hitCount := atomicCounter.Load()
ruleIDs = append(ruleIDs, string(ruleID))
scores = append(scores, fmt.Sprintf("%s:%d", string(ruleID), hitCount))
return true
@@ -59,25 +61,31 @@ func (m *Middleware) DumpRulesToFile(path string) error {
}
defer f.Close()
f.WriteString("=== WAF Rules Dump ===\n\n")
if _, err := f.WriteString("=== WAF Rules Dump ===\n\n"); err != nil {
return err
}
for phase := 1; phase <= 4; phase++ {
f.WriteString(fmt.Sprintf("== Phase %d Rules ==\n", phase))
fmt.Fprintf(f, "== Phase %d Rules ==\n", phase)
rules, ok := m.Rules[phase]
if !ok || len(rules) == 0 {
f.WriteString(" No rules for this phase\n\n")
if _, err := f.WriteString(" No rules for this phase\n\n"); err != nil {
return err
}
continue
}
for i, rule := range rules {
f.WriteString(fmt.Sprintf(" Rule %d:\n", i+1))
f.WriteString(fmt.Sprintf(" ID: %s\n", rule.ID))
f.WriteString(fmt.Sprintf(" Pattern: %s\n", rule.Pattern))
f.WriteString(fmt.Sprintf(" Targets: %v\n", rule.Targets))
f.WriteString(fmt.Sprintf(" Score: %d\n", rule.Score))
f.WriteString(fmt.Sprintf(" Action: %s\n", rule.Action))
f.WriteString(fmt.Sprintf(" Description: %s\n", rule.Description))
f.WriteString("\n")
fmt.Fprintf(f, " Rule %d:\n", i+1)
fmt.Fprintf(f, " ID: %s\n", rule.ID)
fmt.Fprintf(f, " Pattern: %s\n", rule.Pattern)
fmt.Fprintf(f, " Targets: %v\n", rule.Targets)
fmt.Fprintf(f, " Score: %d\n", rule.Score)
fmt.Fprintf(f, " Action: %s\n", rule.Action)
fmt.Fprintf(f, " Description: %s\n", rule.Description)
if _, err := f.WriteString("\n"); err != nil {
return err
}
}
}

6
doc.go
View File

@@ -16,6 +16,12 @@
//
// Installation:
//
// Option 1 - Using caddy add-package (recommended if Caddy is already installed):
//
// caddy add-package github.com/fabriziosalmi/caddy-waf
//
// Option 2 - Building from source with xcaddy:
//
// xcaddy build --with github.com/fabriziosalmi/caddy-waf
//
// Basic usage in Caddyfile:

View File

@@ -8,8 +8,9 @@ This documentation provides everything you need to deploy and manage the Caddy W
### 🚀 Getting Started
1. **[Introduction](introduction.md)** - *Overview of the Caddy WAF, its purpose, and key benefits.
1. **[Introduction](introduction.md)** - *Overview of the Caddy WAF, its purpose, and key benefits.*
2. **[Installation](installation.md)** - *Instructions for installing the Caddy WAF middleware.*
3. **[Using caddy add-package](add-package-guide.md)** - *Quick guide for installing with the `caddy add-package` command.*
### ⚙️ Core Configuration

174
docs/add-package-guide.md Normal file
View File

@@ -0,0 +1,174 @@
# Using `caddy add-package` to Install Caddy WAF
This guide demonstrates how to install the Caddy WAF module using Caddy's built-in `caddy add-package` command.
## Prerequisites
- Caddy v2.7 or higher already installed on your system
- Internet connection (to download the custom binary)
- Appropriate permissions to replace the Caddy binary
## Quick Installation
To add the Caddy WAF module to your existing Caddy installation:
```bash
caddy add-package github.com/fabriziosalmi/caddy-waf
```
That's it! The command will:
1. Detect your current Caddy installation and modules
2. Send a build request to Caddy's remote build service
3. Download a new binary with the WAF module included
4. Backup your current Caddy binary
5. Replace the binary with the new one
## Verification
After installation, verify the module is loaded:
```bash
caddy list-modules | grep waf
```
Expected output:
```
http.handlers.waf
```
You can also check the full list of modules:
```bash
caddy list-modules --packages
```
## Usage Example
Once installed, you can use the WAF module in your Caddyfile:
```caddyfile
{
auto_https off
admin localhost:2019
}
:8080 {
log {
output stdout
format console
level INFO
}
route {
waf {
metrics_endpoint /waf_metrics
rule_file rules.json
ip_blacklist_file ip_blacklist.txt
dns_blacklist_file dns_blacklist.txt
anomaly_threshold 10
rate_limit {
requests 100
window 60s
cleanup_interval 300s
}
}
respond "Hello, World!" 200
}
}
```
## Advanced Options
### Keep Backup
By default, the command deletes the backup after successful replacement. To keep the backup:
```bash
caddy add-package --keep-backup github.com/fabriziosalmi/caddy-waf
```
The backup will be saved as `caddy.backup` in the same directory.
### Version Pinning
To install a specific version of the module:
```bash
caddy add-package github.com/fabriziosalmi/caddy-waf@vX.Y.Z
```
Replace `vX.Y.Z` with the desired version tag (e.g., `v0.1.3`). You can find available versions on the [GitHub releases page](https://github.com/fabriziosalmi/caddy-waf/releases).
## Troubleshooting
### Command Not Found
If the `caddy add-package` command is not available, you may be using an older version of Caddy:
```bash
caddy version
```
You need Caddy v2.7 or higher. To update Caddy, visit [caddyserver.com/download](https://caddyserver.com/download) and follow the instructions for your operating system and architecture.
### Permission Denied
If you get a permission denied error, run the command with sudo:
```bash
sudo caddy add-package github.com/fabriziosalmi/caddy-waf
```
### Build Service Unavailable
If Caddy's remote build service is unavailable, you can build from source instead:
```bash
# Install xcaddy
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
# Build with the module
xcaddy build --with github.com/fabriziosalmi/caddy-waf
```
### Module Already Exists
If you get an error that the package already exists, the module is already installed. To update:
```bash
# Remove the package first
caddy remove-package github.com/fabriziosalmi/caddy-waf
# Then add it again
caddy add-package github.com/fabriziosalmi/caddy-waf
```
## Removing the Module
To remove the Caddy WAF module:
```bash
caddy remove-package github.com/fabriziosalmi/caddy-waf
```
## Comparison with Other Installation Methods
| Method | Pros | Cons |
|--------|------|------|
| `caddy add-package` | Simple, no build tools needed, keeps existing modules | Requires internet, depends on remote build service |
| `xcaddy build` | Full control, works offline (after dependencies cached), good for development | Requires Go and build tools, more complex |
| Quick script | Automated setup with sample configs | Downloads and builds from source, requires build tools |
## Next Steps
- Read the [Configuration Guide](configuration.md) to customize your WAF rules
- Learn about [Rate Limiting](ratelimit.md) configuration
- Explore [GeoIP Blocking](geoblocking.md) features
- Check out the [Metrics](metrics.md) endpoint for monitoring
## References
- [Caddy Command Line Documentation](https://caddyserver.com/docs/command-line)
- [Caddy Module System](https://caddyserver.com/docs/extending-caddy)
- [xcaddy Build Tool](https://github.com/caddyserver/xcaddy)

View File

@@ -140,6 +140,9 @@ The WAF provides a variety of configuration options to control its behavior. The
| **`log_path`** | Specifies the path for the WAF log file. | `log_path /var/log/waf/access.log` |
| **`redact_sensitive_data`** | Redacts sensitive data from the request query string in logs. | `redact_sensitive_data` |
| **`custom_response`** | Defines custom HTTP responses for blocked requests. Requires status code, content type, and response content or file path. | `custom_response 403 application/json error.json` |
| **`max_request_body_size`**| Configures request body size limit (default 10MB). Uses `io.LimitReader` for protection. | `max_request_body_size 20MB` |
| **`block_asns`** | Blocks requests from specified Autonomous Systems (ASNs) using the MaxMind GeoIP2 ASN database. | `block_asns GeoLite2-ASN.mmdb 12345 67890` |
| **`geoip_fail_open`** | Configures the WAF to allow requests if GeoIP/ASN lookup fails (Circuit Breaker pattern). Default is false (Fail Closed). | `geoip_fail_open` |
---

View File

@@ -1,6 +1,34 @@
# Installation
## Quick Start
## Method 1: Using `caddy add-package` (Recommended)
If you already have Caddy installed, you can add the WAF plugin directly:
```bash
caddy add-package github.com/fabriziosalmi/caddy-waf
```
This command will download and install a new Caddy binary with the WAF module included. It uses Caddy's remote build service to compile a custom binary with the module.
**Advantages:**
- No need to install Go or build tools
- Keeps your existing Caddy modules intact
- Automatic backup of your current binary
- Quick and simple installation
**Options:**
- `--keep-backup` - Keep the backup of your previous Caddy binary
**Note:** This is an experimental Caddy feature introduced in Caddy v2.7.
After installation, verify the module is loaded:
```bash
caddy list-modules | grep waf
```
You should see `http.handlers.waf` in the output.
## Method 2: Quick Script Installation
```bash
curl -fsSL -H "Pragma: no-cache" https://raw.githubusercontent.com/fabriziosalmi/caddy-waf/refs/heads/main/install.sh | bash
@@ -20,7 +48,9 @@ INFO Rules loaded {"file": "rules.json", "total_rules": 70, "invalid_rules
INFO WAF middleware provisioned successfully
```
## Step by step installation
## Method 3: Build from Source (Advanced)
For development or if you need full control over the build process:
```bash
# Step 1: Clone the caddy-waf repository from GitHub

View File

@@ -3,6 +3,7 @@ package caddywaf
import (
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
@@ -197,3 +198,56 @@ func (gh *GeoIPHandler) cacheGeoIPRecord(ip string, record GeoIPRecord) {
})
}
}
// IsASNInList checks if an IP belongs to a list of blocked ASNs
func (gh *GeoIPHandler) IsASNInList(remoteAddr string, blockedASNs []string, geoIP *maxminddb.Reader) (bool, error) {
if geoIP == nil {
return false, fmt.Errorf("geoip database not loaded")
}
// Extract IP address without port
ip := extractIP(remoteAddr)
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
gh.logger.Debug("Invalid IP address for ASN lookup", zap.String("ip", ip))
return false, fmt.Errorf("invalid IP address: %s", ip)
}
var record ASNRecord
err := geoIP.Lookup(parsedIP, &record)
if err != nil {
gh.logger.Error("GeoIP ASN lookup failed", zap.String("ip", ip), zap.Error(err))
return false, fmt.Errorf("geoip lookup failed: %w", err)
}
asnStr := strconv.FormatUint(uint64(record.AutonomousSystemNumber), 10)
for _, blockedASN := range blockedASNs {
if asnStr == blockedASN {
return true, nil
}
}
return false, nil
}
// GetASN extracts the ASN for logging purposes
func (gh *GeoIPHandler) GetASN(remoteAddr string, geoIP *maxminddb.Reader) string {
if geoIP == nil {
return "N/A"
}
ipConf, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
ipConf = remoteAddr
}
parsedIP := net.ParseIP(ipConf)
if parsedIP == nil {
return "N/A"
}
var record ASNRecord
err = geoIP.Lookup(parsedIP, &record)
if err != nil {
return "N/A"
}
return fmt.Sprintf("AS%d %s", record.AutonomousSystemNumber, record.AutonomousSystemOrganization)
}

51
go.mod
View File

@@ -2,8 +2,6 @@ module github.com/fabriziosalmi/caddy-waf
go 1.25
toolchain go1.25.3
require (
github.com/caddyserver/caddy/v2 v2.10.2
github.com/fsnotify/fsnotify v1.9.0
@@ -34,13 +32,13 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/caddyserver/certmagic v0.25.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/ccoveille/go-safecast v1.7.0 // indirect
github.com/ccoveille/go-safecast/v2 v2.0.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/coreos/go-oidc/v3 v3.16.0 // indirect
github.com/coreos/go-oidc/v3 v3.17.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect
@@ -50,9 +48,8 @@ require (
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-chi/chi/v5 v5.2.2 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-chi/chi/v5 v5.2.3 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
@@ -62,10 +59,10 @@ require (
github.com/golang/snappy v1.0.0 // indirect
github.com/google/cel-go v0.26.1 // indirect
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect
github.com/google/go-tpm v0.9.6 // indirect
github.com/google/go-tpm v0.9.7 // indirect
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
@@ -74,7 +71,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.6 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
@@ -94,19 +91,19 @@ require (
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.55.0 // indirect
github.com/prometheus/procfs v0.18.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.57.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/slackhq/nebula v1.9.7 // indirect
github.com/smallstep/certificates v0.28.4 // indirect
github.com/smallstep/certificates v0.29.0 // indirect
github.com/smallstep/cli-utils v0.12.2 // indirect
github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca // indirect
github.com/smallstep/linkedca v0.24.0 // indirect
github.com/smallstep/linkedca v0.25.0 // indirect
github.com/smallstep/nosql v0.7.0 // indirect
github.com/smallstep/pkcs7 v0.2.1 // indirect
github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 // indirect
@@ -136,27 +133,27 @@ require (
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.step.sm/crypto v0.72.0 // indirect
go.step.sm/crypto v0.74.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20251009181029-0b7aa0cfb07b // indirect
golang.org/x/exp v0.0.0-20251017212417-90e834f514db // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.33.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/api v0.252.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251014184007-4626949a642f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/api v0.256.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
google.golang.org/grpc v1.77.0 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect

309
go.sum
View File

@@ -1,11 +1,7 @@
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
@@ -14,19 +10,14 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/kms v1.23.1 h1:Mesyv84WoP3tPjUC0O5LRqPWICO0ufdpWf9jtBCEz64=
cloud.google.com/go/kms v1.23.1/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k=
cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g=
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -53,53 +44,49 @@ github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iX
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8=
github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8=
github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI=
github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I=
github.com/aws/aws-sdk-go-v2/service/kms v1.45.6 h1:Br3kil4j7RPW+7LoLVkYt8SuhIWlg6ylmbmzXJ7PgXY=
github.com/aws/aws-sdk-go-v2/service/kms v1.45.6/go.mod h1:FKXkHzw1fJZtg1P1qoAIiwen5thz/cDRTTDCIu8ljxc=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8=
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/aws/aws-sdk-go-v2 v1.39.5 h1:e/SXuia3rkFtapghJROrydtQpfQaaUgd1cUvyO1mp2w=
github.com/aws/aws-sdk-go-v2 v1.39.5/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM=
github.com/aws/aws-sdk-go-v2/config v1.31.16 h1:E4Tz+tJiPc7kGnXwIfCyUj6xHJNpENlY11oKpRTgsjc=
github.com/aws/aws-sdk-go-v2/config v1.31.16/go.mod h1:2S9hBElpCyGMifv14WxQ7EfPumgoeCPZUpuPX8VtW34=
github.com/aws/aws-sdk-go-v2/credentials v1.18.20 h1:KFndAnHd9NUuzikHjQ8D5CfFVO+bgELkmcGY8yAw98Q=
github.com/aws/aws-sdk-go-v2/credentials v1.18.20/go.mod h1:9mCi28a+fmBHSQ0UM79omkz6JtN+PEsvLrnG36uoUv0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 h1:VO3FIM2TDbm0kqp6sFNR0PbioXJb/HzCDW6NtIZpIWE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12/go.mod h1:6C39gB8kg82tx3r72muZSrNhHia9rjGkX7ORaS2GKNE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 h1:p/9flfXdoAnwJnuW9xHEAFY22R3A6skYkW19JFF9F+8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12/go.mod h1:ZTLHakoVCTtW8AaLGSwJ3LXqHD9uQKnOcv1TrpO6u2k=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 h1:2lTWFvRcnWFFLzHWmtddu5MTchc5Oj2OOey++99tPZ0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12/go.mod h1:hI92pK+ho8HVcWMHKHrK3Uml4pfG7wvL86FzO0LVtQQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 h1:MM8imH7NZ0ovIVX7D2RxfMDv7Jt9OiUXkcQ+GqywA7M=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12/go.mod h1:gf4OGwdNkbEsb7elw2Sy76odfhwNktWII3WgvQgQQ6w=
github.com/aws/aws-sdk-go-v2/service/kms v1.47.0 h1:A97YCVyGz19rRs3+dWf3GpMPflCswgETA9r6/Q0JNSY=
github.com/aws/aws-sdk-go-v2/service/kms v1.47.0/go.mod h1:ZJ1ghBt9gQM8JoNscUua1siIgao8w74o3kvdWUU6N/Q=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 h1:xHXvxst78wBpJFgDW07xllOx0IAzbryrSdM4nMVQ4Dw=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0/go.mod h1:/e8m+AO6HNPPqMyfKRtzZ9+mBF5/x1Wk8QiDva4m07I=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 h1:tBw2Qhf0kj4ZwtsVpDiVRU3zKLvjvjgIjHMKirxXg8M=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4/go.mod h1:Deq4B7sRM6Awq/xyOBlxBdgW8/Z926KYNNaGMW2lrkA=
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 h1:C+BRMnasSYFcgDw8o9H5hzehKzXyAb9GY5v/8bP9DUY=
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0/go.mod h1:4EjU+4mIx6+JqKQkruye+CaigV7alL3thVPfDd9VlMs=
github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M=
github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/caddyserver/caddy/v2 v2.10.2 h1:g/gTYjGMD0dec+UgMw8SnfmJ3I9+M2TdvoRL/Ovu6U8=
github.com/caddyserver/caddy/v2 v2.10.2/go.mod h1:TXLQHx+ev4HDpkO6PnVVHUbL6OXt6Dfe7VcIBdQnPL0=
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/ccoveille/go-safecast v1.7.0 h1:i+0eldPFBeXCTARGyocNS6BNRomquA/GhTZVNEtaIXI=
github.com/ccoveille/go-safecast v1.7.0/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8=
github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0=
github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
@@ -115,15 +102,13 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
@@ -151,21 +136,15 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
@@ -177,19 +156,12 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
@@ -197,34 +169,24 @@ github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PU
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA=
github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA=
github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.6 h1:hwIwPG7w4z5eQEBq11gYw8YYr9xXLfBQ/0JsKyq5AJM=
github.com/google/go-tpm-tools v0.4.6/go.mod h1:MsVQbJnRhKDfWwf5zgr3cDGpj13P1uLAFF0wMEP/n5w=
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@@ -243,14 +205,10 @@ github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -258,7 +216,6 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@@ -266,21 +223,17 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ=
github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
@@ -291,13 +244,8 @@ github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
@@ -316,22 +264,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
github.com/prometheus/procfs v0.18.0 h1:2QTA9cKdznfYJz7EDaa7IiJobHuV7E1WzeBwcrhk0ao=
github.com/prometheus/procfs v0.18.0/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
@@ -341,33 +285,10 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -375,14 +296,14 @@ github.com/slackhq/nebula v1.9.7 h1:v5u46efIyYHGdfjFnozQbRRhMdaB9Ma1SSTcUcE2lfE=
github.com/slackhq/nebula v1.9.7/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
github.com/smallstep/certificates v0.28.4 h1:JTU6/A5Xes6m+OsR6fw1RACSA362vJc9SOFVG7poBEw=
github.com/smallstep/certificates v0.28.4/go.mod h1:LUqo+7mKZE7FZldlTb0zhU4A0bq4G4+akieFMcTaWvA=
github.com/smallstep/certificates v0.29.0 h1:f90szTKYTW62bmCc+qE5doGqIGPVxTQb8Ba37e/K8Zs=
github.com/smallstep/certificates v0.29.0/go.mod h1:27WI0od6gu84mvE4mYQ/QZGyYwHXvhsiSRNC+y3t+mo=
github.com/smallstep/cli-utils v0.12.2 h1:lGzM9PJrH/qawbzMC/s2SvgLdJPKDWKwKzx9doCVO+k=
github.com/smallstep/cli-utils v0.12.2/go.mod h1:uCPqefO29goHLGqFnwk0i8W7XJu18X3WHQFRtOm/00Y=
github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca h1:VX8L0r8vybH0bPeaIxh4NQzafKQiqvlOn8pmOXbFLO4=
github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
github.com/smallstep/linkedca v0.24.0 h1:7nQuHLrI7XQVbZUgvNsUiW35mskyK1itsZyboZxor3E=
github.com/smallstep/linkedca v0.24.0/go.mod h1:7VovSkUuLpO4sJPUxp25aEo9+3XIcgEEMoj2noEQFcI=
github.com/smallstep/linkedca v0.25.0 h1:txT9QHGbCsJq0MhAghBq7qhurGY727tQuqUi+n4BVBo=
github.com/smallstep/linkedca v0.25.0/go.mod h1:Q3jVAauFKNlF86W5/RFtgQeyDKz98GL/KN3KG4mJOvc=
github.com/smallstep/nosql v0.7.0 h1:YiWC9ZAHcrLCrayfaF+QJUv16I2bZ7KdLC3RpJcnAnE=
github.com/smallstep/nosql v0.7.0/go.mod h1:H5VnKMCbeq9QA6SRY5iqPylfxLfYcLwvUff3onQ8+HU=
github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA=
@@ -391,8 +312,6 @@ github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 h1:k23+s51sgYix4Zgb
github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492/go.mod h1:QQhwLqCS13nhv8L5ov7NgusowENUtXdEzdytjmJHdZQ=
github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -428,12 +347,9 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -451,7 +367,6 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
@@ -484,8 +399,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.step.sm/crypto v0.72.0 h1:cwkxbmnN8jj8YWmoXdoGhaac81d2SwXguwmHN9KJxHw=
go.step.sm/crypto v0.72.0/go.mod h1:EAy7MSOXxCvCaDAKJqz0bLdTSDdhpEM9xqye8XsfrM4=
go.step.sm/crypto v0.74.0 h1:/APBEv45yYR4qQFg47HA8w1nesIGcxh44pGyQNw6JRA=
go.step.sm/crypto v0.74.0/go.mod h1:UoXqCAJjjRgzPte0Llaqen7O9P7XjPmgjgTHQGkKCDk=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -500,27 +415,19 @@ go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto/x509roots/fallback v0.0.0-20251009181029-0b7aa0cfb07b h1:YjNArlzCQB2fDkuKSxMwY1ZUQeRXFIFa23Ov9Wa7TUE=
golang.org/x/crypto/x509roots/fallback v0.0.0-20251009181029-0b7aa0cfb07b/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20251017212417-90e834f514db h1:by6IehL4BH5k3e3SJmcoNbOobMey2SLpAF79iPOEBvw=
golang.org/x/exp v0.0.0-20251017212417-90e834f514db/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -528,14 +435,6 @@ golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -544,19 +443,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -564,15 +454,11 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -588,8 +474,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -599,10 +485,9 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
@@ -611,17 +496,11 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
@@ -632,32 +511,16 @@ golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.252.0 h1:xfKJeAJaMwb8OC9fesr369rjciQ704AjU/psjkKURSI=
google.golang.org/api v0.252.0/go.mod h1:dnHOv81x5RAmumZ7BWLShB/u7JZNeyalImxHmtTHxqw=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20251014184007-4626949a642f h1:OiFuztEyBivVKDvguQJYWq1yDcfAHIID/FVrPR4oiI0=
google.golang.org/genproto/googleapis/api v0.0.0-20251014184007-4626949a642f/go.mod h1:kprOiu9Tr0JYyD6DORrc4Hfyk3RFXqkQ3ctHEum3ZbM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
@@ -666,21 +529,13 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@@ -32,7 +32,14 @@ func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next cadd
)
// Return 500 error to client
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal Server Error"))
if _, err := w.Write([]byte("Internal Server Error")); err != nil {
m.logger.Error(err.Error(),
zap.String("log_id", logID),
zap.Any("panic", rec),
zap.Stack("stack"),
)
return
}
}
}()
@@ -175,7 +182,7 @@ func (m *Middleware) handleResponseBodyPhase(recorder *responseRecorder, r *http
for _, rule := range rules {
if rule.regex.MatchString(body) {
if m.processRuleMatch(recorder, r, &rule, body, state) {
if m.processRuleMatch(recorder, r, &rule, "RESPONSE_BODY", body, state) { // Pass RESPONSE_BODY as target
return
}
}
@@ -252,7 +259,6 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
)
if phase == 1 {
// IP blacklisting - the highest priority
m.logger.Debug("Checking for IP blacklisting", zap.String("remote_addr", r.RemoteAddr)) // Added log for checking before to isIPBlacklisted call
xForwardedFor := r.Header.Get("X-Forwarded-For")
@@ -263,7 +269,7 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
m.logger.Debug("Checking IP blacklist with X-Forwarded-For", zap.String("remote_addr_xff", firstIP), zap.String("r.RemoteAddr", r.RemoteAddr))
if m.isIPBlacklisted(firstIP) {
m.logger.Debug("Starting IP blacklist phase")
m.blockRequest(w, r, state, http.StatusForbidden, "ip_blacklist", "ip_blacklist_rule", firstIP,
m.blockRequest(w, r, state, http.StatusForbidden, "ip_blacklist", "ip_blacklist_rule",
zap.String("message", "Request blocked by IP blacklist"),
)
if m.CustomResponses != nil {
@@ -274,12 +280,11 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
} else {
m.logger.Debug("X-Forwarded-For header present but empty or invalid")
}
} else {
m.logger.Debug("X-Forwarded-For header not present using r.RemoteAddr")
if m.isIPBlacklisted(r.RemoteAddr) {
m.logger.Debug("Starting IP blacklist phase")
m.blockRequest(w, r, state, http.StatusForbidden, "ip_blacklist", "ip_blacklist_rule", r.RemoteAddr,
m.blockRequest(w, r, state, http.StatusForbidden, "ip_blacklist", "ip_blacklist_rule",
zap.String("message", "Request blocked by IP blacklist"),
)
if m.CustomResponses != nil {
@@ -292,7 +297,7 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
// DNS blacklisting
if m.isDNSBlacklisted(r.Host) {
m.logger.Debug("Starting DNS blacklist phase")
m.blockRequest(w, r, state, http.StatusForbidden, "dns_blacklist", "dns_blacklist_rule", r.Host,
m.blockRequest(w, r, state, http.StatusForbidden, "dns_blacklist", "dns_blacklist_rule",
zap.String("message", "Request blocked by DNS blacklist"),
zap.String("host", r.Host),
)
@@ -309,7 +314,7 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
path := r.URL.Path // Get the request path
if m.rateLimiter.isRateLimited(ip, path) {
m.incrementRateLimiterBlockedRequestsMetric() // Increment the counter in the Middleware
m.blockRequest(w, r, state, http.StatusTooManyRequests, "rate_limit", "rate_limit_rule", r.RemoteAddr,
m.blockRequest(w, r, state, http.StatusTooManyRequests, "rate_limit", "rate_limit_rule",
zap.String("message", "Request blocked by rate limit"),
)
if m.CustomResponses != nil {
@@ -323,20 +328,25 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
// Whitelisting
if m.CountryWhitelist.Enabled {
m.logger.Debug("Starting country whitelisting phase")
allowed, err := m.isCountryInList(r.RemoteAddr, m.CountryWhitelist.CountryList, m.CountryWhitelist.geoIP)
clientIP := getClientIP(r)
allowed, err := m.isCountryInList(clientIP, m.CountryWhitelist.CountryList, m.CountryWhitelist.geoIP)
if err != nil {
m.logRequest(zapcore.ErrorLevel, "Failed to check country whitelist",
r,
zap.Error(err),
)
m.blockRequest(w, r, state, http.StatusForbidden, "internal_error", "country_block_rule", r.RemoteAddr,
zap.String("message", "Request blocked due to internal error"),
)
m.logger.Debug("Country whitelisting phase completed - blocked due to error")
m.incrementGeoIPRequestsMetric(false) // Increment with false for error
return
if m.GeoIPFailOpen {
m.logger.Warn("GeoIP lookup failed (Whitelist); Failing OPEN")
} else {
m.blockRequest(w, r, state, http.StatusForbidden, "internal_error", "country_block_rule",
zap.String("message", "Request blocked due to internal error"),
)
m.logger.Debug("Country whitelisting phase completed - blocked due to error")
m.incrementGeoIPRequestsMetric(false) // Increment with false for error
return
}
} else if !allowed {
m.blockRequest(w, r, state, http.StatusForbidden, "country_block", "country_block_rule", r.RemoteAddr,
m.blockRequest(w, r, state, http.StatusForbidden, "country_block", "country_block_rule",
zap.String("message", "Request blocked by country"))
m.incrementGeoIPRequestsMetric(true) // Increment with true for blocked
if m.CustomResponses != nil {
@@ -348,24 +358,63 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
m.incrementGeoIPRequestsMetric(false) // Increment with false for no block
}
// ASN Blocking
if m.BlockASNs.Enabled {
m.logger.Debug("Starting ASN blocking phase")
clientIP := getClientIP(r)
blocked, err := m.geoIPHandler.IsASNInList(clientIP, m.BlockASNs.BlockedASNs, m.BlockASNs.geoIP)
if err != nil {
m.logRequest(zapcore.ErrorLevel, "Failed to check ASN blocking",
r,
zap.Error(err),
)
if m.GeoIPFailOpen {
m.logger.Warn("ASN lookup failed; Failing OPEN")
} else {
m.blockRequest(w, r, state, http.StatusForbidden, "internal_error", "asn_block_rule",
zap.String("message", "Request blocked due to internal error"),
)
m.logger.Debug("ASN blocking phase completed - blocked due to error")
m.incrementGeoIPRequestsMetric(false) // Increment with false for error
return
}
} else if blocked {
asnInfo := m.geoIPHandler.GetASN(clientIP, m.BlockASNs.geoIP)
m.blockRequest(w, r, state, http.StatusForbidden, "asn_block", "asn_block_rule",
zap.String("message", "Request blocked by ASN"),
zap.String("asn", asnInfo),
)
m.incrementGeoIPRequestsMetric(true) // Increment with true for blocked
if m.CustomResponses != nil {
m.writeCustomResponse(w, state.StatusCode)
}
return
}
m.logger.Debug("ASN blocking phase completed - not blocked")
}
// Blacklisting
if m.CountryBlacklist.Enabled {
m.logger.Debug("Starting country blacklisting phase")
blocked, err := m.isCountryInList(r.RemoteAddr, m.CountryBlacklist.CountryList, m.CountryBlacklist.geoIP)
clientIP := getClientIP(r)
blocked, err := m.isCountryInList(clientIP, m.CountryBlacklist.CountryList, m.CountryBlacklist.geoIP)
if err != nil {
m.logRequest(zapcore.ErrorLevel, "Failed to check country blacklisting",
r,
zap.Error(err),
)
m.blockRequest(w, r, state, http.StatusForbidden, "internal_error", "country_block_rule", r.RemoteAddr,
zap.String("message", "Request blocked due to internal error"),
)
m.logger.Debug("Country blacklisting phase completed - blocked due to error")
m.incrementGeoIPRequestsMetric(false) // Increment with false for error
return
if m.GeoIPFailOpen {
m.logger.Warn("GeoIP lookup failed (Blacklist); Failing OPEN")
} else {
m.blockRequest(w, r, state, http.StatusForbidden, "internal_error", "country_block_rule",
zap.String("message", "Request blocked due to internal error"),
)
m.logger.Debug("Country blacklisting phase completed - blocked due to error")
m.incrementGeoIPRequestsMetric(false) // Increment with false for error
return
}
} else if blocked {
m.blockRequest(w, r, state, http.StatusForbidden, "country_block", "country_block_rule", r.RemoteAddr,
m.blockRequest(w, r, state, http.StatusForbidden, "country_block", "country_block_rule",
zap.String("message", "Request blocked by country"))
m.incrementGeoIPRequestsMetric(true) // Increment with true for blocked
if m.CustomResponses != nil {
@@ -388,14 +437,14 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
m.logger.Debug("Starting rule evaluation for phase", zap.Int("phase", phase), zap.Int("rule_count", len(rules)))
for _, rule := range rules {
m.logger.Debug("Processing rule", zap.String("rule_id", string(rule.ID)), zap.Int("target_count", len(rule.Targets)))
m.logger.Debug("Processing rule", zap.String("rule_id", rule.ID), zap.Int("target_count", len(rule.Targets)))
// Use the custom type as the key
ctx := context.WithValue(r.Context(), ContextKeyRule("rule_id"), rule.ID)
r = r.WithContext(ctx)
for _, target := range rule.Targets {
m.logger.Debug("Extracting value for target", zap.String("target", target), zap.String("rule_id", string(rule.ID)))
m.logger.Debug("Extracting value for target", zap.String("target", target), zap.String("rule_id", rule.ID))
var value string
var err error
@@ -413,42 +462,44 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
if err != nil {
m.logger.Debug("Failed to extract value for target, skipping rule for this target",
zap.String("target", target),
zap.String("rule_id", string(rule.ID)),
zap.String("rule_id", rule.ID),
zap.Error(err),
)
continue
}
redactedValue := m.requestValueExtractor.RedactValueIfSensitive(target, value)
m.logger.Debug("Extracted value",
zap.String("rule_id", string(rule.ID)),
zap.String("rule_id", rule.ID),
zap.String("target", target),
zap.String("value", value),
zap.String("value", redactedValue),
)
if rule.regex.MatchString(value) {
m.logger.Debug("Rule matched",
zap.String("rule_id", string(rule.ID)),
zap.String("rule_id", rule.ID),
zap.String("target", target),
zap.String("value", value),
zap.String("value", redactedValue),
)
// FIXED: Correctly interpret processRuleMatch return value
var shouldContinue bool
if phase == 3 || phase == 4 {
if recorder, ok := w.(*responseRecorder); ok {
shouldContinue = m.processRuleMatch(recorder, r, &rule, value, state)
shouldContinue = m.processRuleMatch(recorder, r, &rule, target, value, state)
} else {
shouldContinue = m.processRuleMatch(w, r, &rule, value, state)
shouldContinue = m.processRuleMatch(w, r, &rule, target, value, state)
}
} else {
shouldContinue = m.processRuleMatch(w, r, &rule, value, state)
shouldContinue = m.processRuleMatch(w, r, &rule, target, value, state)
}
// If processRuleMatch returned false or state is now blocked, stop processing
if !shouldContinue || state.Blocked || state.ResponseWritten {
m.logger.Debug("Rule evaluation stopping due to blocking or rule directive",
zap.Int("phase", phase),
zap.String("rule_id", string(rule.ID)),
zap.String("rule_id", rule.ID),
zap.Bool("continue", shouldContinue),
zap.Bool("blocked", state.Blocked),
)
@@ -460,9 +511,9 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
}
} else {
m.logger.Debug("Rule did not match",
zap.String("rule_id", string(rule.ID)),
zap.String("rule_id", rule.ID),
zap.String("target", target),
zap.String("value", value),
zap.String("value", redactedValue),
)
}
}

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"net/netip"
"os"
"regexp"
"strings"
"testing"
@@ -26,8 +27,9 @@ func TestBlockedRequestPhase1_DNSBlacklist(t *testing.T) {
dnsBlacklist: map[string]struct{}{
"malicious.domain": {},
},
ipBlacklist: iptrie.NewTrie(),
CustomResponses: customResponse,
ipBlacklist: iptrie.NewTrie(),
CustomResponses: customResponse,
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
w := httptest.NewRecorder()
@@ -60,6 +62,9 @@ func TestBlockedRequestPhase1_DNSBlacklist(t *testing.T) {
}
func TestBlockedRequestPhase1_GeoIPBlocking(t *testing.T) {
if _, err := os.Stat(geoIPdata); os.IsNotExist(err) {
t.Skip("GeoIP database not found, skipping test")
}
logger, err := zap.NewDevelopment()
assert.NoError(t, err)
@@ -77,7 +82,8 @@ func TestBlockedRequestPhase1_GeoIPBlocking(t *testing.T) {
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
geoIP: geoIPBlock,
},
CustomResponses: customResponse,
CustomResponses: customResponse,
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
wlMiddleware := &Middleware{
@@ -90,7 +96,8 @@ func TestBlockedRequestPhase1_GeoIPBlocking(t *testing.T) {
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
geoIP: geoIPBlock,
},
CustomResponses: customResponse,
CustomResponses: customResponse,
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
blackWhiteMw := &Middleware{
@@ -109,7 +116,8 @@ func TestBlockedRequestPhase1_GeoIPBlocking(t *testing.T) {
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
geoIP: geoIPBlock,
},
CustomResponses: customResponse,
CustomResponses: customResponse,
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -205,9 +213,10 @@ func TestBlockedRequestPhase1_IPBlocking(t *testing.T) {
t.Run("Allow unblocked CIDR", func(t *testing.T) {
middleware := &Middleware{
logger: logger,
ipBlacklist: blackList,
CustomResponses: customResponse,
logger: logger,
ipBlacklist: blackList,
CustomResponses: customResponse,
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -222,9 +231,10 @@ func TestBlockedRequestPhase1_IPBlocking(t *testing.T) {
t.Run("Blocks blacklisted CIDR", func(t *testing.T) {
middleware := &Middleware{
logger: logger,
ipBlacklist: blackList,
CustomResponses: customResponse,
logger: logger,
ipBlacklist: blackList,
CustomResponses: customResponse,
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -260,7 +270,7 @@ func TestHandlePhase_Phase2_NiktoUserAgent(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
CustomResponses: customResponse,
}
@@ -315,7 +325,7 @@ func TestBlockedRequestPhase1_HeaderRegex(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -368,7 +378,7 @@ func TestBlockedRequestPhase1_HeaderRegex_SpecificValue(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -421,7 +431,7 @@ func TestBlockedRequestPhase1_HeaderRegex_CommaSeparatedTargets(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -475,7 +485,7 @@ func TestBlockedRequestPhase1_CombinedConditions(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", "http://bad-host.com", nil)
@@ -528,7 +538,7 @@ func TestBlockedRequestPhase1_NoMatch(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -581,7 +591,7 @@ func TestBlockedRequestPhase1_HeaderRegex_EmptyHeader(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -633,7 +643,7 @@ func TestBlockedRequestPhase1_HeaderRegex_MissingHeader(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil) // Header not set
@@ -685,7 +695,7 @@ func TestBlockedRequestPhase1_HeaderRegex_ComplexPattern(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -738,7 +748,7 @@ func TestBlockedRequestPhase1_MultiTargetMatch(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -791,7 +801,7 @@ func TestBlockedRequestPhase1_MultiTargetNoMatch(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -845,7 +855,7 @@ func TestBlockedRequestPhase1_URLParameterRegex_NoMatch(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", "http://example.com?param1=good-param-value¶m2=good-value", nil)
@@ -905,7 +915,7 @@ func TestBlockedRequestPhase1_MultipleRules(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", "http://bad-host.com", nil)
@@ -981,7 +991,7 @@ func TestBlockedRequestPhase2_BodyRegex(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("POST", testURL,
@@ -1040,7 +1050,7 @@ func TestBlockedRequestPhase2_BodyRegex_JSON(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("POST", testURL,
@@ -1099,7 +1109,7 @@ func TestBlockedRequestPhase2_BodyRegex_FormURLEncoded(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("POST", testURL,
@@ -1154,7 +1164,7 @@ func TestBlockedRequestPhase2_BodyRegex_SpecificPattern(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("POST", testURL,
@@ -1213,7 +1223,7 @@ func TestBlockedRequestPhase2_BodyRegex_NoMatch(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("POST", testURL,
@@ -1272,7 +1282,7 @@ func TestBlockedRequestPhase2_BodyRegex_NoMatch_MultipartForm(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
body := &bytes.Buffer{}
@@ -1340,7 +1350,7 @@ func TestBlockedRequestPhase2_BodyRegex_NoBody(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("POST", testURL, nil)
@@ -1386,7 +1396,7 @@ func TestBlockedRequestPhase3_ResponseHeaderRegex_NoMatch(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
mockHandler := func() caddyhttp.Handler {
@@ -1442,7 +1452,7 @@ func TestBlockedRequestPhase4_ResponseBodyRegex_EmptyBody(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
mockHandler := func() caddyhttp.Handler {
@@ -1498,7 +1508,7 @@ func TestBlockedRequestPhase4_ResponseBodyRegex_NoBody(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
mockHandler := func() caddyhttp.Handler {
@@ -1552,7 +1562,7 @@ func TestBlockedRequestPhase3_ResponseHeaderRegex_NoSetCookie(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
mockHandler := func() caddyhttp.Handler {
return caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
@@ -1608,7 +1618,7 @@ func TestBlockedRequestPhase1_HeaderRegex_CaseInsensitive(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -1661,7 +1671,7 @@ func TestBlockedRequestPhase1_HeaderRegex_MultipleMatchingHeaders(t *testing.T)
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
req := httptest.NewRequest("GET", testURL, nil)
@@ -1747,16 +1757,16 @@ func TestBlockedRequestPhase1_RateLimiting_MultiplePaths(t *testing.T) {
middleware := &Middleware{
logger: logger,
rateLimiter: func() *RateLimiter {
rl := &RateLimiter{
config: RateLimit{
Requests: 1,
Window: time.Minute,
CleanupInterval: time.Minute,
Paths: []string{"/api/v1/.*", "/admin/.*"},
MatchAllPaths: false,
},
requests: make(map[string]map[string]*requestCounter),
stopCleanup: make(chan struct{}),
config := RateLimit{
Requests: 1,
Window: time.Minute,
CleanupInterval: time.Minute,
Paths: []string{"/api/v1/.*", "/admin/.*"},
MatchAllPaths: false,
}
rl, err := NewRateLimiter(config)
if err != nil {
t.Fatalf("Failed to create rate limiter: %v", err)
}
rl.startCleanup()
return rl

View File

@@ -2,6 +2,7 @@ package caddywaf
import (
"net"
"net/http"
"os"
"strings"
)
@@ -19,6 +20,8 @@ func fileExists(path string) bool {
}
// isIPv4 - checks if input IP is of type v4
//
//nolint:unused
func isIPv4(addr string) bool {
return strings.Count(addr, ":") < 2
}
@@ -43,3 +46,14 @@ func extractIP(remoteAddr string) string {
}
return host
}
// getClientIP returns the real client IP, checking X-Forwarded-For header first.
func getClientIP(r *http.Request) string {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
ips := strings.Split(xff, ",")
if len(ips) > 0 {
return strings.TrimSpace(ips[0])
}
}
return r.RemoteAddr
}

View File

@@ -1,6 +1,7 @@
package caddywaf
import (
"net/http"
"os"
"testing"
)
@@ -48,3 +49,57 @@ func TestFileExists(t *testing.T) {
})
}
}
func TestGetClientIP(t *testing.T) {
tests := []struct {
name string
remoteAddr string
xff string
want string
}{
{
name: "no X-Forwarded-For, use RemoteAddr",
remoteAddr: "192.168.1.1:12345",
xff: "",
want: "192.168.1.1:12345",
},
{
name: "single IP in X-Forwarded-For",
remoteAddr: "10.0.0.1:12345",
xff: "203.0.113.50",
want: "203.0.113.50",
},
{
name: "multiple IPs in X-Forwarded-For",
remoteAddr: "10.0.0.1:12345",
xff: "203.0.113.50, 70.41.3.18, 150.172.238.178",
want: "203.0.113.50",
},
{
name: "X-Forwarded-For with spaces",
remoteAddr: "10.0.0.1:12345",
xff: " 203.0.113.50 , 70.41.3.18 ",
want: "203.0.113.50",
},
{
name: "IPv6 in X-Forwarded-For",
remoteAddr: "10.0.0.1:12345",
xff: "2001:db8::1",
want: "2001:db8::1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "/", nil)
req.RemoteAddr = tt.remoteAddr
if tt.xff != "" {
req.Header.Set("X-Forwarded-For", tt.xff)
}
if got := getClientIP(req); got != tt.want {
t.Errorf("getClientIP() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,3 +1,5 @@
//go:build it
package caddywaf_test
import (

View File

@@ -129,7 +129,7 @@ func (m *Middleware) redactSensitiveFields(fields []zap.Field) []zap.Field {
// prepareLogFields consolidates the logic for preparing log fields, including common fields and log_id.
func (m *Middleware) prepareLogFields(r *http.Request, fields []zap.Field) []zap.Field {
var logID string
var allFields []zap.Field
allFields := make([]zap.Field, 0)
// Initialize with common fields
var sourceIP, userAgent, requestMethod, requestPath, queryParams string

View File

@@ -58,33 +58,41 @@ func NewRateLimiter(config RateLimit) (*RateLimiter, error) {
// isRateLimited checks if a given IP is rate limited for a specific path.
func (rl *RateLimiter) isRateLimited(ip, path string) bool {
now := time.Now()
rl.Lock() // Use Lock for write operations or potential creation of nested maps.
defer rl.Unlock()
rl.incrementTotalRequestsMetric() // Increment the total requests received
// SOTA Pattern: Reduce Lock Contention (move expensive regex out of critical section)
matched := false
var key string
// 1. Determine if this path needs limiting (Read-only config access, safe without lock if config is immutable)
if rl.config.MatchAllPaths {
matched = true
key = ip
} else {
// Check if path is matching
if len(rl.config.PathRegexes) > 0 {
matched := false
for _, regex := range rl.config.PathRegexes {
if regex.MatchString(path) {
matched = true
break
}
}
if !matched {
return false // Path does not match any configured paths, no rate limiting
if matched {
key = ip + path
}
}
key = ip + path
}
if !matched && !rl.config.MatchAllPaths {
// Optimization: If no path matched, we don't need to track this request
rl.incrementTotalRequestsMetric()
return false
}
now := time.Now()
rl.Lock() // Critical Section Start
defer rl.Unlock()
rl.incrementTotalRequestsMetric() // Metric under lock to ensure consistency (or use atomic outside)
// Initialize the nested map if it doesn't exist
if _, exists := rl.requests[ip]; !exists {
rl.requests[ip] = make(map[string]*requestCounter)

View File

@@ -8,6 +8,7 @@ import (
"net/url"
"strconv"
"strings"
"unsafe"
"go.uber.org/zap"
)
@@ -16,6 +17,7 @@ import (
type RequestValueExtractor struct {
logger *zap.Logger
redactSensitiveData bool // Add this field
maxBodySize int64
}
// Extraction Target Constants - Improved Readability and Maintainability
@@ -47,8 +49,11 @@ const (
var sensitiveTargets = []string{"password", "token", "apikey", "authorization", "secret"} // Define sensitive targets for redaction as package variable
// NewRequestValueExtractor creates a new RequestValueExtractor with a given logger
func NewRequestValueExtractor(logger *zap.Logger, redactSensitiveData bool) *RequestValueExtractor {
return &RequestValueExtractor{logger: logger, redactSensitiveData: redactSensitiveData}
func NewRequestValueExtractor(logger *zap.Logger, redactSensitiveData bool, maxBodySize int64) *RequestValueExtractor {
if maxBodySize <= 0 {
maxBodySize = 10 * 1024 * 1024 // Default 10MB
}
return &RequestValueExtractor{logger: logger, redactSensitiveData: redactSensitiveData, maxBodySize: maxBodySize}
}
// ExtractValue extracts values based on the target, handling comma separated targets
@@ -166,7 +171,7 @@ func (rve *RequestValueExtractor) extractSingleValue(target string, r *http.Requ
}
// Redact sensitive fields before returning the value (as before)
value := rve.redactValueIfSensitive(target, unredactedValue)
value := rve.RedactValueIfSensitive(target, unredactedValue)
// Log the extracted value (redacted if necessary)
rve.logger.Debug("Extracted value",
@@ -204,13 +209,29 @@ func (rve *RequestValueExtractor) extractBody(r *http.Request, target string) (s
rve.logger.Debug("Request body is empty", zap.String("target", target))
return "", fmt.Errorf("request body is empty for target: %s", target)
}
bodyBytes, err := io.ReadAll(r.Body)
reader := io.LimitReader(r.Body, rve.maxBodySize)
bodyBytes, err := io.ReadAll(reader)
if err != nil {
rve.logger.Error("Failed to read request body", zap.Error(err))
return "", fmt.Errorf("failed to read request body for target %s: %w", target, err)
}
r.Body = http.NoBody // Reset body for next read - using http.NoBody
return string(bodyBytes), nil
// Restore body for next read, verifying if we need to combine with remaining body
// We use io.MultiReader to concatenate the bytes we read with the *remaining* bytes in the original body.
// This ensures that even if we hit the limit, the downstream consumer can read the full body.
// We also ensure the original Closer is preserved.
r.Body = &struct {
io.Reader
io.Closer
}{
Reader: io.MultiReader(strings.NewReader(string(bodyBytes)), r.Body),
Closer: r.Body,
}
// SOTA Pattern: Zero-Copy (avoid allocation for string conversion)
if len(bodyBytes) == 0 {
return "", nil
}
return unsafe.String(&bodyBytes[0], len(bodyBytes)), nil
}
// Helper function to extract all headers
@@ -334,12 +355,13 @@ func (rve *RequestValueExtractor) extractValueForJSONPath(r *http.Request, jsonP
return "", fmt.Errorf("request body is empty for target: %s", target)
}
bodyBytes, err := io.ReadAll(r.Body)
reader := io.LimitReader(r.Body, rve.maxBodySize)
bodyBytes, err := io.ReadAll(reader)
if err != nil {
rve.logger.Error("Failed to read request body", zap.Error(err))
return "", fmt.Errorf("failed to read request body for JSON_PATH target %s: %w", target, err)
}
r.Body = http.NoBody // Reset body for next read
r.Body = io.NopCloser(strings.NewReader(string(bodyBytes))) // Restore body for next read
// Use helper method to dynamically extract value based on JSON path (e.g., 'data.items.0.name').
unredactedValue, err := rve.extractJSONPath(string(bodyBytes), jsonPath)
@@ -351,7 +373,7 @@ func (rve *RequestValueExtractor) extractValueForJSONPath(r *http.Request, jsonP
}
// Helper function to redact value if target is sensitive
func (rve *RequestValueExtractor) redactValueIfSensitive(target string, value string) string {
func (rve *RequestValueExtractor) RedactValueIfSensitive(target string, value string) string {
if rve.redactSensitiveData {
for _, sensitive := range sensitiveTargets {
if strings.Contains(strings.ToLower(target), sensitive) {

76
request_body_test.go Normal file
View File

@@ -0,0 +1,76 @@
package caddywaf
import (
"bytes"
"io"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
)
func TestMiddleware_RequestBodyRestoration(t *testing.T) {
// Setup middleware
// Create extractor
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false, 1024*1024) // 1MB limit
t.Run("Body < MaxSize", func(t *testing.T) {
bodyContent := "small body"
req := httptest.NewRequest("POST", "/", strings.NewReader(bodyContent))
// Extract body (simulates WAF inspecting it)
extracted, err := rve.ExtractValue(TargetBody, req, nil)
assert.NoError(t, err)
assert.Equal(t, bodyContent, extracted)
// Verify body is restored and readable again
restoredBody, err := io.ReadAll(req.Body)
assert.NoError(t, err)
assert.Equal(t, bodyContent, string(restoredBody))
})
t.Run("Body > MaxSize", func(t *testing.T) {
// Max size is 1MB. Let's send 2MB.
size := 2 * 1024 * 1024
bodyContent := make([]byte, size)
// Fill with some data
for i := 0; i < size; i++ {
bodyContent[i] = 'a'
}
req := httptest.NewRequest("POST", "/", bytes.NewReader(bodyContent))
// Extract body
extracted, err := rve.ExtractValue(TargetBody, req, nil)
assert.NoError(t, err)
// Extracted should be truncated to 1MB
assert.Equal(t, 1024*1024, len(extracted))
// Verify body restoration
// If the implementation is naive, it might only restore the 1MB we read,
// and the rest is lost because LimitReader consumed the prefix.
// OR, if it restores using LimitReader's underlying reader, maybe it's fine?
// Wait, LimitReader wraps the original request body.
// We read from LimitReader.
// If we replace req.Body with a new reader containing key read bytes...
// The original req.Body (the socket/buffer) has been advanced by 1MB.
// If we set req.Body = NewReader(readBytes), subsequent consumers will read 1MB and then EOF.
// The remaining 1MB in the original req.Body is skipped/lost!
restored, err := io.ReadAll(req.Body)
assert.NoError(t, err)
// This assertion is expected to FAIL if the bug exists for large bodies.
// Use NotEqual or expect failure if we want to demonstrate the bug?
// User says "POST request's body gone". They didn't specify size.
// But let's see what happens.
if len(restored) != size {
t.Logf("Bug confirmed: Expected %d bytes, got %d", size, len(restored))
}
assert.Equal(t, size, len(restored))
})
}

View File

@@ -21,7 +21,7 @@ import (
func TestExtractValue(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, true)
rve := NewRequestValueExtractor(logger, true, 0)
tests := []struct {
name string
@@ -144,8 +144,8 @@ func TestRedactValueIfSensitive(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rve := NewRequestValueExtractor(logger, tt.redactSensitive)
result := rve.redactValueIfSensitive(tt.target, tt.value)
rve := NewRequestValueExtractor(logger, tt.redactSensitive, 0)
result := rve.RedactValueIfSensitive(tt.target, tt.value)
if tt.expectedRedacted && result != "REDACTED" {
t.Errorf("Expected REDACTED but got %q", result)
@@ -159,7 +159,7 @@ func TestRedactValueIfSensitive(t *testing.T) {
func TestExtractValue_HeaderCaseInsensitive(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("x-test-header", "test-value")
@@ -172,7 +172,7 @@ func TestExtractValue_HeaderCaseInsensitive(t *testing.T) {
func TestExtractValue_EmptyTarget(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
@@ -184,7 +184,7 @@ func TestExtractValue_EmptyTarget(t *testing.T) {
func TestExtractValue_Method(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
@@ -196,7 +196,7 @@ func TestExtractValue_Method(t *testing.T) {
func TestExtractValue_RemoteIP(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
req.RemoteAddr = localIP
@@ -209,7 +209,7 @@ func TestExtractValue_RemoteIP(t *testing.T) {
func TestExtractValue_Protocol(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
req.Proto = "HTTP/1.1"
@@ -222,7 +222,7 @@ func TestExtractValue_Protocol(t *testing.T) {
func TestExtractValue_Host(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
req.Host = "example.com"
@@ -235,7 +235,7 @@ func TestExtractValue_Host(t *testing.T) {
func TestExtractValue_Args(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/?foo=bar&baz=qux", nil)
w := httptest.NewRecorder()
@@ -247,7 +247,7 @@ func TestExtractValue_Args(t *testing.T) {
func TestExtractValue_UserAgent(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("User-Agent", "test-agent")
@@ -260,7 +260,7 @@ func TestExtractValue_UserAgent(t *testing.T) {
func TestExtractValue_Path(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/test-path", nil)
w := httptest.NewRecorder()
@@ -272,7 +272,7 @@ func TestExtractValue_Path(t *testing.T) {
func TestExtractValue_URI(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/test-path?foo=bar", nil)
w := httptest.NewRecorder()
@@ -284,7 +284,7 @@ func TestExtractValue_URI(t *testing.T) {
func TestExtractValue_Body(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
body := bytes.NewBufferString("test body")
req := httptest.NewRequest("POST", "/", body)
@@ -297,7 +297,7 @@ func TestExtractValue_Body(t *testing.T) {
func TestExtractValue_Headers(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("X-Test-Header", "test-value")
@@ -310,7 +310,7 @@ func TestExtractValue_Headers(t *testing.T) {
func TestExtractValue_Cookies(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
req.AddCookie(&http.Cookie{Name: "test-cookie", Value: "test-value"})
@@ -323,7 +323,7 @@ func TestExtractValue_Cookies(t *testing.T) {
func TestExtractValue_UnknownTarget(t *testing.T) {
logger := zap.NewNop()
rve := NewRequestValueExtractor(logger, false)
rve := NewRequestValueExtractor(logger, false, 0)
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
@@ -364,10 +364,11 @@ func newMockLogger() *MockLogger {
func TestProcessRuleMatch_HighScore(t *testing.T) {
logger := newMockLogger()
middleware := &Middleware{
logger: logger.Logger,
AnomalyThreshold: 100, // High threshold
ruleHits: sync.Map{},
muMetrics: sync.RWMutex{},
logger: logger.Logger,
AnomalyThreshold: 100, // High threshold
ruleHits: sync.Map{},
muMetrics: sync.RWMutex{},
requestValueExtractor: NewRequestValueExtractor(logger.Logger, false, 0), // Initialize
}
rule := &Rule{
@@ -394,7 +395,7 @@ func TestProcessRuleMatch_HighScore(t *testing.T) {
w := httptest.NewRecorder()
// Test blocking rule with high score
shouldContinue := middleware.processRuleMatch(w, req, rule, "value", state)
shouldContinue := middleware.processRuleMatch(w, req, rule, "header", "value", state)
assert.False(t, shouldContinue)
assert.Equal(t, http.StatusForbidden, w.Code)
assert.True(t, state.Blocked)
@@ -419,7 +420,7 @@ func TestValidateRule_EmptyTargets(t *testing.T) {
func TestNewRequestValueExtractor(t *testing.T) {
logger := zap.NewNop()
redactSensitiveData := true
rve := NewRequestValueExtractor(logger, redactSensitiveData)
rve := NewRequestValueExtractor(logger, redactSensitiveData, 0)
assert.NotNil(t, rve)
assert.Equal(t, logger, rve.logger)
@@ -447,7 +448,7 @@ func TestConcurrentRuleEvaluation(t *testing.T) {
ruleCache: NewRuleCache(),
ipBlacklist: iptrie.NewTrie(),
dnsBlacklist: map[string]struct{}{},
requestValueExtractor: NewRequestValueExtractor(logger, false),
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
rateLimiter: func() *RateLimiter {
rl, err := NewRateLimiter(RateLimit{
Requests: 10,

View File

@@ -18,7 +18,7 @@ func (m *Middleware) allowRequest(state *WAFState) {
}
// blockRequest handles blocking a request and logging the details.
func (m *Middleware) blockRequest(recorder http.ResponseWriter, r *http.Request, state *WAFState, statusCode int, reason, ruleID, matchedValue string, fields ...zap.Field) {
func (m *Middleware) blockRequest(recorder http.ResponseWriter, r *http.Request, state *WAFState, statusCode int, reason, ruleID string, fields ...zap.Field) {
// CRITICAL FIX: Set these flags before any other operations
state.Blocked = true
state.StatusCode = statusCode

View File

@@ -37,7 +37,7 @@ func TestBlockRequest(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/test", nil)
state := &WAFState{}
m.blockRequest(w, r, state, http.StatusForbidden, "test reason", "rule1", "match1")
m.blockRequest(w, r, state, http.StatusForbidden, "test reason", "rule1")
assert.Equal(t, http.StatusForbidden, w.Code)
assert.Equal(t, "Blocked", w.Body.String())
@@ -56,7 +56,7 @@ func TestBlockRequest(t *testing.T) {
r = r.WithContext(ctx)
state := &WAFState{}
m.blockRequest(w, r, state, http.StatusForbidden, "test reason", "rule1", "match1")
m.blockRequest(w, r, state, http.StatusForbidden, "test reason", "rule1")
assert.Equal(t, http.StatusForbidden, w.Code)
assert.True(t, state.Blocked)
@@ -75,7 +75,7 @@ func TestBlockRequest(t *testing.T) {
}
recorder := NewResponseRecorder(w)
m.blockRequest(recorder, r, state, http.StatusForbidden, "test reason", "rule1", "match1")
m.blockRequest(recorder, r, state, http.StatusForbidden, "test reason", "rule1")
assert.Equal(t, http.StatusForbidden, recorder.StatusCode()) // Check the Recorder status code instead
assert.True(t, state.ResponseWritten) // Check that the ResponseWritten flag is set

View File

@@ -8,19 +8,21 @@ import (
"os"
"regexp"
"sort"
"strings"
"sync/atomic"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, rule *Rule, value string, state *WAFState) bool {
func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, rule *Rule, target, value string, state *WAFState) bool {
logID := r.Context().Value(ContextKeyLogId("logID")).(string)
redactedValue := m.requestValueExtractor.RedactValueIfSensitive(target, value)
m.logRequest(zapcore.DebugLevel, "Rule Matched", r, // More concise log message
zap.String("rule_id", string(rule.ID)),
zap.String("target", strings.Join(rule.Targets, ",")),
zap.String("value", value),
zap.String("rule_id", rule.ID),
zap.String("target", target), // Log the specific target that matched
zap.String("value", redactedValue),
zap.String("description", rule.Description),
zap.Int("score", rule.Score),
zap.Int("anomaly_threshold_config", m.AnomalyThreshold), // ADDED: Log configured anomaly threshold
@@ -37,7 +39,7 @@ func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, ru
state.TotalScore += rule.Score
m.logRequest(zapcore.DebugLevel, "Anomaly score increased", r, // Corrected argument order - 'r' is now the third argument
zap.String("log_id", logID),
zap.String("rule_id", string(rule.ID)),
zap.String("rule_id", rule.ID),
zap.Int("score_increase", rule.Score),
zap.Int("old_score", oldScore),
zap.Int("new_score", state.TotalScore),
@@ -51,7 +53,7 @@ func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, ru
// Debug the actual action field value to verify what's being used
m.logger.Debug("Rule action/mode check",
zap.String("rule_id", string(rule.ID)),
zap.String("rule_id", rule.ID),
zap.String("action_field", rule.Action),
zap.Int("score", rule.Score),
zap.Int("threshold", m.AnomalyThreshold),
@@ -77,7 +79,7 @@ func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, ru
state.StatusCode = http.StatusForbidden
// Block the request and write the response immediately
m.blockRequest(w, r, state, http.StatusForbidden, blockReason, string(rule.ID), value,
m.blockRequest(w, r, state, http.StatusForbidden, blockReason, rule.ID,
zap.Int("total_score", state.TotalScore),
zap.Int("anomaly_threshold", m.AnomalyThreshold),
zap.String("final_block_reason", blockReason),
@@ -92,14 +94,14 @@ func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, ru
if rule.Action == "log" {
m.logRequest(zapcore.InfoLevel, "Rule action: Log", r,
zap.String("log_id", logID),
zap.String("rule_id", string(rule.ID)),
zap.String("rule_id", rule.ID),
zap.Int("total_score", state.TotalScore), // ADDED: Log total score for log action
zap.Int("anomaly_threshold", m.AnomalyThreshold), // ADDED: Log anomaly threshold for log action
)
} else if !shouldBlock && !state.ResponseWritten {
m.logRequest(zapcore.DebugLevel, "Rule action: No Block", r,
zap.String("log_id", logID),
zap.String("rule_id", string(rule.ID)),
zap.String("rule_id", rule.ID),
zap.String("action", rule.Action),
zap.Int("total_score", state.TotalScore),
zap.Int("anomaly_threshold", m.AnomalyThreshold),
@@ -112,14 +114,14 @@ func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, ru
// incrementRuleHitCount increments the hit counter for a given rule ID.
func (m *Middleware) incrementRuleHitCount(ruleID RuleID) {
hitCount := HitCount(1) // Default increment
if currentCount, loaded := m.ruleHits.Load(ruleID); loaded {
hitCount = currentCount.(HitCount) + 1
}
m.ruleHits.Store(ruleID, hitCount)
// SOTA Pattern: Wait-Free / Lock-Free Data Structures (using atomic)
counterInterface, _ := m.ruleHits.LoadOrStore(ruleID, &atomic.Int64{})
counter := counterInterface.(*atomic.Int64)
newVal := counter.Add(1)
m.logger.Debug("Rule hit count updated",
zap.String("rule_id", string(ruleID)),
zap.Int("hit_count", int(hitCount)), // More descriptive log field
zap.Int64("hit_count", newVal),
)
}
@@ -240,11 +242,11 @@ func (m *Middleware) loadRulesFromFile(path string, ruleIDs map[string]bool) (va
continue
}
if _, exists := ruleIDs[string(rule.ID)]; exists {
if _, exists := ruleIDs[rule.ID]; exists {
fileInvalidRules = append(fileInvalidRules, fmt.Sprintf("Duplicate rule ID '%s' at index %d", rule.ID, i))
continue
}
ruleIDs[string(rule.ID)] = true // Track rule IDs to prevent duplicates
ruleIDs[rule.ID] = true // Track rule IDs to prevent duplicates
// RuleCache handling (compile and cache regex)
if cachedRegex, exists := m.ruleCache.Get(rule.ID); exists {

View File

@@ -146,10 +146,11 @@ func TestProcessRuleMatch(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &Middleware{
logger: logger,
AnomalyThreshold: tt.anomalyThreshold,
ruleHits: sync.Map{},
muMetrics: sync.RWMutex{},
logger: logger,
AnomalyThreshold: tt.anomalyThreshold,
ruleHits: sync.Map{},
muMetrics: sync.RWMutex{},
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
}
w := httptest.NewRecorder()
@@ -162,7 +163,7 @@ func TestProcessRuleMatch(t *testing.T) {
ResponseWritten: tt.responseWritten,
}
result := m.processRuleMatch(w, r, &tt.rule, "test-value", state)
result := m.processRuleMatch(w, r, &tt.rule, "ARGS", "test-value", state)
if result == tt.wantBlock {
t.Errorf("processRuleMatch() returned %v, want %v", result, !tt.wantBlock)
}

View File

@@ -1,9 +1,24 @@
[
{
"id": "BLOCK-NIKTO",
"phase": 2,
"pattern": "nikto",
"targets": [
"USER_AGENT"
],
"severity": "critical",
"score": 5,
"mode": "block",
"description": "Block Nikto scanner",
"priority": 10
},
{
"id": "TEST-RULE-1",
"phase": 2,
"pattern": "low_score_test",
"targets": ["URL_PARAM:test"],
"targets": [
"URL_PARAM:test"
],
"severity": "low",
"score": 1,
"mode": "log",
@@ -14,7 +29,9 @@
"id": "TEST-RULE-PARAM1",
"phase": 2,
"pattern": "score2",
"targets": ["URL_PARAM:param1"],
"targets": [
"URL_PARAM:param1"
],
"severity": "medium",
"score": 2,
"mode": "log",
@@ -25,7 +42,9 @@
"id": "TEST-RULE-PARAM2",
"phase": 2,
"pattern": "score2",
"targets": ["URL_PARAM:param2"],
"targets": [
"URL_PARAM:param2"
],
"severity": "medium",
"score": 2,
"mode": "log",
@@ -36,7 +55,9 @@
"id": "TEST-RULE-PARAM1-HIGH",
"phase": 2,
"pattern": "score3",
"targets": ["URL_PARAM:param1"],
"targets": [
"URL_PARAM:param1"
],
"severity": "high",
"score": 3,
"mode": "log",
@@ -47,7 +68,9 @@
"id": "TEST-RULE-PARAM2-HIGH",
"phase": 2,
"pattern": "score3",
"targets": ["URL_PARAM:param2"],
"targets": [
"URL_PARAM:param2"
],
"severity": "high",
"score": 3,
"mode": "log",
@@ -58,7 +81,9 @@
"id": "TEST-RULE-PARAM3-HIGH",
"phase": 2,
"pattern": "score3",
"targets": ["URL_PARAM:param3"],
"targets": [
"URL_PARAM:param3"
],
"severity": "high",
"score": 3,
"mode": "log",
@@ -69,7 +94,9 @@
"id": "TEST-RULE-BLOCK",
"phase": 2,
"pattern": "true",
"targets": ["URL_PARAM:block"],
"targets": [
"URL_PARAM:block"
],
"severity": "critical",
"score": 0,
"mode": "block",
@@ -80,7 +107,9 @@
"id": "TEST-RULE-INCR-1",
"phase": 2,
"pattern": "score1",
"targets": ["URL_PARAM:increment"],
"targets": [
"URL_PARAM:increment"
],
"severity": "low",
"score": 1,
"mode": "log",
@@ -91,7 +120,9 @@
"id": "TEST-RULE-INCR-2",
"phase": 2,
"pattern": "score2",
"targets": ["URL_PARAM:increment"],
"targets": [
"URL_PARAM:increment"
],
"severity": "medium",
"score": 2,
"mode": "log",
@@ -102,11 +133,13 @@
"id": "TEST-RULE-INCR-3",
"phase": 2,
"pattern": "score3",
"targets": ["URL_PARAM:increment"],
"targets": [
"URL_PARAM:increment"
],
"severity": "high",
"score": 3,
"mode": "log",
"description": "Incremental test rule 3",
"priority": 10
}
]
]

View File

@@ -7,7 +7,7 @@
:8080 {
route {
waf {
rule_file /Users/fab/GitHub/caddy-waf/sample_rules.json
rule_file ./sample_rules.json
anomaly_threshold 5
log_severity debug
metrics_endpoint /metrics

2
tor.go
View File

@@ -135,7 +135,7 @@ func (t *TorConfig) readExistingBlacklist() ([]string, error) {
// writeBlacklist writes the updated IP blacklist to the file.
func (t *TorConfig) writeBlacklist(ips []string) error {
data := strings.Join(ips, "\n")
err := os.WriteFile(t.TORIPBlacklistFile, []byte(data), 0o644)
err := os.WriteFile(t.TORIPBlacklistFile, []byte(data), 0o600)
if err != nil {
return fmt.Errorf("failed to write IP blacklist file %s: %w", t.TORIPBlacklistFile, err) // Improved error message with filename
}

View File

@@ -47,6 +47,14 @@ type CountryAccessFilter struct {
geoIP *maxminddb.Reader `json:"-"` // Explicitly mark as not serialized
}
// ASNAccessFilter struct
type ASNAccessFilter struct {
Enabled bool `json:"enabled"`
BlockedASNs []string `json:"blocked_asns"`
GeoIPDBPath string `json:"geoip_db_path"`
geoIP *maxminddb.Reader `json:"-"` // Explicitly mark as not serialized
}
// GeoIPRecord struct
type GeoIPRecord struct {
Country struct {
@@ -54,6 +62,12 @@ type GeoIPRecord struct {
} `maxminddb:"country"`
}
// ASNRecord struct
type ASNRecord struct {
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"`
}
// Rule struct
type Rule struct {
ID string `json:"id"`
@@ -106,6 +120,7 @@ type Middleware struct {
AnomalyThreshold int `json:"anomaly_threshold"`
CountryBlacklist CountryAccessFilter `json:"country_blacklist"`
CountryWhitelist CountryAccessFilter `json:"country_whitelist"`
BlockASNs ASNAccessFilter `json:"block_asns"`
Rules map[int][]Rule `json:"-"`
ipBlacklist *iptrie.Trie `json:"-"`
dnsBlacklist map[string]struct{} `json:"-"` // Changed to map[string]struct{}
@@ -120,8 +135,10 @@ type Middleware struct {
CustomResponses map[int]CustomBlockResponse `json:"custom_responses,omitempty"`
LogFilePath string
LogBuffer int `json:"log_buffer,omitempty"` // Add the LogBuffer field
RedactSensitiveData bool `json:"redact_sensitive_data,omitempty"`
LogBuffer int `json:"log_buffer,omitempty"` // Add the LogBuffer field
RedactSensitiveData bool `json:"redact_sensitive_data,omitempty"`
MaxRequestBodySize int64 `json:"max_request_body_size,omitempty"`
GeoIPFailOpen bool `json:"geoip_fail_open,omitempty"`
ruleHits sync.Map `json:"-"`
MetricsEndpoint string `json:"metrics_endpoint,omitempty"`