mirror of
https://github.com/fabriziosalmi/caddy-waf.git
synced 2025-12-31 01:59:16 -05:00
Compare commits
60 Commits
copilot/fi
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf45542c7e | ||
|
|
571d095028 | ||
|
|
d3f918c4c4 | ||
|
|
5c5f32741c | ||
|
|
0a96f22563 | ||
|
|
12d70c0eec | ||
|
|
83a4df7e65 | ||
|
|
05152510f5 | ||
|
|
5928ff4210 | ||
|
|
78f0066cb8 | ||
|
|
00c547e2a3 | ||
|
|
c29a7ce9aa | ||
|
|
eea39d253b | ||
|
|
5d57051169 | ||
|
|
47e05e907e | ||
|
|
1c9b6a287d | ||
|
|
b3d3d5692c | ||
|
|
a179255b3f | ||
|
|
1da1fea22b | ||
|
|
34d7a29119 | ||
|
|
66685526e5 | ||
|
|
971bc53f8a | ||
|
|
937808048b | ||
|
|
b9fe9ddbb3 | ||
|
|
db95a9b2ed | ||
|
|
e98fd16392 | ||
|
|
65f8c8a62f | ||
|
|
c8c0fed9e2 | ||
|
|
06a496e3d3 | ||
|
|
a71b182158 | ||
|
|
cf7c995137 | ||
|
|
14e4de4b66 | ||
|
|
8b702b4281 | ||
|
|
1c32e928f1 | ||
|
|
f45e8331cc | ||
|
|
fa7f421773 | ||
|
|
1a65ea7049 | ||
|
|
a77a2d2e36 | ||
|
|
63ca645404 | ||
|
|
1207bd7a6d | ||
|
|
5c8d13199b | ||
|
|
8be3863b48 | ||
|
|
1e5d6d9e3d | ||
|
|
2e6aa32858 | ||
|
|
145feb4bf8 | ||
|
|
485c86fdbc | ||
|
|
08021ee7e0 | ||
|
|
27abae69ea | ||
|
|
2fffae5d18 | ||
|
|
8d5af6be5f | ||
|
|
7938023ed1 | ||
|
|
c905277058 | ||
|
|
6429e286fd | ||
|
|
95efcabc27 | ||
|
|
c483f5baba | ||
|
|
feee09fcf7 | ||
|
|
6b5b686b55 | ||
|
|
719dd2c007 | ||
|
|
be8baedaae | ||
|
|
8685d03503 |
16
.github/workflows/build-run-validate.yml
vendored
16
.github/workflows/build-run-validate.yml
vendored
@@ -27,18 +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'
|
||||
|
||||
- name: Validate Go Installation
|
||||
run: |
|
||||
go version
|
||||
if ! go version | grep -q "go1.24.2"; then
|
||||
echo "Go installation failed or incorrect version"
|
||||
exit 1
|
||||
fi
|
||||
go-version: '1.25'
|
||||
|
||||
- name: Clone caddy-waf Repository
|
||||
run: |
|
||||
@@ -61,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: |
|
||||
@@ -139,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
|
||||
|
||||
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
66
.github/workflows/test.yml
vendored
Normal file
66
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- ci
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
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
|
||||
GOTOOLCHAIN: local
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
# Default is true, cancels jobs for other platforms in the matrix if one fails
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
go:
|
||||
- '1.25'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
actions: write # to allow uploading artifacts and cache
|
||||
steps:
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
check-latest: true
|
||||
|
||||
- name: Get dependencies
|
||||
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://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 ./... -tags=it
|
||||
69
.github/workflows/tests.yml
vendored
69
.github/workflows/tests.yml
vendored
@@ -1,69 +0,0 @@
|
||||
name: "Tests"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24.2"
|
||||
|
||||
- name: Get Dependencies
|
||||
run: go get -v ./...
|
||||
|
||||
- name: Get Caddy Dependencies
|
||||
run: |
|
||||
go get github.com/caddyserver/caddy/v2/modules/caddyhttp/templates@v2.9.1
|
||||
go get github.com/caddyserver/caddy/v2/modules/caddyhttp/proxyprotocol@v2.9.1
|
||||
go get github.com/caddyserver/caddy/v2/modules/caddyhttp/tracing@v2.9.1
|
||||
go get github.com/caddyserver/caddy/v2/modules/caddypki/acmeserver@v2.9.1
|
||||
go get github.com/smallstep/certificates/acme@v0.26.1
|
||||
|
||||
- name: Build Caddy
|
||||
run: |
|
||||
go build -v -o caddy github.com/caddyserver/caddy/v2/cmd/caddy
|
||||
|
||||
- name: Tidy modules
|
||||
run: go mod tidy
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
id: test
|
||||
run: |
|
||||
test_output=$(go test -v -count=1 ./... 2>&1)
|
||||
echo "test_output<<EOF" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$test_output" >> $GITHUB_STEP_SUMMARY
|
||||
echo "EOF" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
passed_count=$(echo "$test_output" | grep "PASS:" | wc -l)
|
||||
failed_count=$(echo "$test_output" | grep "FAIL:" | wc -l)
|
||||
|
||||
echo "passed=$passed_count" >> $GITHUB_ENV
|
||||
echo "failed=$failed_count" >> $GITHUB_ENV
|
||||
|
||||
- name: Test Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "Test Results:"
|
||||
echo "Passed Tests: ${{ steps.test.outputs.passed }}"
|
||||
echo "Failed Tests: ${{ steps.test.outputs.failed }}"
|
||||
|
||||
- name: Fail if tests failed
|
||||
if: steps.test.outputs.failed != '0'
|
||||
run: exit 1
|
||||
122
.golangci.yml
Normal file
122
.golangci.yml
Normal file
@@ -0,0 +1,122 @@
|
||||
version: "2"
|
||||
run:
|
||||
issues-exit-code: 1
|
||||
tests: false
|
||||
build-tags:
|
||||
- nobadger
|
||||
- nomysql
|
||||
- nopgx
|
||||
output:
|
||||
formats:
|
||||
text:
|
||||
path: stdout
|
||||
print-linter-name: true
|
||||
print-issued-lines: true
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- decorder
|
||||
- dogsled
|
||||
- dupl
|
||||
- dupword
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errname
|
||||
- exhaustive
|
||||
- gosec
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- misspell
|
||||
- prealloc
|
||||
- promlinter
|
||||
- sloglint
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- testableexamples
|
||||
- testifylint
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unused
|
||||
- wastedassign
|
||||
- whitespace
|
||||
- zerologlint
|
||||
settings:
|
||||
staticcheck:
|
||||
checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1006", "-QF1008"] # default, and exclude 1 more undesired check
|
||||
errcheck:
|
||||
exclude-functions:
|
||||
- fmt.*
|
||||
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
|
||||
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
|
||||
exhaustive:
|
||||
ignore-enum-types: reflect.Kind|svc.Cmd
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- gosec
|
||||
text: G115 # Excluded due to potential noisy integer overflow warnings
|
||||
- linters:
|
||||
- gosec
|
||||
text: G107 # we aren't calling unknown URL
|
||||
- linters:
|
||||
- gosec
|
||||
text: G203 # as a web server that's expected to handle any template, this is totally in the hands of the user.
|
||||
- linters:
|
||||
- gosec
|
||||
text: G204 # we're shelling out to known commands, not relying on user-defined input.
|
||||
- linters:
|
||||
- gosec
|
||||
# the choice of weakrand is deliberate, hence the named import "weakrand"
|
||||
path: modules/caddyhttp/reverseproxy/selectionpolicies.go
|
||||
text: G404
|
||||
- linters:
|
||||
- gosec
|
||||
path: modules/caddyhttp/reverseproxy/streaming.go
|
||||
text: G404
|
||||
- linters:
|
||||
- dupl
|
||||
path: modules/logging/filters.go
|
||||
- linters:
|
||||
- dupl
|
||||
path: modules/caddyhttp/matchers.go
|
||||
- linters:
|
||||
- dupl
|
||||
path: modules/caddyhttp/vars.go
|
||||
- linters:
|
||||
- errcheck
|
||||
path: _test\.go
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard # Standard section: captures all standard packages.
|
||||
- default # Default section: contains all imports that could not be matched to another section type.
|
||||
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
|
||||
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
|
||||
custom-order: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
34
CHANGELOG.md
Normal file
34
CHANGELOG.md
Normal 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
29
GNUmakefile
Normal 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
|
||||
21
README.md
21
README.md
@@ -2,12 +2,12 @@
|
||||
|
||||
A robust, highly customizable, and feature-rich **Web Application Firewall (WAF)** middleware for the Caddy web server. This middleware provides **advanced protection** against a comprehensive range of web-based threats, seamlessly integrating with Caddy and offering flexible configuration options to secure your applications effectively.
|
||||
|
||||
[](https://github.com/fabriziosalmi/caddy-waf/actions/workflows/go.yml) [](https://github.com/fabriziosalmi/caddy-waf/actions/workflows/github-code-scanning/codeql) [](https://github.com/fabriziosalmi/caddy-waf/actions/workflows/build-run-validate.yml)
|
||||
[](https://github.com/fabriziosalmi/caddy-waf/actions/workflows/test.yml) [](https://github.com/fabriziosalmi/caddy-waf/actions/workflows/github-code-scanning/codeql) [](https://github.com/fabriziosalmi/caddy-waf/actions/workflows/build-run-validate.yml)
|
||||
|
||||
## 🛡️ 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 :)_
|
||||

|
||||
|
||||
## 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,12 @@ curl -fsSL -H "Pragma: no-cache" https://raw.githubusercontent.com/fabriziosalmi
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
### 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
|
||||
@@ -176,7 +189,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
|
||||
|
||||
31
SECURITY.md
31
SECURITY.md
@@ -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
8
assets.go
Normal 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
7
assets_stub.go
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !with_ui
|
||||
|
||||
package caddywaf
|
||||
|
||||
import "embed"
|
||||
|
||||
var Assets embed.FS
|
||||
30
blacklist.go
30
blacklist.go
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -63,17 +64,21 @@ func (bl *BlacklistLoader) LoadDNSBlacklistFromFile(path string, dnsBlacklist ma
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Middleware) isIPBlacklisted(ip string) bool {
|
||||
if m.ipBlacklist == nil { // Defensive check: ensure ipBlacklist is not nil
|
||||
return false
|
||||
func (m *Middleware) isIPBlacklisted(addr string) bool {
|
||||
ip := extractIP(addr)
|
||||
|
||||
if m.ipBlacklist == nil {
|
||||
m.logger.Error("blacklist", zap.String("IP blacklist", "is nil"))
|
||||
}
|
||||
if m.ipBlacklist.Contains(ip) {
|
||||
|
||||
if m.ipBlacklist.Contains(netip.MustParseAddr(ip)) {
|
||||
m.muIPBlacklistMetrics.Lock() // Acquire lock before accessing shared counter
|
||||
m.IPBlacklistBlockCount++ // Increment the counter
|
||||
m.muIPBlacklistMetrics.Unlock() // Release lock after accessing counter
|
||||
m.logger.Debug("IP blacklist hit", zap.String("ip", ip)) // Keep existing debug log
|
||||
return true // Indicate that the IP is blacklisted
|
||||
}
|
||||
|
||||
return false // Indicate that the IP is NOT blacklisted
|
||||
}
|
||||
|
||||
@@ -111,23 +116,6 @@ func (m *Middleware) isDNSBlacklisted(host string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// extractIP extracts the IP address from a remote address string.
|
||||
func extractIP(remoteAddr string, logger *zap.Logger) string {
|
||||
if logger == nil {
|
||||
logger = zap.NewNop()
|
||||
}
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
logger.Debug("Using full remote address as IP",
|
||||
zap.String("remoteAddr", remoteAddr),
|
||||
zap.Error(err),
|
||||
)
|
||||
return remoteAddr // Assume the input is already an IP address
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
// LoadIPBlacklistFromFile loads IP addresses from a file into the provided map.
|
||||
// LoadIPBlacklistFromFile loads IP addresses from a file into the provided map.
|
||||
func (bl *BlacklistLoader) LoadIPBlacklistFromFile(path string, ipBlacklist map[string]struct{}) error {
|
||||
bl.logger.Debug("Loading IP blacklist", zap.String("path", path))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package caddywaf
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -15,7 +14,7 @@ func TestLoadDNSBlacklistFromFile(t *testing.T) {
|
||||
malicious.com
|
||||
spaces.com
|
||||
`
|
||||
tmpfile, err := ioutil.TempFile("", "dnsblacklist")
|
||||
tmpfile, err := os.CreateTemp("", "dnsblacklist")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -60,7 +59,7 @@ func TestLoadIPBlacklistFromFile(t *testing.T) {
|
||||
172.16.1.1
|
||||
invalid-ip
|
||||
`
|
||||
tmpfile, err := ioutil.TempFile("", "ipblacklist")
|
||||
tmpfile, err := os.CreateTemp("", "ipblacklist")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -97,30 +96,6 @@ invalid-ip
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"IP with port", "192.168.1.1:8080", "192.168.1.1"},
|
||||
{"IP only", "192.168.1.1", "192.168.1.1"},
|
||||
{"IPv6 with port", "[2001:db8::1]:8080", "2001:db8::1"},
|
||||
{"IPv6 only", "2001:db8::1", "2001:db8::1"},
|
||||
}
|
||||
|
||||
logger := zap.NewNop()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := extractIP(tt.input, logger)
|
||||
if result != tt.expected {
|
||||
t.Errorf("extractIP(%s) = %s; want %s", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddIPEntry(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
bl := NewBlacklistLoader(logger)
|
||||
|
||||
117
caddywaf.go
117
caddywaf.go
@@ -20,19 +20,21 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
"github.com/phemmer/go-iptrie"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
// ==================== Constants and Globals ====================
|
||||
@@ -46,7 +48,7 @@ var (
|
||||
)
|
||||
|
||||
// Add or update the version constant as needed
|
||||
const wafVersion = "v0.0.6" // 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 ====================
|
||||
|
||||
@@ -80,7 +82,9 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
|
||||
func (m *Middleware) Provision(ctx caddy.Context) error {
|
||||
m.logger = ctx.Logger(m)
|
||||
m.ruleCache = NewRuleCache() // Initialize RuleCache
|
||||
m.ruleCache = NewRuleCache() // Initialize RuleCache
|
||||
m.Rules = make(map[int][]Rule) // Initialize Rules map to prevent nil pointer panic
|
||||
m.ipBlacklist = iptrie.NewTrie()
|
||||
|
||||
// Set default log severity if not provided
|
||||
if m.LogSeverity == "" {
|
||||
@@ -118,7 +122,7 @@ func (m *Middleware) Provision(ctx caddy.Context) error {
|
||||
fileCfg.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
fileEncoder := zapcore.NewJSONEncoder(fileCfg.EncoderConfig)
|
||||
|
||||
fileSync, err := os.OpenFile(m.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
fileSync, err := os.OpenFile(m.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
m.logger.Warn("Failed to open log file, logging only to console", zap.String("path", m.LogFilePath), zap.Error(err))
|
||||
m.logger = zap.New(zapcore.NewCore(consoleEncoder, consoleSync, logLevel))
|
||||
@@ -191,23 +195,23 @@ func (m *Middleware) Provision(ctx caddy.Context) error {
|
||||
// Initialize GeoIP stats
|
||||
m.geoIPStats = make(map[string]int64)
|
||||
|
||||
// Configure GeoIP-based country blocking/whitelisting
|
||||
if m.CountryBlock.Enabled || m.CountryWhitelist.Enabled {
|
||||
geoIPPath := m.CountryBlock.GeoIPDBPath
|
||||
// Configure GeoIP-based country blacklisting/whitelisting
|
||||
if m.CountryBlacklist.Enabled || m.CountryWhitelist.Enabled {
|
||||
geoIPPath := m.CountryBlacklist.GeoIPDBPath
|
||||
if m.CountryWhitelist.Enabled && m.CountryWhitelist.GeoIPDBPath != "" {
|
||||
geoIPPath = m.CountryWhitelist.GeoIPDBPath
|
||||
}
|
||||
|
||||
if !fileExists(geoIPPath) {
|
||||
m.logger.Warn("GeoIP database not found. Country blocking/whitelisting will be disabled", zap.String("path", geoIPPath))
|
||||
m.logger.Warn("GeoIP database not found. Country blacklisting/whitelisting will be disabled", zap.String("path", geoIPPath))
|
||||
} else {
|
||||
reader, err := maxminddb.Open(geoIPPath)
|
||||
if err != nil {
|
||||
m.logger.Error("Failed to load GeoIP database", zap.String("path", geoIPPath), zap.Error(err))
|
||||
} else {
|
||||
m.logger.Info("GeoIP database loaded successfully", zap.String("path", geoIPPath))
|
||||
if m.CountryBlock.Enabled {
|
||||
m.CountryBlock.geoIP = reader
|
||||
if m.CountryBlacklist.Enabled {
|
||||
m.CountryBlacklist.geoIP = reader
|
||||
}
|
||||
if m.CountryWhitelist.Enabled {
|
||||
m.CountryWhitelist.geoIP = reader
|
||||
@@ -216,27 +220,46 @@ 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
|
||||
if m.IPBlacklistFile != "" {
|
||||
m.ipBlacklist = NewCIDRTrie()
|
||||
err = m.loadIPBlacklist(m.IPBlacklistFile, m.ipBlacklist)
|
||||
m.ipBlacklist = iptrie.NewTrie()
|
||||
err = m.loadIPBlacklist(m.IPBlacklistFile, *m.ipBlacklist)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load IP blacklist: %w", err)
|
||||
}
|
||||
@@ -283,23 +306,19 @@ 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.CountryBlock.geoIP != nil {
|
||||
m.logger.Debug("Closing country block GeoIP database...")
|
||||
if err := m.CountryBlock.geoIP.Close(); err != nil {
|
||||
m.logger.Error("Error encountered while closing country block GeoIP database", zap.Error(err))
|
||||
if !errorOccurred {
|
||||
firstError = fmt.Errorf("error closing country block GeoIP: %w", err)
|
||||
errorOccurred = true
|
||||
}
|
||||
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))
|
||||
firstError = fmt.Errorf("error closing country blacklist GeoIP: %w", err)
|
||||
} else {
|
||||
m.logger.Debug("Country block GeoIP database closed successfully.")
|
||||
m.logger.Debug("Country blacklist GeoIP database closed successfully.")
|
||||
}
|
||||
m.CountryBlock.geoIP = nil
|
||||
m.CountryBlacklist.geoIP = nil
|
||||
} else {
|
||||
m.logger.Debug("Country block GeoIP database was not open, skipping close.")
|
||||
m.logger.Debug("Country blacklist GeoIP database was not open, skipping close.")
|
||||
}
|
||||
|
||||
if m.CountryWhitelist.geoIP != nil {
|
||||
@@ -317,6 +336,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 {
|
||||
@@ -326,15 +358,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
|
||||
})
|
||||
@@ -424,8 +457,8 @@ func (m *Middleware) ReloadConfig() error {
|
||||
|
||||
m.logger.Info("Reloading WAF configuration")
|
||||
if m.IPBlacklistFile != "" {
|
||||
newIPBlacklist := NewCIDRTrie()
|
||||
if err := m.loadIPBlacklist(m.IPBlacklistFile, newIPBlacklist); err != nil {
|
||||
newIPBlacklist := iptrie.NewTrie()
|
||||
if err := m.loadIPBlacklist(m.IPBlacklistFile, *newIPBlacklist); err != nil {
|
||||
m.logger.Error("Failed to reload IP blacklist", zap.String("file", m.IPBlacklistFile), zap.Error(err))
|
||||
return fmt.Errorf("failed to reload IP blacklist: %v", err)
|
||||
}
|
||||
@@ -450,7 +483,7 @@ func (m *Middleware) ReloadConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Middleware) loadIPBlacklist(path string, blacklistMap *CIDRTrie) error {
|
||||
func (m *Middleware) loadIPBlacklist(path string, blacklistMap iptrie.Trie) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
m.logger.Warn("Skipping IP blacklist load, file does not exist", zap.String("file", path))
|
||||
return nil
|
||||
@@ -464,7 +497,12 @@ func (m *Middleware) loadIPBlacklist(path string, blacklistMap *CIDRTrie) error
|
||||
|
||||
// Convert the map to CIDRTrie
|
||||
for ip := range blacklist {
|
||||
blacklistMap.Insert(ip)
|
||||
prefix, err := netip.ParsePrefix(appendCIDR(ip))
|
||||
if err != nil {
|
||||
m.logger.Warn("Skipping invalid IP in blacklist", zap.String("ip", ip), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
blacklistMap.Insert(prefix, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -492,12 +530,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
|
||||
|
||||
@@ -2,15 +2,14 @@ package caddywaf
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func TestMiddleware_Provision(t *testing.T) {
|
||||
@@ -33,7 +32,7 @@ func TestMiddleware_Provision(t *testing.T) {
|
||||
IPBlacklistFile: "testdata/ip_blacklist.txt",
|
||||
DNSBlacklistFile: "testdata/dns_blacklist.txt",
|
||||
AnomalyThreshold: 10,
|
||||
CountryBlock: CountryAccessFilter{
|
||||
CountryBlacklist: CountryAccessFilter{
|
||||
Enabled: true,
|
||||
CountryList: []string{"US"},
|
||||
GeoIPDBPath: "testdata/GeoIP2-Country-Test.mmdb",
|
||||
|
||||
21
common_test.go
Normal file
21
common_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package caddywaf
|
||||
|
||||
import "net/http"
|
||||
|
||||
const (
|
||||
geoIPdata = "GeoLite2-Country.mmdb"
|
||||
localIP = "127.0.0.1:32555"
|
||||
aliCNIP = "47.88.198.38"
|
||||
googleUSIP = "74.125.131.105"
|
||||
googleBRIP = "128.201.228.12"
|
||||
googleRUIP = "74.125.131.94"
|
||||
testURL = "http://example.com"
|
||||
torListURL = "https://cdn.nws.neurodyne.pro/nws-cdn-ut8hw561/waf/torbulkexitlist" // custom TOR list URL for testing
|
||||
)
|
||||
|
||||
var customResponse = map[int]CustomBlockResponse{
|
||||
403: {
|
||||
StatusCode: http.StatusForbidden,
|
||||
Body: "Access Denied",
|
||||
},
|
||||
}
|
||||
34
config.go
34
config.go
@@ -7,8 +7,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
// ConfigLoader structure to encapsulate loading and parsing logic
|
||||
@@ -139,11 +140,12 @@ func (cl *ConfigLoader) UnmarshalCaddyfile(d *caddyfile.Dispenser, m *Middleware
|
||||
m.LogSeverity = "info"
|
||||
m.LogJSON = false
|
||||
m.AnomalyThreshold = 5
|
||||
m.CountryBlock.Enabled = false
|
||||
m.CountryBlacklist.Enabled = false
|
||||
m.CountryWhitelist.Enabled = false
|
||||
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,
|
||||
@@ -151,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,
|
||||
@@ -268,7 +271,7 @@ func (cl *ConfigLoader) parseCustomResponse(d *caddyfile.Dispenser, m *Middlewar
|
||||
// parseCountryBlockDirective returns a closure to handle block_countries and whitelist_countries directives.
|
||||
func (cl *ConfigLoader) parseCountryBlockDirective(isBlock bool) func(d *caddyfile.Dispenser, m *Middleware) error {
|
||||
return func(d *caddyfile.Dispenser, m *Middleware) error {
|
||||
target := &m.CountryBlock
|
||||
target := &m.CountryBlacklist
|
||||
directiveName := "block_countries"
|
||||
if !isBlock {
|
||||
target = &m.CountryWhitelist
|
||||
@@ -299,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()
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
package caddywaf
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
func TestNewConfigLoader(t *testing.T) {
|
||||
@@ -202,14 +201,14 @@ func TestParseCountryBlock(t *testing.T) {
|
||||
t.Fatalf("parseCountryBlockDirective failed: %v", err)
|
||||
}
|
||||
|
||||
if !m.CountryBlock.Enabled {
|
||||
t.Errorf("Expected country block to be enabled, got %v", m.CountryBlock.Enabled)
|
||||
if !m.CountryBlacklist.Enabled {
|
||||
t.Errorf("Expected country blacklist to be enabled, got %v", m.CountryBlacklist.Enabled)
|
||||
}
|
||||
if m.CountryBlock.GeoIPDBPath != "/etc/geoip/GeoIP.dat" {
|
||||
t.Errorf("Expected GeoIP DB path to be '/etc/geoip/GeoIP.dat', got '%s'", m.CountryBlock.GeoIPDBPath)
|
||||
if m.CountryBlacklist.GeoIPDBPath != "/etc/geoip/GeoIP.dat" {
|
||||
t.Errorf("Expected GeoIP DB path to be '/etc/geoip/GeoIP.dat', got '%s'", m.CountryBlacklist.GeoIPDBPath)
|
||||
}
|
||||
if len(m.CountryBlock.CountryList) != 2 || m.CountryBlock.CountryList[0] != "US" || m.CountryBlock.CountryList[1] != "CA" {
|
||||
t.Errorf("Expected country list to be ['US', 'CA'], got %v", m.CountryBlock.CountryList)
|
||||
if len(m.CountryBlacklist.CountryList) != 2 || m.CountryBlacklist.CountryList[0] != "US" || m.CountryBlacklist.CountryList[1] != "CA" {
|
||||
t.Errorf("Expected country list to be ['US', 'CA'], got %v", m.CountryBlacklist.CountryList)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
debug_waf.go
32
debug_waf.go
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
doc.go
16
doc.go
@@ -15,14 +15,16 @@
|
||||
// - Dynamic configuration reloading
|
||||
//
|
||||
// Installation:
|
||||
// xcaddy build --with github.com/fabriziosalmi/caddy-waf
|
||||
//
|
||||
// xcaddy build --with github.com/fabriziosalmi/caddy-waf
|
||||
//
|
||||
// Basic usage in Caddyfile:
|
||||
// waf {
|
||||
// rule_file rules.json
|
||||
// ip_blacklist_file blacklist.txt
|
||||
// metrics_endpoint /waf_metrics
|
||||
// }
|
||||
//
|
||||
// waf {
|
||||
// rule_file rules.json
|
||||
// ip_blacklist_file blacklist.txt
|
||||
// metrics_endpoint /waf_metrics
|
||||
// }
|
||||
//
|
||||
// For complete documentation, see: https://github.com/fabriziosalmi/caddy-waf
|
||||
package caddywaf
|
||||
package caddywaf
|
||||
|
||||
@@ -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` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4,6 +4,26 @@
|
||||
* Download the `GeoLite2-Country.mmdb` file (see [Installation](#-installation)).
|
||||
* Use `block_countries` or `whitelist_countries` with ISO country codes:
|
||||
|
||||
## Priorities
|
||||
`Whitelisting` has a **higher** priority than `Blacklisting`.
|
||||
|
||||
### Config Example
|
||||
|
||||
Whitelist: BR <br>
|
||||
Blacklist: US, UK <br>
|
||||
|
||||
Q: Which is THE priority ? <br>
|
||||
A: BR IPs are allowed, all others are **blocked**
|
||||
|
||||
## Global blocking priorities
|
||||
|
||||
- IP blacklist
|
||||
- DNS blacklist
|
||||
- Rate limit
|
||||
- Whitelist
|
||||
- Blacklist
|
||||
|
||||
## Config example
|
||||
```caddyfile
|
||||
# Block requests from Russia, China, and North Korea
|
||||
block_countries /path/to/GeoLite2-Country.mmdb RU CN KP
|
||||
|
||||
83
geoip.go
83
geoip.go
@@ -3,6 +3,7 @@ package caddywaf
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -63,11 +64,8 @@ func (gh *GeoIPHandler) IsCountryInList(remoteAddr string, countryList []string,
|
||||
return false, fmt.Errorf("geoip database not loaded")
|
||||
}
|
||||
|
||||
ip, err := gh.extractIPFromRemoteAddr(remoteAddr)
|
||||
if err != nil {
|
||||
gh.logger.Debug("Failed to extract IP from remote address", zap.String("remote_addr", remoteAddr), zap.Error(err))
|
||||
return false, err
|
||||
}
|
||||
// Extract IP address without port
|
||||
ip := extractIP(remoteAddr)
|
||||
|
||||
parsedIP := net.ParseIP(ip)
|
||||
if parsedIP == nil {
|
||||
@@ -85,10 +83,10 @@ func (gh *GeoIPHandler) GetCountryCode(remoteAddr string, geoIP *maxminddb.Reade
|
||||
return "N/A"
|
||||
}
|
||||
|
||||
ip, err := gh.extractIPFromRemoteAddr(remoteAddr)
|
||||
ip, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
gh.logger.Debug("Failed to extract IP from remote address for GetCountryCode", zap.String("remote_addr", remoteAddr), zap.Error(err))
|
||||
return "N/A"
|
||||
// fallback to input IP
|
||||
ip = remoteAddr
|
||||
}
|
||||
|
||||
parsedIP := net.ParseIP(ip)
|
||||
@@ -101,7 +99,6 @@ func (gh *GeoIPHandler) GetCountryCode(remoteAddr string, geoIP *maxminddb.Reade
|
||||
}
|
||||
|
||||
func (gh *GeoIPHandler) isCountryInListWithCache(ip string, parsedIP net.IP, countryList []string, geoIP *maxminddb.Reader) (bool, error) {
|
||||
|
||||
// Check cache first
|
||||
if gh.geoIPCache != nil {
|
||||
gh.geoIPCacheMutex.RLock()
|
||||
@@ -127,7 +124,6 @@ func (gh *GeoIPHandler) isCountryInListWithCache(ip string, parsedIP net.IP, cou
|
||||
}
|
||||
|
||||
func (gh *GeoIPHandler) getCountryCodeWithCache(ip string, parsedIP net.IP, geoIP *maxminddb.Reader) string {
|
||||
|
||||
// Check cache first for GetCountryCode as well for consistency and potential perf gain
|
||||
if gh.geoIPCache != nil {
|
||||
gh.geoIPCacheMutex.RLock()
|
||||
@@ -153,20 +149,6 @@ func (gh *GeoIPHandler) getCountryCodeWithCache(ip string, parsedIP net.IP, geoI
|
||||
return record.Country.ISOCode
|
||||
}
|
||||
|
||||
// extractIPFromRemoteAddr extracts the ip from remote address
|
||||
func (gh *GeoIPHandler) extractIPFromRemoteAddr(remoteAddr string) (string, error) {
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
// If it's not in host:port format, assume it's just the IP
|
||||
ip := net.ParseIP(remoteAddr)
|
||||
if ip == nil {
|
||||
return "", fmt.Errorf("invalid IP format: %s", remoteAddr)
|
||||
}
|
||||
return remoteAddr, nil
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// Helper function to check if the country in the record is in the country list
|
||||
func (gh *GeoIPHandler) isCountryInRecord(record GeoIPRecord, countryList []string) bool {
|
||||
for _, country := range countryList {
|
||||
@@ -216,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)
|
||||
}
|
||||
|
||||
@@ -66,49 +66,6 @@ func TestLoadGeoIPDatabase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractIPFromRemoteAddr(t *testing.T) {
|
||||
handler := NewGeoIPHandler(nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
remoteAddr string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid IP and port",
|
||||
remoteAddr: "192.168.1.1:8080",
|
||||
want: "192.168.1.1",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid IP only",
|
||||
remoteAddr: "192.168.1.1",
|
||||
want: "192.168.1.1",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid IP",
|
||||
remoteAddr: "invalid-ip",
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := handler.extractIPFromRemoteAddr(tt.remoteAddr)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("extractIPFromRemoteAddr() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("extractIPFromRemoteAddr() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCountryInList(t *testing.T) {
|
||||
handler := NewGeoIPHandler(nil)
|
||||
|
||||
|
||||
@@ -13,107 +13,49 @@ blocklist_sources = {
|
||||
"Greensnow": "https://blocklist.greensnow.co/greensnow.txt",
|
||||
}
|
||||
|
||||
# --- Tor Exit Node Source (Testing) ---
|
||||
tor_exit_nodes_url = "https://check.torproject.org/exit-addresses" # Testing
|
||||
# Tor Exit Node Source
|
||||
tor_exit_nodes_url = "https://check.torproject.org/exit-addresses"
|
||||
|
||||
|
||||
def extract_ips(source_name, url):
|
||||
"""Fetches data from the given URL and extracts IP addresses."""
|
||||
"""Fetches data from the given URL and extracts IP addresses in CIDR format."""
|
||||
ips = set()
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
|
||||
response.raise_for_status()
|
||||
content = response.text
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error fetching {source_name} from {url}: {e}")
|
||||
return ips
|
||||
|
||||
if source_name == "Talos Intelligence":
|
||||
print(f"Skipping {source_name} due to webpage format, needs manual parsing.")
|
||||
return ips
|
||||
elif source_name == "TOR Exit Nodes":
|
||||
for line in content.splitlines():
|
||||
if line.startswith("ExitAddress"):
|
||||
parts = line.split(" ")
|
||||
if len(parts) > 1:
|
||||
try:
|
||||
ipaddress.ip_address(parts[1].strip())
|
||||
ips.add(parts[1].strip())
|
||||
except ValueError:
|
||||
continue
|
||||
return ips
|
||||
elif source_name == "Spamhaus DROP" or source_name == "Spamhaus EDROP":
|
||||
for line in content.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith(";"):
|
||||
for line in content.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or line.startswith(";"):
|
||||
continue
|
||||
|
||||
# MODIFIED: Preserve CIDR notation if it already exists
|
||||
if "/" in line:
|
||||
try:
|
||||
# Validate it's a real network and add it
|
||||
net = ipaddress.ip_network(line, strict=False)
|
||||
ips.add(net.with_prefixlen)
|
||||
except ValueError:
|
||||
continue
|
||||
if "/" in line:
|
||||
try:
|
||||
for ip in ipaddress.ip_network(line, strict=False):
|
||||
ips.add(str(ip))
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
ipaddress.ip_address(line)
|
||||
ips.add(line)
|
||||
except ValueError:
|
||||
continue
|
||||
return ips
|
||||
elif source_name == "MaxMind GeoIP2 Anonymous IP Database":
|
||||
# Requires a license key, skipping for now.
|
||||
print(f"Skipping {source_name} because it requires a license key.")
|
||||
return ips
|
||||
else:
|
||||
# Default parsing for normal text file blocklists
|
||||
for line in content.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
else:
|
||||
# MODIFIED: Convert single IPs to CIDR notation
|
||||
try:
|
||||
ip_obj = ipaddress.ip_address(line)
|
||||
if ip_obj.version == 4:
|
||||
ips.add(f"{line}/32")
|
||||
else:
|
||||
ips.add(f"{line}/128")
|
||||
except ValueError:
|
||||
continue
|
||||
# Normalize ranges to single IPs
|
||||
if "/" in line:
|
||||
try:
|
||||
for ip in ipaddress.ip_network(line, strict=False):
|
||||
ips.add(str(ip))
|
||||
except ValueError:
|
||||
continue
|
||||
elif "-" in line:
|
||||
try:
|
||||
start, end = line.split('-')
|
||||
start_ip = ipaddress.ip_address(start.strip())
|
||||
end_ip = ipaddress.ip_address(end.strip())
|
||||
if start_ip.version == end_ip.version:
|
||||
for ip_int in range(int(start_ip), int(end_ip) + 1):
|
||||
ips.add(str(ipaddress.ip_address(ip_int)))
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
ipaddress.ip_address(line)
|
||||
ips.add(line)
|
||||
except ValueError:
|
||||
continue
|
||||
return ips
|
||||
return ips
|
||||
|
||||
|
||||
def is_valid_ip(ip_str):
|
||||
"""Helper function to check if an IP address is valid."""
|
||||
try:
|
||||
ipaddress.ip_address(ip_str)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def ip_to_int(ip_str):
|
||||
"""Converts an IP address string to its integer representation."""
|
||||
try:
|
||||
return int(ipaddress.ip_address(ip_str))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def extract_tor_exit_nodes(url):
|
||||
"""Fetches data from the given URL and extracts Tor exit node IPs."""
|
||||
"""Fetches Tor exit node IPs and returns them in CIDR format."""
|
||||
ips = set()
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
@@ -127,9 +69,14 @@ def extract_tor_exit_nodes(url):
|
||||
if line.startswith("ExitAddress"):
|
||||
parts = line.split(" ")
|
||||
if len(parts) > 1:
|
||||
ip_str = parts[1].strip()
|
||||
# MODIFIED: Convert single IPs to CIDR notation
|
||||
try:
|
||||
ipaddress.ip_address(parts[1].strip())
|
||||
ips.add(parts[1].strip())
|
||||
ip_obj = ipaddress.ip_address(ip_str)
|
||||
if ip_obj.version == 4:
|
||||
ips.add(f"{ip_str}/32")
|
||||
else:
|
||||
ips.add(f"{ip_str}/128")
|
||||
except ValueError:
|
||||
continue
|
||||
return ips
|
||||
@@ -140,31 +87,21 @@ def main():
|
||||
for source_name, url in tqdm(blocklist_sources.items(), desc="Processing Blocklists"):
|
||||
print(f"Processing {source_name} from {url}")
|
||||
ips = extract_ips(source_name, url)
|
||||
print(f" Found {len(ips)} IPs in {source_name}")
|
||||
print(f" Found {len(ips)} IPs/CIDRs in {source_name}")
|
||||
combined_ips.update(ips)
|
||||
|
||||
# --- Tor Exit Node Processing (Testing) ---
|
||||
# Tor Exit Node Processing
|
||||
tor_exit_ips = extract_tor_exit_nodes(tor_exit_nodes_url)
|
||||
print(f"Total Tor exit node IPs: {len(tor_exit_ips)}")
|
||||
valid_tor_ips = [ip for ip in tor_exit_ips if is_valid_ip(ip)]
|
||||
print(f"Total Valid Tor IPs after filtering: {len(valid_tor_ips)}")
|
||||
print(f"Total Tor exit node IPs/CIDRs: {len(tor_exit_ips)}")
|
||||
combined_ips.update(tor_exit_ips)
|
||||
|
||||
# Add Tor exit IPs to the combined IPs
|
||||
combined_ips.update(valid_tor_ips)
|
||||
|
||||
print(f"Total IPs before filtering and deduplication: {len(combined_ips)}")
|
||||
|
||||
# Filter out invalid IPs before sorting.
|
||||
valid_ips = [ip for ip in combined_ips if is_valid_ip(ip)]
|
||||
print(f"Total Valid IPs after filtering: {len(valid_ips)}")
|
||||
|
||||
# Remove duplicates by converting to a set before sorting
|
||||
unique_ips = set(valid_ips)
|
||||
print(f"Total Unique IPs/CIDRs after deduplication: {len(combined_ips)}")
|
||||
|
||||
# MODIFIED: The final write loop is simpler. The sorting key is removed as sorting CIDRs as integers is incorrect.
|
||||
# A simple lexicographical sort is sufficient here.
|
||||
with open("ip_blacklist.txt", "w") as f:
|
||||
# Sort using the integer representation and write each IP to the file
|
||||
for ip in sorted(unique_ips, key=ip_to_int):
|
||||
f.write(f"{ip}\n")
|
||||
for ip_cidr in sorted(list(combined_ips)):
|
||||
f.write(f"{ip_cidr}\n")
|
||||
|
||||
print("IP blacklist saved to ip_blacklist.txt")
|
||||
|
||||
|
||||
188
go.mod
188
go.mod
@@ -1,124 +1,164 @@
|
||||
module github.com/fabriziosalmi/caddy-waf
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.2
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/caddyserver/caddy/v2 v2.9.1
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/caddyserver/caddy/v2 v2.10.2
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/phemmer/go-iptrie v0.0.0-20240326174613-ba542f5282c9
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/zap v1.27.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go/auth v0.17.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/KimMachineGun/automemlimit v0.7.4 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/caddyserver/certmagic v0.21.6 // indirect
|
||||
github.com/caddyserver/certmagic v0.25.0 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // 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/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // 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
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect
|
||||
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.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-kit/kit v0.13.0 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/glog v1.2.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/cel-go v0.21.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect
|
||||
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.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.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
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.14.3 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgtype v1.14.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.18.3 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/libdns/libdns v0.2.2 // indirect
|
||||
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.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
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mholt/acmez/v3 v3.0.0 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.4 // indirect
|
||||
github.com/miekg/dns v1.1.68 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
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.18.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.48.2 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/quic-go/quic-go v0.54.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/slackhq/nebula v1.6.1 // indirect
|
||||
github.com/smallstep/certificates v0.26.1 // indirect
|
||||
github.com/smallstep/nosql v0.6.1 // indirect
|
||||
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
|
||||
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/slackhq/nebula v1.9.7 // 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.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
|
||||
github.com/smallstep/truststore v0.13.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/cobra v1.10.1 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.1 // indirect
|
||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect
|
||||
github.com/urfave/cli v1.22.14 // indirect
|
||||
github.com/urfave/cli v1.22.17 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/yuin/goldmark v1.7.13 // indirect
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
go.step.sm/cli-utils v0.9.0 // indirect
|
||||
go.step.sm/crypto v0.45.0 // indirect
|
||||
go.step.sm/linkedca v0.20.1 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.37.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.37.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.37.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
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.74.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.36.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
||||
google.golang.org/grpc v1.67.1 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // 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.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.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
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
howett.net/plist v1.0.1 // indirect
|
||||
)
|
||||
|
||||
655
go.sum
655
go.sum
@@ -1,22 +1,25 @@
|
||||
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.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
|
||||
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
||||
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
||||
cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
|
||||
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
|
||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
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=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
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.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=
|
||||
@@ -27,66 +30,80 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
|
||||
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=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/KimMachineGun/automemlimit v0.7.4 h1:UY7QYOIfrr3wjjOAqahFmC3IaQCLWvur9nmfIn6LnWk=
|
||||
github.com/KimMachineGun/automemlimit v0.7.4/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||
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.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
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.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
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 v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
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.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=
|
||||
github.com/caddyserver/caddy/v2 v2.9.1/go.mod h1:ImUELya2el1FDVp3ahnSO2iH1or1aHxlQEQxd/spP68=
|
||||
github.com/caddyserver/certmagic v0.21.6 h1:1th6GfprVfsAtFNOu4StNMF5IxK5XiaI0yZhAHlZFPE=
|
||||
github.com/caddyserver/certmagic v0.21.6/go.mod h1:n1sCo7zV1Ez2j+89wrzDxo4N/T1Ws/Vx8u5NvuBFabw=
|
||||
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/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=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
@@ -99,19 +116,18 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
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/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
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.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/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -121,11 +137,15 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
|
||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
|
||||
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
|
||||
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
|
||||
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
@@ -137,41 +157,28 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD
|
||||
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.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
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.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.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.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
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-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
|
||||
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
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/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
|
||||
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
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=
|
||||
@@ -180,151 +187,102 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
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 v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/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.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI=
|
||||
github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc=
|
||||
github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
|
||||
github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
|
||||
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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
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.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
||||
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
|
||||
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
|
||||
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/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU=
|
||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
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.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
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.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
||||
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=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
|
||||
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
|
||||
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
|
||||
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
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.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
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=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
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/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
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=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
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.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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.0.0 h1:r1NcjuWR0VaKP2BTjDK9LRFBw/WvURx3jlaEUl9Ht8E=
|
||||
github.com/mholt/acmez/v3 v3.0.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
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.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
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=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@@ -335,18 +293,22 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
||||
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/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
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=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
|
||||
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
|
||||
github.com/phemmer/go-iptrie v0.0.0-20240326174613-ba542f5282c9 h1:C8IqpV7kfAyZDRCnAVNi//l1mWlpyPmq1N6DjVvYEnY=
|
||||
github.com/phemmer/go-iptrie v0.0.0-20240326174613-ba542f5282c9/go.mod h1:dDLiSjNqdp8VjphLdGTx19OeAUsHOzhtc1FFJqpzWMU=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -355,38 +317,31 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
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.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
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.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
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.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
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.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
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.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
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=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
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 v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
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=
|
||||
@@ -413,25 +368,27 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
|
||||
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.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
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=
|
||||
github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM=
|
||||
github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI=
|
||||
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.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o=
|
||||
github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis=
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA=
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
|
||||
github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y=
|
||||
github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y=
|
||||
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg=
|
||||
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y=
|
||||
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw=
|
||||
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU=
|
||||
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.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=
|
||||
github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0=
|
||||
github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 h1:k23+s51sgYix4Zgbvpmy+1ZgXLjr4ZTkBTqXmpnImwA=
|
||||
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=
|
||||
@@ -441,128 +398,136 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
|
||||
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
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.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
|
||||
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
|
||||
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=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
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.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
|
||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||
go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
|
||||
go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
|
||||
go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc=
|
||||
go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY=
|
||||
go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU=
|
||||
go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
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=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0 h1:1+EHlhAe/tukctfePZRrDruB9vn7MdwyC+rf36nUSPM=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0/go.mod h1:skzESZBY3IYcqJgImc+fwXQWflvVe+jZxoA/uw60NaI=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.37.0 h1:cp8AFiM/qjBm10C/ATIRnEDXpD5MBknrA0ANw4T2/ss=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.37.0/go.mod h1:Cy8Hk2E2iSGEbsLnPUdeigrexaAOAGIAmBFK919EQs0=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.37.0 h1:0aGKdIuVhy5l4GClAjl72ntkZJhijf2wg1S7b5oLoYA=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.37.0/go.mod h1:nhyrxEJEOQdwR15zXrCKI6+cJK60PXAkJ/jRyfhr2mg=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.37.0 h1:pW+qDVo0jB0rLsNeaP85xLuz20cvsECUcN7TE+D8YTM=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.37.0/go.mod h1:x7bd+t034hxLTve1hF9Yn9qQJlO/pP8H5pWIt7+gsFM=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.37.0 h1:tVjnBF6EiTDMXoq2Xuc2vK0I7MTbEs05II/0j9mMK+E=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.37.0/go.mod h1:MQjyNXtxAC8PGN9gzPtO4GY5zuP+RI3XX53uWbCTvEQ=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
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.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=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
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-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
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.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 h1:4cEcP5+OjGppY79LCQ5Go2B1Boix2x0v6pvA01P3FoA=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
||||
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.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-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
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=
|
||||
@@ -570,23 +535,23 @@ golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
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-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20190813141303-74dc4d7220e7/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=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
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.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
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/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=
|
||||
@@ -595,89 +560,83 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
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=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
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.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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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/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-20190222072716-a9d3bda3a223/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
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/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
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=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
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.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
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.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/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=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
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/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
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=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
|
||||
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
|
||||
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/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=
|
||||
@@ -687,27 +646,26 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
|
||||
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/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
|
||||
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
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-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.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.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
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=
|
||||
@@ -722,8 +680,7 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd
|
||||
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=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
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=
|
||||
|
||||
269
handler.go
269
handler.go
@@ -5,20 +5,44 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
type ContextKeyLogId string
|
||||
type ContextKeyRule string
|
||||
type (
|
||||
ContextKeyLogId string
|
||||
ContextKeyRule string
|
||||
)
|
||||
|
||||
// ServeHTTP implements caddyhttp.Handler.
|
||||
// handler.go
|
||||
func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||
logID := uuid.New().String()
|
||||
|
||||
// Add panic recovery to catch and log panics
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
m.logger.Error("PANIC in ServeHTTP",
|
||||
zap.String("log_id", logID),
|
||||
zap.Any("panic", rec),
|
||||
zap.Stack("stack"),
|
||||
)
|
||||
// Return 500 error to client
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
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
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
m.logRequestStart(r, logID)
|
||||
|
||||
// Propagate log ID within the request context for logging
|
||||
@@ -149,12 +173,18 @@ func (m *Middleware) handleResponseBodyPhase(recorder *responseRecorder, r *http
|
||||
}
|
||||
m.logger.Debug("Response body captured for Phase 4 analysis", zap.String("log_id", logID))
|
||||
|
||||
for _, rule := range m.Rules[4] {
|
||||
// Check if rules exist for Phase 4 before iterating
|
||||
rules, ok := m.Rules[4]
|
||||
if !ok || len(rules) == 0 {
|
||||
m.logger.Debug("No rules found for Phase 4")
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,47 +258,9 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
zap.String("user_agent", r.UserAgent()),
|
||||
)
|
||||
|
||||
if phase == 1 && m.CountryBlock.Enabled {
|
||||
m.logger.Debug("Starting country blocking phase")
|
||||
blocked, err := m.isCountryInList(r.RemoteAddr, m.CountryBlock.CountryList, m.CountryBlock.geoIP)
|
||||
if err != nil {
|
||||
m.logRequest(zapcore.ErrorLevel, "Failed to check country block",
|
||||
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 blocking 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,
|
||||
zap.String("message", "Request blocked by country"))
|
||||
m.incrementGeoIPRequestsMetric(true) // Increment with true for blocked
|
||||
return
|
||||
}
|
||||
m.logger.Debug("Country blocking phase completed - not blocked")
|
||||
m.incrementGeoIPRequestsMetric(false) // Increment with false for no block
|
||||
}
|
||||
|
||||
if phase == 1 && m.rateLimiter != nil {
|
||||
m.logger.Debug("Starting rate limiting phase")
|
||||
ip := extractIP(r.RemoteAddr, m.logger) // Pass the logger here
|
||||
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,
|
||||
zap.String("message", "Request blocked by rate limit"),
|
||||
)
|
||||
return
|
||||
}
|
||||
m.logger.Debug("Rate limiting phase completed - not blocked")
|
||||
}
|
||||
|
||||
if phase == 1 {
|
||||
m.logger.Debug("Checking for IP blacklisting", zap.String("remote_addr", r.RemoteAddr)) //Added log for checking before to isIPBlacklisted call
|
||||
// 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")
|
||||
if xForwardedFor != "" {
|
||||
ips := strings.Split(xForwardedFor, ",")
|
||||
@@ -277,54 +269,179 @@ 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 {
|
||||
m.writeCustomResponse(w, state.StatusCode)
|
||||
}
|
||||
return
|
||||
}
|
||||
} 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 {
|
||||
m.writeCustomResponse(w, state.StatusCode)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if phase == 1 && 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,
|
||||
zap.String("message", "Request blocked by DNS blacklist"),
|
||||
zap.String("host", r.Host),
|
||||
)
|
||||
return
|
||||
// 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",
|
||||
zap.String("message", "Request blocked by DNS blacklist"),
|
||||
zap.String("host", r.Host),
|
||||
)
|
||||
if m.CustomResponses != nil {
|
||||
m.writeCustomResponse(w, state.StatusCode)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Rate limiting
|
||||
if m.rateLimiter != nil {
|
||||
m.logger.Debug("Starting rate limiting phase")
|
||||
ip := extractIP(r.RemoteAddr) // Pass the logger here
|
||||
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",
|
||||
zap.String("message", "Request blocked by rate limit"),
|
||||
)
|
||||
if m.CustomResponses != nil {
|
||||
m.writeCustomResponse(w, state.StatusCode)
|
||||
}
|
||||
return
|
||||
}
|
||||
m.logger.Debug("Rate limiting phase completed - not blocked")
|
||||
}
|
||||
|
||||
// Whitelisting
|
||||
if m.CountryWhitelist.Enabled {
|
||||
m.logger.Debug("Starting country whitelisting phase")
|
||||
allowed, err := m.isCountryInList(r.RemoteAddr, m.CountryWhitelist.CountryList, m.CountryWhitelist.geoIP)
|
||||
if err != nil {
|
||||
m.logRequest(zapcore.ErrorLevel, "Failed to check country whitelist",
|
||||
r,
|
||||
zap.Error(err),
|
||||
)
|
||||
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",
|
||||
zap.String("message", "Request blocked by country"))
|
||||
m.incrementGeoIPRequestsMetric(true) // Increment with true for blocked
|
||||
if m.CustomResponses != nil {
|
||||
m.writeCustomResponse(w, state.StatusCode)
|
||||
}
|
||||
return
|
||||
}
|
||||
m.logger.Debug("Country whitelisting phase completed - not blocked")
|
||||
m.incrementGeoIPRequestsMetric(false) // Increment with false for no block
|
||||
}
|
||||
|
||||
// ASN Blocking
|
||||
if m.BlockASNs.Enabled {
|
||||
m.logger.Debug("Starting ASN blocking phase")
|
||||
blocked, err := m.geoIPHandler.IsASNInList(r.RemoteAddr, 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(r.RemoteAddr, 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)
|
||||
if err != nil {
|
||||
m.logRequest(zapcore.ErrorLevel, "Failed to check country blacklisting",
|
||||
r,
|
||||
zap.Error(err),
|
||||
)
|
||||
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",
|
||||
zap.String("message", "Request blocked by country"))
|
||||
m.incrementGeoIPRequestsMetric(true) // Increment with true for blocked
|
||||
if m.CustomResponses != nil {
|
||||
m.writeCustomResponse(w, state.StatusCode)
|
||||
}
|
||||
return
|
||||
}
|
||||
m.logger.Debug("Country blacklisting phase completed - not blocked")
|
||||
m.incrementGeoIPRequestsMetric(false) // Increment with false for no block
|
||||
}
|
||||
}
|
||||
|
||||
rules, ok := m.Rules[phase]
|
||||
if !ok {
|
||||
m.logger.Debug("No rules found for phase", zap.Int("phase", phase))
|
||||
return
|
||||
// Don't block on empty rules. There may be no rules specified
|
||||
// return
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -342,52 +459,58 @@ 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),
|
||||
)
|
||||
|
||||
if m.CustomResponses != nil {
|
||||
m.writeCustomResponse(w, state.StatusCode)
|
||||
}
|
||||
return
|
||||
}
|
||||
} 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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -414,6 +537,8 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
zap.Int("total_score", state.TotalScore),
|
||||
zap.Int("anomaly_threshold", m.AnomalyThreshold),
|
||||
)
|
||||
|
||||
m.allowRequest(state)
|
||||
}
|
||||
|
||||
// incrementRateLimiterBlockedRequestsMetric increments the blocked requests metric for the rate limiter.
|
||||
|
||||
493
handler_test.go
493
handler_test.go
@@ -6,14 +6,18 @@ import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/phemmer/go-iptrie"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
func TestBlockedRequestPhase1_DNSBlacklist(t *testing.T) {
|
||||
@@ -23,63 +27,227 @@ func TestBlockedRequestPhase1_DNSBlacklist(t *testing.T) {
|
||||
dnsBlacklist: map[string]struct{}{
|
||||
"malicious.domain": {},
|
||||
},
|
||||
ipBlacklist: NewCIDRTrie(), // Initialize ipBlacklist
|
||||
CustomResponses: map[int]CustomBlockResponse{
|
||||
403: {
|
||||
StatusCode: http.StatusForbidden,
|
||||
Body: "Access Denied",
|
||||
},
|
||||
},
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
// Simulate a request to a blacklisted domain
|
||||
req := httptest.NewRequest("GET", "http://malicious.domain", nil)
|
||||
w := httptest.NewRecorder()
|
||||
state := &WAFState{}
|
||||
|
||||
// Process the request in Phase 1
|
||||
middleware.handlePhase(w, req, 1, state)
|
||||
t.Run("Allow unblocked domain", func(t *testing.T) {
|
||||
// Simulate a request to a blacklisted domain
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
|
||||
// Debug: Print the response body and status code
|
||||
t.Logf("Response Body: %s", w.Body.String())
|
||||
t.Logf("Response Status Code: %d", w.Code)
|
||||
// Process the request in Phase 1
|
||||
middleware.handlePhase(w, req, 1, state)
|
||||
assert.False(t, state.Blocked, "Request should be allowed")
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
|
||||
})
|
||||
|
||||
// Verify that the request was blocked
|
||||
assert.True(t, state.Blocked, "Request should be blocked")
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Expected status code 403")
|
||||
assert.Contains(t, w.Body.String(), "Access Denied", "Response body should contain 'Access Denied'")
|
||||
t.Run("Block blacklisted domain", func(t *testing.T) {
|
||||
// Simulate a request to a blacklisted domain
|
||||
req := httptest.NewRequest("GET", "http://malicious.domain", nil)
|
||||
req.RemoteAddr = localIP
|
||||
|
||||
// Process the request in Phase 1
|
||||
middleware.handlePhase(w, req, 1, state)
|
||||
|
||||
// Verify that the request was blocked
|
||||
assert.True(t, state.Blocked, "Request should be blocked")
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Expected status code 403")
|
||||
assert.Contains(t, w.Body.String(), "Access Denied", "Response body should contain 'Access Denied'")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlockedRequestPhase1_GeoIPBlocking(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
middleware := &Middleware{
|
||||
logger: logger,
|
||||
CountryBlock: CountryAccessFilter{
|
||||
if _, err := os.Stat(geoIPdata); os.IsNotExist(err) {
|
||||
t.Skip("GeoIP database not found, skipping test")
|
||||
}
|
||||
logger, err := zap.NewDevelopment()
|
||||
assert.NoError(t, err)
|
||||
|
||||
geoIPHandler := NewGeoIPHandler(logger)
|
||||
geoIPBlock, err := geoIPHandler.LoadGeoIPDatabase(geoIPdata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blMiddleware := &Middleware{
|
||||
logger: logger,
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
geoIPHandler: geoIPHandler,
|
||||
CountryBlacklist: CountryAccessFilter{
|
||||
Enabled: true,
|
||||
CountryList: []string{"US"},
|
||||
GeoIPDBPath: "testdata/GeoIP2-Country-Test.mmdb", // Path to a test GeoIP database
|
||||
},
|
||||
CustomResponses: map[int]CustomBlockResponse{
|
||||
403: {
|
||||
StatusCode: http.StatusForbidden,
|
||||
Body: "Access Denied",
|
||||
},
|
||||
CountryList: []string{"US", "RU"},
|
||||
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
|
||||
geoIP: geoIPBlock,
|
||||
},
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
// Simulate a request from a blocked country (US)
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req.RemoteAddr = "192.168.1.1:12345" // IP from the US (mocked in the test GeoIP database)
|
||||
w := httptest.NewRecorder()
|
||||
wlMiddleware := &Middleware{
|
||||
logger: logger,
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
geoIPHandler: geoIPHandler,
|
||||
CountryWhitelist: CountryAccessFilter{
|
||||
Enabled: true,
|
||||
CountryList: []string{"BR"},
|
||||
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
|
||||
geoIP: geoIPBlock,
|
||||
},
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
blackWhiteMw := &Middleware{
|
||||
logger: logger,
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
geoIPHandler: geoIPHandler,
|
||||
CountryWhitelist: CountryAccessFilter{
|
||||
Enabled: true,
|
||||
CountryList: []string{"BR"},
|
||||
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
|
||||
geoIP: geoIPBlock,
|
||||
},
|
||||
CountryBlacklist: CountryAccessFilter{
|
||||
Enabled: true,
|
||||
CountryList: []string{"US", "RU"},
|
||||
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
|
||||
geoIP: geoIPBlock,
|
||||
},
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
|
||||
state := &WAFState{}
|
||||
|
||||
// Process the request in Phase 1
|
||||
middleware.handlePhase(w, req, 1, state)
|
||||
t.Run("GeoIP Blacklist: Allow CN IP", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req.RemoteAddr = aliCNIP
|
||||
|
||||
// Verify that the request was blocked
|
||||
assert.True(t, state.Blocked, "Request should be blocked")
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Expected status code 403")
|
||||
assert.Contains(t, w.Body.String(), "Access Denied", "Response body should contain 'Access Denied'")
|
||||
// Process the request in Phase 1
|
||||
blMiddleware.handlePhase(w, req, 1, state)
|
||||
assert.False(t, state.Blocked, "Request should be allowed")
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
|
||||
})
|
||||
|
||||
t.Run("GeoIP Blacklist: Block US IP", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req.RemoteAddr = googleUSIP
|
||||
|
||||
// Process the request in Phase 1
|
||||
blMiddleware.handlePhase(w, req, 1, state)
|
||||
|
||||
// Verify that the request was blocked
|
||||
assert.True(t, state.Blocked, "Request should be blocked")
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Expected status code 403")
|
||||
assert.Contains(t, w.Body.String(), "Access Denied", "Response body should contain 'Access Denied'")
|
||||
})
|
||||
|
||||
t.Run("GeoIP Whitelist: Allow BR IP", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req.RemoteAddr = googleBRIP
|
||||
|
||||
// Process the request in Phase 1
|
||||
wlMiddleware.handlePhase(w, req, 1, state)
|
||||
assert.False(t, state.Blocked, "Request should be allowed")
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
|
||||
})
|
||||
|
||||
t.Run("GeoIP Whitelist: Block RU IP", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req.RemoteAddr = googleRUIP
|
||||
|
||||
// Process the request in Phase 1
|
||||
wlMiddleware.handlePhase(w, req, 1, state)
|
||||
|
||||
// Verify that the request was blocked
|
||||
assert.True(t, state.Blocked, "Request should be blocked")
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Expected status code 403")
|
||||
assert.Contains(t, w.Body.String(), "Access Denied", "Response body should contain 'Access Denied'")
|
||||
})
|
||||
|
||||
t.Run("GeoIP whitelist and blacklist: whitelist has the priority", func(t *testing.T) {
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// BR should be allowed
|
||||
req0 := httptest.NewRequest("GET", testURL, nil)
|
||||
req0.RemoteAddr = googleBRIP
|
||||
|
||||
blackWhiteMw.handlePhase(w, req0, 1, state)
|
||||
assert.False(t, state.Blocked, "Request should be allowed")
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
|
||||
|
||||
// US must be blocked
|
||||
req1 := httptest.NewRequest("GET", testURL, nil)
|
||||
req1.RemoteAddr = googleUSIP
|
||||
|
||||
blackWhiteMw.handlePhase(w, req1, 1, state)
|
||||
// Verify that the request was blocked
|
||||
assert.True(t, state.Blocked, "Request should be blocked")
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Expected status code 403")
|
||||
assert.Contains(t, w.Body.String(), "Access Denied", "Response body should contain 'Access Denied'")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlockedRequestPhase1_IPBlocking(t *testing.T) {
|
||||
logger, err := zap.NewDevelopment()
|
||||
assert.NoError(t, err)
|
||||
|
||||
blackList := iptrie.NewTrie()
|
||||
loader := iptrie.NewTrieLoader(blackList)
|
||||
|
||||
for _, net := range []string{
|
||||
"192.168.0.0/24",
|
||||
"192.168.1.1/32",
|
||||
} {
|
||||
loader.Insert(netip.MustParsePrefix(net), "net="+net)
|
||||
}
|
||||
|
||||
state := &WAFState{}
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
t.Run("Allow unblocked CIDR", func(t *testing.T) {
|
||||
middleware := &Middleware{
|
||||
logger: logger,
|
||||
ipBlacklist: blackList,
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
|
||||
// Process the request in Phase 1
|
||||
middleware.handlePhase(w, req, 1, state)
|
||||
|
||||
assert.False(t, state.Blocked, "Request should be allowed")
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
|
||||
})
|
||||
|
||||
t.Run("Blocks blacklisted CIDR", func(t *testing.T) {
|
||||
middleware := &Middleware{
|
||||
logger: logger,
|
||||
ipBlacklist: blackList,
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = "192.168.1.1"
|
||||
|
||||
// Process the request in Phase 1
|
||||
middleware.handlePhase(w, req, 1, state)
|
||||
|
||||
// Verify that the request was blocked
|
||||
assert.True(t, state.Blocked, "Request should be blocked")
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Expected status code 403")
|
||||
assert.Contains(t, w.Body.String(), "Access Denied", "Response body should contain 'Access Denied'")
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandlePhase_Phase2_NiktoUserAgent(t *testing.T) {
|
||||
@@ -100,18 +268,13 @@ func TestHandlePhase_Phase2_NiktoUserAgent(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
CustomResponses: map[int]CustomBlockResponse{
|
||||
403: {
|
||||
StatusCode: http.StatusForbidden,
|
||||
Body: "Access Denied",
|
||||
},
|
||||
},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
CustomResponses: customResponse,
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.Header.Set("User-Agent", "nikto")
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -135,7 +298,9 @@ func TestHandlePhase_Phase2_NiktoUserAgent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBlockedRequestPhase1_HeaderRegex(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
logger, err := zap.NewDevelopment()
|
||||
assert.NoError(t, err)
|
||||
|
||||
middleware := &Middleware{
|
||||
logger: logger,
|
||||
Rules: map[int][]Rule{
|
||||
@@ -158,12 +323,13 @@ func TestBlockedRequestPhase1_HeaderRegex(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("X-Custom-Header", "this-is-a-bad-header") // Simulate a request with bad header
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -210,12 +376,13 @@ func TestBlockedRequestPhase1_HeaderRegex_SpecificValue(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("X-Specific-Header", "specific-value") // Simulate a request with the specific header
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -262,12 +429,13 @@ func TestBlockedRequestPhase1_HeaderRegex_CommaSeparatedTargets(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("X-Custom-Header1", "good-value")
|
||||
req.Header.Set("X-Custom-Header2", "bad-value") // Simulate a request with bad value in one of the headers
|
||||
|
||||
@@ -315,12 +483,13 @@ func TestBlockedRequestPhase1_CombinedConditions(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
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)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("User-Agent", "good-user")
|
||||
|
||||
// Create a context and add logID to it
|
||||
@@ -367,12 +536,13 @@ func TestBlockedRequestPhase1_NoMatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("User-Agent", "good-user")
|
||||
|
||||
// Create a context and add logID to it
|
||||
@@ -419,12 +589,13 @@ func TestBlockedRequestPhase1_HeaderRegex_EmptyHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
|
||||
// Create a context and add logID to it
|
||||
ctx := context.Background()
|
||||
@@ -445,6 +616,7 @@ func TestBlockedRequestPhase1_HeaderRegex_EmptyHeader(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
|
||||
assert.Empty(t, w.Body.String(), "Response body should be empty")
|
||||
}
|
||||
|
||||
func TestBlockedRequestPhase1_HeaderRegex_MissingHeader(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
middleware := &Middleware{
|
||||
@@ -469,12 +641,13 @@ func TestBlockedRequestPhase1_HeaderRegex_MissingHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil) // Header not set
|
||||
req := httptest.NewRequest("GET", testURL, nil) // Header not set
|
||||
req.RemoteAddr = localIP
|
||||
|
||||
// Create a context and add logID to it
|
||||
ctx := context.Background()
|
||||
@@ -494,7 +667,6 @@ func TestBlockedRequestPhase1_HeaderRegex_MissingHeader(t *testing.T) {
|
||||
assert.False(t, state.Blocked, "Request should not be blocked because header is missing")
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Expected status code 200")
|
||||
assert.Empty(t, w.Body.String(), "Response body should be empty")
|
||||
|
||||
}
|
||||
|
||||
func TestBlockedRequestPhase1_HeaderRegex_ComplexPattern(t *testing.T) {
|
||||
@@ -521,12 +693,13 @@ func TestBlockedRequestPhase1_HeaderRegex_ComplexPattern(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("X-Email-Header", "test@example.com") // Simulate a request with a valid email
|
||||
|
||||
// Create a context and add logID to it
|
||||
@@ -573,12 +746,13 @@ func TestBlockedRequestPhase1_MultiTargetMatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("X-Custom-Header", "good-header")
|
||||
req.Header.Set("User-Agent", "bad-user-agent")
|
||||
|
||||
@@ -625,12 +799,13 @@ func TestBlockedRequestPhase1_MultiTargetNoMatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("X-Custom-Header", "good-header")
|
||||
req.Header.Set("User-Agent", "good-user-agent")
|
||||
|
||||
@@ -678,12 +853,13 @@ func TestBlockedRequestPhase1_URLParameterRegex_NoMatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
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)
|
||||
req.RemoteAddr = localIP
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
ctx := context.Background()
|
||||
@@ -737,12 +913,13 @@ func TestBlockedRequestPhase1_MultipleRules(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
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)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("User-Agent", "bad-user") // Simulate a request with a bad user agent
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -765,6 +942,7 @@ func TestBlockedRequestPhase1_MultipleRules(t *testing.T) {
|
||||
assert.Contains(t, w.Body.String(), "Blocked by Multiple Rules", "Response body should contain 'Blocked by Multiple Rules'")
|
||||
|
||||
req2 := httptest.NewRequest("GET", "http://good-host.com", nil)
|
||||
req2.RemoteAddr = localIP
|
||||
req2.Header.Set("User-Agent", "bad-user") // Simulate a request with a bad user agent
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE for req2 as well!
|
||||
@@ -811,18 +989,19 @@ func TestBlockedRequestPhase2_BodyRegex(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com",
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
func() *bytes.Buffer {
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString("this-is-a-bad-body")
|
||||
return b
|
||||
}(), // Simulate a request with bad body
|
||||
)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -869,18 +1048,19 @@ func TestBlockedRequestPhase2_BodyRegex_JSON(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com",
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
func() *bytes.Buffer {
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString(`{"data":{"malicious":true,"name":"test"}}`)
|
||||
return b
|
||||
}(), // Simulate a request with JSON body
|
||||
)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -927,14 +1107,15 @@ func TestBlockedRequestPhase2_BodyRegex_FormURLEncoded(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com",
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
strings.NewReader("param1=value1&secret=badvalue¶m2=value2"),
|
||||
)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -981,18 +1162,19 @@ func TestBlockedRequestPhase2_BodyRegex_SpecificPattern(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com",
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
func() *bytes.Buffer {
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString("User ID: 123-45-6789")
|
||||
return b
|
||||
}(),
|
||||
)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("Content-Type", "text/plain") // Setting content type
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -1039,18 +1221,19 @@ func TestBlockedRequestPhase2_BodyRegex_NoMatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com",
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
func() *bytes.Buffer {
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString("this-is-a-good-body")
|
||||
return b
|
||||
}(),
|
||||
)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -1097,9 +1280,9 @@ func TestBlockedRequestPhase2_BodyRegex_NoMatch_MultipartForm(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
@@ -1117,7 +1300,8 @@ func TestBlockedRequestPhase2_BodyRegex_NoMatch_MultipartForm(t *testing.T) {
|
||||
t.Fatalf("Failed to close multipart writer: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com", body)
|
||||
req := httptest.NewRequest("POST", testURL, body)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -1164,12 +1348,13 @@ func TestBlockedRequestPhase2_BodyRegex_NoBody(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", "http://example.com", nil)
|
||||
req := httptest.NewRequest("POST", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
w := httptest.NewRecorder()
|
||||
state := &WAFState{}
|
||||
|
||||
@@ -1209,9 +1394,9 @@ func TestBlockedRequestPhase3_ResponseHeaderRegex_NoMatch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
mockHandler := func() caddyhttp.Handler {
|
||||
@@ -1222,7 +1407,8 @@ func TestBlockedRequestPhase3_ResponseHeaderRegex_NoMatch(t *testing.T) {
|
||||
})
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
w := httptest.NewRecorder()
|
||||
state := &WAFState{}
|
||||
|
||||
@@ -1264,9 +1450,9 @@ func TestBlockedRequestPhase4_ResponseBodyRegex_EmptyBody(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
mockHandler := func() caddyhttp.Handler {
|
||||
@@ -1276,7 +1462,8 @@ func TestBlockedRequestPhase4_ResponseBodyRegex_EmptyBody(t *testing.T) {
|
||||
})
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
w := httptest.NewRecorder()
|
||||
state := &WAFState{}
|
||||
err := middleware.ServeHTTP(w, req, mockHandler)
|
||||
@@ -1319,9 +1506,9 @@ func TestBlockedRequestPhase4_ResponseBodyRegex_NoBody(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
mockHandler := func() caddyhttp.Handler {
|
||||
@@ -1331,7 +1518,8 @@ func TestBlockedRequestPhase4_ResponseBodyRegex_NoBody(t *testing.T) {
|
||||
})
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
w := httptest.NewRecorder()
|
||||
state := &WAFState{}
|
||||
err := middleware.ServeHTTP(w, req, mockHandler)
|
||||
@@ -1372,9 +1560,9 @@ func TestBlockedRequestPhase3_ResponseHeaderRegex_NoSetCookie(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
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 {
|
||||
@@ -1384,7 +1572,8 @@ func TestBlockedRequestPhase3_ResponseHeaderRegex_NoSetCookie(t *testing.T) {
|
||||
})
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
w := httptest.NewRecorder()
|
||||
state := &WAFState{}
|
||||
err := middleware.ServeHTTP(w, req, mockHandler)
|
||||
@@ -1427,12 +1616,13 @@ func TestBlockedRequestPhase1_HeaderRegex_CaseInsensitive(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("X-Custom-Header", "bAd-VaLuE") // Test with mixed-case header value
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
@@ -1479,12 +1669,13 @@ func TestBlockedRequestPhase1_HeaderRegex_MultipleMatchingHeaders(t *testing.T)
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("X-Custom-Header1", "bad-value")
|
||||
req.Header.Set("X-Custom-Header2", "bad-value") // Both headers have a "bad" value
|
||||
|
||||
@@ -1507,7 +1698,8 @@ func TestBlockedRequestPhase1_HeaderRegex_MultipleMatchingHeaders(t *testing.T)
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Expected status code 403")
|
||||
assert.Contains(t, w.Body.String(), "Blocked by Multiple Matching Headers Regex", "Response body should contain 'Blocked by Multiple Matching Headers Regex'")
|
||||
|
||||
req2 := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req2 := httptest.NewRequest("GET", testURL, nil)
|
||||
req2.RemoteAddr = localIP
|
||||
req2.Header.Set("X-Custom-Header1", "good-value")
|
||||
req2.Header.Set("X-Custom-Header2", "bad-value") // One header has a "bad" value
|
||||
|
||||
@@ -1530,7 +1722,8 @@ func TestBlockedRequestPhase1_HeaderRegex_MultipleMatchingHeaders(t *testing.T)
|
||||
assert.Equal(t, http.StatusForbidden, w2.Code, "Expected status code 403")
|
||||
assert.Contains(t, w2.Body.String(), "Blocked by Multiple Matching Headers Regex", "Response body should contain 'Blocked by Multiple Matching Headers Regex'")
|
||||
|
||||
req3 := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req3 := httptest.NewRequest("GET", testURL, nil)
|
||||
req3.RemoteAddr = localIP
|
||||
req3.Header.Set("X-Custom-Header1", "good-value")
|
||||
req3.Header.Set("X-Custom-Header2", "good-value") // None headers have a "bad" value
|
||||
|
||||
@@ -1564,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
|
||||
@@ -1584,13 +1777,13 @@ func TestBlockedRequestPhase1_RateLimiting_MultiplePaths(t *testing.T) {
|
||||
Body: "Rate limit exceeded",
|
||||
},
|
||||
},
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
// Test path 1
|
||||
req1 := httptest.NewRequest("GET", "/api/v1/users", nil)
|
||||
req1.RemoteAddr = "192.168.1.1:12345"
|
||||
req1.RemoteAddr = localIP
|
||||
w1 := httptest.NewRecorder()
|
||||
state1 := &WAFState{}
|
||||
|
||||
@@ -1599,7 +1792,7 @@ func TestBlockedRequestPhase1_RateLimiting_MultiplePaths(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w1.Code, "Expected status code 200")
|
||||
|
||||
req2 := httptest.NewRequest("GET", "/api/v1/users", nil)
|
||||
req2.RemoteAddr = "192.168.1.1:12345"
|
||||
req2.RemoteAddr = localIP
|
||||
w2 := httptest.NewRecorder()
|
||||
state2 := &WAFState{}
|
||||
middleware.handlePhase(w2, req2, 1, state2)
|
||||
@@ -1608,7 +1801,7 @@ func TestBlockedRequestPhase1_RateLimiting_MultiplePaths(t *testing.T) {
|
||||
|
||||
// Test path 2
|
||||
req3 := httptest.NewRequest("GET", "/admin/dashboard", nil)
|
||||
req3.RemoteAddr = "192.168.1.1:12345"
|
||||
req3.RemoteAddr = localIP
|
||||
w3 := httptest.NewRecorder()
|
||||
state3 := &WAFState{}
|
||||
middleware.handlePhase(w3, req3, 1, state3)
|
||||
@@ -1616,7 +1809,7 @@ func TestBlockedRequestPhase1_RateLimiting_MultiplePaths(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w3.Code, "Expected status code 200")
|
||||
|
||||
req4 := httptest.NewRequest("GET", "/admin/dashboard", nil)
|
||||
req4.RemoteAddr = "192.168.1.1:12345"
|
||||
req4.RemoteAddr = localIP
|
||||
w4 := httptest.NewRecorder()
|
||||
state4 := &WAFState{}
|
||||
middleware.handlePhase(w4, req4, 1, state4)
|
||||
@@ -1624,7 +1817,7 @@ func TestBlockedRequestPhase1_RateLimiting_MultiplePaths(t *testing.T) {
|
||||
assert.Equal(t, http.StatusTooManyRequests, w4.Code, "Expected status code 429")
|
||||
|
||||
req5 := httptest.NewRequest("GET", "/not-rate-limited", nil)
|
||||
req5.RemoteAddr = "192.168.1.1:12345"
|
||||
req5.RemoteAddr = localIP
|
||||
w5 := httptest.NewRecorder()
|
||||
state5 := &WAFState{}
|
||||
middleware.handlePhase(w5, req5, 1, state5)
|
||||
@@ -1654,13 +1847,13 @@ func TestBlockedRequestPhase1_RateLimiting_DifferentIPs(t *testing.T) {
|
||||
Body: "Rate limit exceeded",
|
||||
},
|
||||
},
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
// Test different IPs
|
||||
req1 := httptest.NewRequest("GET", "/api/users", nil)
|
||||
req1.RemoteAddr = "192.168.1.1:12345"
|
||||
req1.RemoteAddr = localIP
|
||||
w1 := httptest.NewRecorder()
|
||||
state1 := &WAFState{}
|
||||
|
||||
@@ -1669,7 +1862,7 @@ func TestBlockedRequestPhase1_RateLimiting_DifferentIPs(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w1.Code, "Expected status code 200")
|
||||
|
||||
req2 := httptest.NewRequest("GET", "/api/users", nil)
|
||||
req2.RemoteAddr = "192.168.1.2:12345"
|
||||
req2.RemoteAddr = "192.168.1.2"
|
||||
w2 := httptest.NewRecorder()
|
||||
state2 := &WAFState{}
|
||||
middleware.handlePhase(w2, req2, 1, state2)
|
||||
@@ -1677,7 +1870,7 @@ func TestBlockedRequestPhase1_RateLimiting_DifferentIPs(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w2.Code, "Expected status code 200")
|
||||
|
||||
req3 := httptest.NewRequest("GET", "/api/users", nil)
|
||||
req3.RemoteAddr = "192.168.1.1:12345"
|
||||
req3.RemoteAddr = localIP
|
||||
w3 := httptest.NewRecorder()
|
||||
state3 := &WAFState{}
|
||||
middleware.handlePhase(w3, req3, 1, state3)
|
||||
@@ -1707,13 +1900,13 @@ func TestBlockedRequestPhase1_RateLimiting_MatchAllPaths(t *testing.T) {
|
||||
Body: "Rate limit exceeded",
|
||||
},
|
||||
},
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
// Test with match all paths
|
||||
req1 := httptest.NewRequest("GET", "/api/users", nil)
|
||||
req1.RemoteAddr = "192.168.1.1:12345"
|
||||
req1.RemoteAddr = localIP
|
||||
w1 := httptest.NewRecorder()
|
||||
state1 := &WAFState{}
|
||||
middleware.handlePhase(w1, req1, 1, state1)
|
||||
@@ -1721,7 +1914,7 @@ func TestBlockedRequestPhase1_RateLimiting_MatchAllPaths(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w1.Code, "Expected status code 200")
|
||||
|
||||
req2 := httptest.NewRequest("GET", "/api/users", nil)
|
||||
req2.RemoteAddr = "192.168.1.1:12345"
|
||||
req2.RemoteAddr = localIP
|
||||
w2 := httptest.NewRecorder()
|
||||
state2 := &WAFState{}
|
||||
|
||||
@@ -1730,7 +1923,7 @@ func TestBlockedRequestPhase1_RateLimiting_MatchAllPaths(t *testing.T) {
|
||||
assert.Equal(t, http.StatusTooManyRequests, w2.Code, "Expected status code 429")
|
||||
|
||||
req3 := httptest.NewRequest("GET", "/some-other-path", nil)
|
||||
req3.RemoteAddr = "192.168.1.1:12345"
|
||||
req3.RemoteAddr = localIP
|
||||
w3 := httptest.NewRecorder()
|
||||
state3 := &WAFState{}
|
||||
middleware.handlePhase(w3, req3, 1, state3)
|
||||
|
||||
30
helpers.go
30
helpers.go
@@ -1,7 +1,9 @@
|
||||
package caddywaf
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// fileExists checks if a file exists and is readable.
|
||||
@@ -15,3 +17,31 @@ func fileExists(path string) bool {
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
// isIPv4 - checks if input IP is of type v4
|
||||
//
|
||||
//nolint:unused
|
||||
func isIPv4(addr string) bool {
|
||||
return strings.Count(addr, ":") < 2
|
||||
}
|
||||
|
||||
// appendCIDR - appends CIDR for a single IP
|
||||
func appendCIDR(ip string) string {
|
||||
// IPv4
|
||||
if strings.Count(ip, ":") < 2 {
|
||||
ip += "/32"
|
||||
// IPv6
|
||||
} else {
|
||||
ip += "/64"
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// extractIP extracts the IP address from a remote address string.
|
||||
func extractIP(remoteAddr string) string {
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
return remoteAddr // Assume the input is already an IP address
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
38
it_test.go
Normal file
38
it_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build it
|
||||
|
||||
package caddywaf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
_ "github.com/fabriziosalmi/caddy-waf"
|
||||
)
|
||||
|
||||
func TestWaf_IPBlacklisting(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
http://localhost:9080 {
|
||||
route {
|
||||
waf {
|
||||
anomaly_threshold 20
|
||||
rule_file rules.json
|
||||
|
||||
ip_blacklist_file ip_blacklist.txt
|
||||
dns_blacklist_file dns_blacklist.txt
|
||||
log_severity info
|
||||
}
|
||||
}
|
||||
respond "Hello, World!"
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
|
||||
}
|
||||
@@ -105,7 +105,6 @@ func (m *Middleware) logRequest(level zapcore.Level, msg string, r *http.Request
|
||||
)
|
||||
m.logger.Log(level, msg, allFields...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// redactSensitiveFields redacts sensitive information in the log fields.
|
||||
@@ -130,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
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestLogRequest(t *testing.T) {
|
||||
|
||||
// Create a test request
|
||||
req := httptest.NewRequest("GET", "/test?foo=bar", nil)
|
||||
req.RemoteAddr = "192.168.1.1:12345"
|
||||
req.RemoteAddr = localIP
|
||||
req.Header.Set("User-Agent", "test-agent")
|
||||
|
||||
// Log a test message
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/phemmer/go-iptrie"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -397,13 +398,13 @@ func TestBlockedRequestPhase1_RateLimiting(t *testing.T) {
|
||||
Body: "Rate limit exceeded",
|
||||
},
|
||||
},
|
||||
ipBlacklist: NewCIDRTrie(), // Initialize ipBlacklist
|
||||
ipBlacklist: iptrie.NewTrie(), // Initialize ipBlacklist
|
||||
dnsBlacklist: make(map[string]struct{}), // Initialize dnsBlacklist
|
||||
}
|
||||
|
||||
// Simulate two requests from the same IP
|
||||
req := httptest.NewRequest("GET", "http://example.com/api/test", nil)
|
||||
req.RemoteAddr = "192.168.1.1:12345"
|
||||
req.RemoteAddr = localIP
|
||||
w1 := httptest.NewRecorder()
|
||||
w2 := httptest.NewRecorder()
|
||||
state1 := &WAFState{}
|
||||
|
||||
40
request.go
40
request.go
@@ -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
76
request_body_test.go
Normal 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))
|
||||
})
|
||||
}
|
||||
@@ -6,19 +6,22 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/phemmer/go-iptrie"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
func TestExtractValue(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, true)
|
||||
rve := NewRequestValueExtractor(logger, true, 0)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -31,7 +34,7 @@ func TestExtractValue(t *testing.T) {
|
||||
name: "Extract METHOD",
|
||||
target: "METHOD",
|
||||
setupRequest: func() (*http.Request, http.ResponseWriter) {
|
||||
req := httptest.NewRequest("POST", "http://example.com", nil)
|
||||
req := httptest.NewRequest("POST", testURL, nil)
|
||||
return req, httptest.NewRecorder()
|
||||
},
|
||||
expectedValue: "POST",
|
||||
@@ -51,7 +54,7 @@ func TestExtractValue(t *testing.T) {
|
||||
name: "Extract USER_AGENT",
|
||||
target: "USER_AGENT",
|
||||
setupRequest: func() (*http.Request, http.ResponseWriter) {
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.Header.Set("User-Agent", "test-agent")
|
||||
return req, httptest.NewRecorder()
|
||||
},
|
||||
@@ -62,7 +65,7 @@ func TestExtractValue(t *testing.T) {
|
||||
name: "Extract HEADERS prefix",
|
||||
target: "HEADERS:Content-Type",
|
||||
setupRequest: func() (*http.Request, http.ResponseWriter) {
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req, httptest.NewRecorder()
|
||||
},
|
||||
@@ -83,7 +86,7 @@ func TestExtractValue(t *testing.T) {
|
||||
name: "Empty target",
|
||||
target: "",
|
||||
setupRequest: func() (*http.Request, http.ResponseWriter) {
|
||||
return httptest.NewRequest("GET", "http://example.com", nil), httptest.NewRecorder()
|
||||
return httptest.NewRequest("GET", testURL, nil), httptest.NewRecorder()
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
@@ -141,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)
|
||||
@@ -156,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")
|
||||
@@ -169,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()
|
||||
@@ -181,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()
|
||||
@@ -193,20 +196,20 @@ 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 = "192.168.1.1:12345"
|
||||
req.RemoteAddr = localIP
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
value, err := rve.ExtractValue("REMOTE_IP", req, w)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "192.168.1.1:12345", value)
|
||||
assert.Equal(t, localIP, value)
|
||||
}
|
||||
|
||||
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"
|
||||
@@ -219,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"
|
||||
@@ -232,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()
|
||||
@@ -244,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")
|
||||
@@ -257,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()
|
||||
@@ -269,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()
|
||||
@@ -281,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)
|
||||
@@ -294,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")
|
||||
@@ -307,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"})
|
||||
@@ -320,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()
|
||||
@@ -361,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{
|
||||
@@ -380,7 +384,7 @@ func TestProcessRuleMatch_HighScore(t *testing.T) {
|
||||
ResponseWritten: false,
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
|
||||
// Create a context and add logID to it - FIX: ADD CONTEXT HERE
|
||||
ctx := context.Background()
|
||||
@@ -391,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)
|
||||
@@ -416,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)
|
||||
@@ -442,9 +446,9 @@ func TestConcurrentRuleEvaluation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: NewCIDRTrie(),
|
||||
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,
|
||||
@@ -456,25 +460,20 @@ func TestConcurrentRuleEvaluation(t *testing.T) {
|
||||
}
|
||||
return rl
|
||||
}(),
|
||||
CustomResponses: map[int]CustomBlockResponse{
|
||||
403: {
|
||||
StatusCode: http.StatusForbidden,
|
||||
Body: "Access Denied",
|
||||
},
|
||||
},
|
||||
CustomResponses: customResponse,
|
||||
}
|
||||
|
||||
// Add some IPs to the blacklist
|
||||
middleware.ipBlacklist.Insert("192.168.1.0/24")
|
||||
middleware.ipBlacklist.Insert(netip.MustParsePrefix("192.168.1.0/24"), nil)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
req.RemoteAddr = fmt.Sprintf("192.168.1.%d:12345", i%256) // Simulate different IPs
|
||||
req.Header.Set("User-Agent", "test-agent") // Add a header for rule evaluation
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
req.RemoteAddr = fmt.Sprintf("192.168.1.%d", i%256) // Simulate different IPs
|
||||
req.Header.Set("User-Agent", "test-agent") // Add a header for rule evaluation
|
||||
w := httptest.NewRecorder()
|
||||
state := &WAFState{}
|
||||
middleware.handlePhase(w, req, 1, state)
|
||||
|
||||
22
response.go
22
response.go
@@ -8,8 +8,17 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// allowRequest - handles request allowing
|
||||
func (m *Middleware) allowRequest(state *WAFState) {
|
||||
state.Blocked = false
|
||||
state.StatusCode = http.StatusOK
|
||||
state.ResponseWritten = false
|
||||
|
||||
m.incrementAllowedRequestsMetric()
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -30,9 +39,13 @@ func (m *Middleware) blockRequest(recorder http.ResponseWriter, r *http.Request,
|
||||
recorder.Header().Set("Content-Type", "text/plain")
|
||||
recorder.WriteHeader(statusCode)
|
||||
|
||||
message := fmt.Sprintf("Request blocked by WAF. Reason: %s", reason)
|
||||
if _, err := recorder.Write([]byte(message)); err != nil {
|
||||
m.logger.Error("Failed to write blocked response", zap.Error(err))
|
||||
if m.CustomResponses != nil {
|
||||
m.writeCustomResponse(recorder, state.StatusCode)
|
||||
} else {
|
||||
message := fmt.Sprintf("Request blocked by WAF. Reason: %s", reason)
|
||||
if _, err := recorder.Write([]byte(message)); err != nil {
|
||||
m.logger.Error("Failed to write blocked response", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +71,6 @@ func NewResponseRecorder(w http.ResponseWriter) *responseRecorder {
|
||||
func (r *responseRecorder) WriteHeader(statusCode int) {
|
||||
r.statusCode = statusCode
|
||||
r.ResponseWriter.WriteHeader(statusCode)
|
||||
|
||||
}
|
||||
|
||||
// Header returns the response headers.
|
||||
|
||||
@@ -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
|
||||
|
||||
38
rules.go
38
rules.go
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -187,7 +188,7 @@ func TestLoadRules(t *testing.T) {
|
||||
"action": "block"
|
||||
}
|
||||
]`
|
||||
os.WriteFile(validRuleFile, []byte(validRules), 0644)
|
||||
os.WriteFile(validRuleFile, []byte(validRules), 0o644)
|
||||
|
||||
invalidRuleFile := filepath.Join(tmpDir, "invalid_rules.json")
|
||||
invalidRules := `[
|
||||
@@ -199,7 +200,7 @@ func TestLoadRules(t *testing.T) {
|
||||
"score": -1
|
||||
}
|
||||
]`
|
||||
os.WriteFile(invalidRuleFile, []byte(invalidRules), 0644)
|
||||
os.WriteFile(invalidRuleFile, []byte(invalidRules), 0o644)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -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
|
||||
|
||||
33
tor.go
33
tor.go
@@ -9,20 +9,22 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
var torExitNodeURL = "https://check.torproject.org/torbulkexitlist"
|
||||
|
||||
type TorConfig struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
TORIPBlacklistFile string `json:"tor_ip_blacklist_file,omitempty"`
|
||||
UpdateInterval string `json:"update_interval,omitempty"`
|
||||
RetryOnFailure bool `json:"retry_on_failure,omitempty"` // Enable/disable retries
|
||||
RetryInterval string `json:"retry_interval,omitempty"` // Retry interval (e.g., "5m")
|
||||
lastUpdated time.Time
|
||||
logger *zap.Logger
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
CustomTORExitNodeURL string `json:"custom_tor_exit_node_url"`
|
||||
TORIPBlacklistFile string `json:"tor_ip_blacklist_file,omitempty"`
|
||||
UpdateInterval string `json:"update_interval,omitempty"`
|
||||
RetryOnFailure bool `json:"retry_on_failure,omitempty"` // Enable/disable retries
|
||||
RetryInterval string `json:"retry_interval,omitempty"` // Retry interval (e.g., "5m")
|
||||
lastUpdated time.Time
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// Provision sets up the Tor blocking configuration.
|
||||
@@ -41,19 +43,24 @@ func (t *TorConfig) Provision(ctx caddy.Context) error {
|
||||
func (t *TorConfig) updateTorExitNodes() error {
|
||||
t.logger.Debug("Updating Tor exit nodes...") // Debug log at start of update
|
||||
|
||||
resp, err := http.Get(torExitNodeURL)
|
||||
url := torExitNodeURL
|
||||
if t.CustomTORExitNodeURL != "" {
|
||||
url = t.CustomTORExitNodeURL
|
||||
}
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http get failed for %s: %w", torExitNodeURL, err) // Improved error message with URL
|
||||
return fmt.Errorf("http get failed for %s: %w", url, err) // Improved error message with URL
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("http get returned status %s for %s", resp.Status, torExitNodeURL) // Check for non-200 status
|
||||
return fmt.Errorf("http get returned status %s for %s", resp.Status, url) // Check for non-200 status
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body from %s: %w", torExitNodeURL, err) // Improved error message with URL
|
||||
return fmt.Errorf("failed to read response body from %s: %w", url, err) // Improved error message with URL
|
||||
}
|
||||
|
||||
torIPs := strings.Split(string(data), "\n")
|
||||
@@ -128,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), 0644)
|
||||
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
|
||||
}
|
||||
|
||||
12
tor_test.go
12
tor_test.go
@@ -6,9 +6,10 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func TestTorConfig_Provision(t *testing.T) {
|
||||
@@ -40,10 +41,11 @@ func TestTorConfig_Provision(t *testing.T) {
|
||||
{
|
||||
name: "enabled config",
|
||||
config: TorConfig{
|
||||
Enabled: true,
|
||||
TORIPBlacklistFile: tmpFile.Name(),
|
||||
UpdateInterval: "5m",
|
||||
logger: logger,
|
||||
Enabled: true,
|
||||
CustomTORExitNodeURL: torListURL,
|
||||
TORIPBlacklistFile: tmpFile.Name(),
|
||||
UpdateInterval: "5m",
|
||||
logger: logger,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
|
||||
183
types.go
183
types.go
@@ -1,8 +1,6 @@
|
||||
package caddywaf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -11,6 +9,7 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
"github.com/phemmer/go-iptrie"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
@@ -28,70 +27,10 @@ var (
|
||||
)
|
||||
|
||||
// Define custom types for rule hits
|
||||
type RuleID string
|
||||
type HitCount int
|
||||
|
||||
// ==================== Struct Definitions ====================
|
||||
type TrieNode struct {
|
||||
children map[byte]*TrieNode
|
||||
isLeaf bool
|
||||
}
|
||||
|
||||
func NewTrieNode() *TrieNode {
|
||||
return &TrieNode{
|
||||
children: make(map[byte]*TrieNode), // Initialize the map
|
||||
isLeaf: false,
|
||||
}
|
||||
}
|
||||
|
||||
type CIDRTrie struct {
|
||||
ipv4Root *TrieNode
|
||||
ipv6Root *TrieNode
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewCIDRTrie() *CIDRTrie {
|
||||
return &CIDRTrie{
|
||||
ipv4Root: NewTrieNode(), // Initialize with a new TrieNode
|
||||
ipv6Root: NewTrieNode(), // Initialize with a new TrieNode
|
||||
}
|
||||
}
|
||||
|
||||
func (t *CIDRTrie) Insert(cidr string) error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
ip, ipNet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ip.To4() != nil {
|
||||
// IPv4
|
||||
return t.insertIPv4(ipNet)
|
||||
} else {
|
||||
// IPv6
|
||||
return t.insertIPv6(ipNet)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *CIDRTrie) Contains(ipStr string) bool {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if ip.To4() != nil {
|
||||
// IPv4
|
||||
return t.containsIPv4(ip)
|
||||
} else {
|
||||
// IPv6
|
||||
return t.containsIPv6(ip)
|
||||
}
|
||||
}
|
||||
type (
|
||||
RuleID string
|
||||
HitCount int
|
||||
)
|
||||
|
||||
// RuleCache caches compiled regex patterns for rules.
|
||||
type RuleCache struct {
|
||||
@@ -107,6 +46,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 {
|
||||
@@ -114,6 +61,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"`
|
||||
@@ -164,10 +117,11 @@ type Middleware struct {
|
||||
IPBlacklistFile string `json:"ip_blacklist_file"`
|
||||
DNSBlacklistFile string `json:"dns_blacklist_file"`
|
||||
AnomalyThreshold int `json:"anomaly_threshold"`
|
||||
CountryBlock CountryAccessFilter `json:"country_block"`
|
||||
CountryBlacklist CountryAccessFilter `json:"country_blacklist"`
|
||||
CountryWhitelist CountryAccessFilter `json:"country_whitelist"`
|
||||
BlockASNs ASNAccessFilter `json:"block_asns"`
|
||||
Rules map[int][]Rule `json:"-"`
|
||||
ipBlacklist *CIDRTrie `json:"-"` // Changed to CIDRTrie
|
||||
ipBlacklist *iptrie.Trie `json:"-"`
|
||||
dnsBlacklist map[string]struct{} `json:"-"` // Changed to map[string]struct{}
|
||||
logger *zap.Logger
|
||||
LogSeverity string `json:"log_severity,omitempty"`
|
||||
@@ -180,8 +134,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"`
|
||||
@@ -244,90 +200,3 @@ func (rc *RuleCache) Set(ruleID string, regex *regexp.Regexp) {
|
||||
defer rc.mu.Unlock()
|
||||
rc.rules[ruleID] = regex
|
||||
}
|
||||
|
||||
func (t *CIDRTrie) insertIPv4(ipNet *net.IPNet) error {
|
||||
ip := ipNet.IP.To4()
|
||||
if ip == nil {
|
||||
return fmt.Errorf("invalid IPv4 address")
|
||||
}
|
||||
|
||||
mask, _ := ipNet.Mask.Size()
|
||||
node := t.ipv4Root
|
||||
|
||||
for i := 0; i < mask; i++ {
|
||||
bit := (ip[i/8] >> (7 - uint(i%8))) & 1
|
||||
if node.children[bit] == nil {
|
||||
node.children[bit] = NewTrieNode() // Initialize the child node
|
||||
}
|
||||
node = node.children[bit]
|
||||
}
|
||||
|
||||
node.isLeaf = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CIDRTrie) insertIPv6(ipNet *net.IPNet) error {
|
||||
ip := ipNet.IP.To16()
|
||||
if ip == nil {
|
||||
return fmt.Errorf("invalid IPv6 address")
|
||||
}
|
||||
|
||||
mask, _ := ipNet.Mask.Size()
|
||||
node := t.ipv6Root
|
||||
|
||||
for i := 0; i < mask; i++ {
|
||||
bit := (ip[i/8] >> (7 - uint(i%8))) & 1
|
||||
if node.children[bit] == nil {
|
||||
node.children[bit] = NewTrieNode() // Initialize the child node
|
||||
}
|
||||
node = node.children[bit]
|
||||
}
|
||||
|
||||
node.isLeaf = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CIDRTrie) containsIPv4(ip net.IP) bool {
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
node := t.ipv4Root
|
||||
for i := 0; i < len(ip)*8; i++ {
|
||||
bit := (ip[i/8] >> (7 - uint(i%8))) & 1
|
||||
if node.children[bit] == nil {
|
||||
return false
|
||||
}
|
||||
node = node.children[bit]
|
||||
if node.isLeaf {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return node.isLeaf
|
||||
}
|
||||
|
||||
func (t *CIDRTrie) containsIPv6(ip net.IP) bool {
|
||||
ip = ip.To16()
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add this check to ensure ip is not empty
|
||||
if len(ip) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
node := t.ipv6Root
|
||||
for i := 0; i < len(ip)*8; i++ {
|
||||
bit := (ip[i/8] >> (7 - uint(i%8))) & 1
|
||||
if node.children[bit] == nil {
|
||||
return false
|
||||
}
|
||||
node = node.children[bit]
|
||||
if node.isLeaf {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,75 +5,6 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewCIDRTrie(t *testing.T) {
|
||||
trie := NewCIDRTrie()
|
||||
if trie == nil {
|
||||
t.Fatal("NewCIDRTrie() returned nil")
|
||||
}
|
||||
if trie.ipv4Root == nil {
|
||||
t.Fatal("NewCIDRTrie() created a trie with nil ipv4Root")
|
||||
}
|
||||
if trie.ipv6Root == nil {
|
||||
t.Fatal("NewCIDRTrie() created a trie with nil ipv6Root")
|
||||
}
|
||||
if trie.ipv4Root.children == nil {
|
||||
t.Fatal("NewCIDRTrie() created ipv4Root with nil children map")
|
||||
}
|
||||
if trie.ipv6Root.children == nil {
|
||||
t.Fatal("NewCIDRTrie() created ipv6Root with nil children map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCIDRTrie_Insert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cidr string
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid IPv4 CIDR", "192.168.1.0/24", false},
|
||||
{"valid IPv6 CIDR", "2001:db8::/32", false}, // IPv6 is now supported
|
||||
{"invalid CIDR", "invalid", true},
|
||||
{"invalid IPv4 mask", "192.168.1.0/33", true},
|
||||
{"invalid IPv6 mask", "2001:db8::/129", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
trie := NewCIDRTrie()
|
||||
err := trie.Insert(tt.cidr)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CIDRTrie.Insert() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCIDRTrie_Contains(t *testing.T) {
|
||||
trie := NewCIDRTrie()
|
||||
_ = trie.Insert("192.168.1.0/24")
|
||||
_ = trie.Insert("2001:db8::/32") // Add an IPv6 CIDR
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
want bool
|
||||
}{
|
||||
{"IPv4 in range", "192.168.1.1", true},
|
||||
{"IPv4 out of range", "192.168.2.1", false},
|
||||
{"Invalid IP", "invalid", false},
|
||||
{"IPv6 in range", "2001:db8::1", true},
|
||||
{"IPv6 out of range", "2001:db9::1", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := trie.Contains(tt.ip); got != tt.want {
|
||||
t.Errorf("CIDRTrie.Contains() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRuleCache(t *testing.T) {
|
||||
cache := NewRuleCache()
|
||||
if cache == nil {
|
||||
|
||||
Reference in New Issue
Block a user