mirror of
https://github.com/fabriziosalmi/caddy-waf.git
synced 2025-12-23 22:27:46 -05:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
8
.github/workflows/build-run-validate.yml
vendored
8
.github/workflows/build-run-validate.yml
vendored
@@ -27,10 +27,10 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt install -y wget git build-essential curl python3 python3-pip
|
||||
|
||||
- name: Install Go 1.24.2
|
||||
- name: Install Go 1.25
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.24.2'
|
||||
go-version: '1.25'
|
||||
|
||||
- name: Clone caddy-waf Repository
|
||||
run: |
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
- name: Download GeoLite2 Country Database
|
||||
run: |
|
||||
cd caddy-waf
|
||||
wget https://git.io/GeoLite2-Country.mmdb
|
||||
wget https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-Country.mmdb
|
||||
|
||||
- name: Validate GeoLite2 Download
|
||||
run: |
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
run: |
|
||||
cd caddy-waf
|
||||
chmod +x caddy
|
||||
./caddy run > caddy_output.log 2>&1 &
|
||||
./caddy run --config test.caddyfile > caddy_output.log 2>&1 &
|
||||
sleep 5
|
||||
|
||||
if ! pgrep -f "caddy run"; then
|
||||
|
||||
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
|
||||
|
||||
17
.github/workflows/test.yml
vendored
17
.github/workflows/test.yml
vendored
@@ -10,7 +10,8 @@ on:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read # for actions/checkout to fetch code
|
||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||
|
||||
env:
|
||||
# https://github.com/actions/setup-go/issues/491
|
||||
@@ -33,10 +34,6 @@ jobs:
|
||||
pull-requests: read
|
||||
actions: write # to allow uploading artifacts and cache
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -51,13 +48,19 @@ jobs:
|
||||
run: |
|
||||
go get -v
|
||||
|
||||
- name: Run linter
|
||||
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout 5m
|
||||
|
||||
- name: Download MaxMindDB binary
|
||||
run: |
|
||||
wget https://git.io/GeoLite2-Country.mmdb
|
||||
wget https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-Country.mmdb
|
||||
|
||||
# Commented bits below were useful to allow the job to continue
|
||||
# even if the tests fail, so we can publish the report separately
|
||||
# For info about set-output, see https://stackoverflow.com/questions/57850553/github-actions-check-steps-status
|
||||
- name: Run tests
|
||||
run: |
|
||||
go test -v -failfast ./...
|
||||
go test -v -failfast ./... -tags=it
|
||||
|
||||
@@ -65,7 +65,7 @@ linters:
|
||||
rules:
|
||||
- linters:
|
||||
- gosec
|
||||
text: G115 # TODO: Either we should fix the issues or nuke the linter if it's bad
|
||||
text: G115 # Excluded due to potential noisy integer overflow warnings
|
||||
- linters:
|
||||
- gosec
|
||||
text: G107 # we aren't calling unknown URL
|
||||
|
||||
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
|
||||
19
README.md
19
README.md
@@ -7,7 +7,7 @@ A robust, highly customizable, and feature-rich **Web Application Firewall (WAF)
|
||||
## 🛡️ Core Protections
|
||||
|
||||
* **Regex-Based Filtering:** Deep URL, data & header inspection using powerful regex rules.
|
||||
* **Blacklisting:** Blocks malicious IPs, domains & optionally TOR exit nodes.
|
||||
* **Blacklisting:** Blocks malicious IPs, domains, ASNs & optionally TOR exit nodes.
|
||||
* **Geo-Blocking:** Restricts access by country using GeoIP.
|
||||
* **Rate Limiting:** Prevents abuse via customizable IP request limits.
|
||||
* **Anomaly Scoring:** Dynamically blocks requests based on cumulative rule matches.
|
||||
@@ -18,11 +18,18 @@ A robust, highly customizable, and feature-rich **Web Application Firewall (WAF)
|
||||
* **Dynamic Config Reloads:** Seamless updates without restarts.
|
||||
* **File Watchers:** Automatic reloads on rule/blacklist changes.
|
||||
* **Observability:** Seamless integration with ELK stack and Prometheus.
|
||||
* **Rules generator**: powered by custom GPT, [try it here](https://chatgpt.com/g/g-677d07dd07e48191b799b9e5d6da7828-caddy-waf-ruler)
|
||||
* **Rules generator**: [available here](https://chatgpt.com/g/g-677d07dd07e48191b799b9e5d6da7828-caddy-waf-ruler)
|
||||
|
||||
_Simple at a glance UI :)_
|
||||

|
||||
|
||||
## Security & Performance (SOTA)
|
||||
* **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
|
||||
44
blacklist.go
44
blacklist.go
@@ -64,27 +64,21 @@ func (bl *BlacklistLoader) LoadDNSBlacklistFromFile(path string, dnsBlacklist ma
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Middleware) isIPBlacklisted(ip string) bool {
|
||||
// Extract IP address without port
|
||||
cleanIP := extractIP(ip, m.logger)
|
||||
func (m *Middleware) isIPBlacklisted(addr string) bool {
|
||||
ip := extractIP(addr)
|
||||
|
||||
addr, err := netip.ParseAddr(cleanIP)
|
||||
if err != nil {
|
||||
m.logger.Warn("Failed to parse IP address for blacklist check",
|
||||
zap.String("ip", ip),
|
||||
zap.String("clean_ip", cleanIP),
|
||||
zap.Error(err),
|
||||
)
|
||||
return false
|
||||
if m.ipBlacklist == nil {
|
||||
m.logger.Error("blacklist", zap.String("IP blacklist", "is nil"))
|
||||
}
|
||||
|
||||
if m.ipBlacklist.Contains(addr) {
|
||||
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", cleanIP)) // Keep existing debug log
|
||||
return true // Indicate that the IP is blacklisted
|
||||
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
|
||||
}
|
||||
|
||||
@@ -122,22 +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.
|
||||
func (bl *BlacklistLoader) LoadIPBlacklistFromFile(path string, ipBlacklist map[string]struct{}) error {
|
||||
bl.logger.Debug("Loading IP blacklist", zap.String("path", path))
|
||||
|
||||
@@ -96,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)
|
||||
|
||||
81
caddywaf.go
81
caddywaf.go
@@ -24,17 +24,17 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
"github.com/phemmer/go-iptrie"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"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"
|
||||
)
|
||||
|
||||
// ==================== Constants and Globals ====================
|
||||
@@ -48,7 +48,7 @@ var (
|
||||
)
|
||||
|
||||
// Add or update the version constant as needed
|
||||
const wafVersion = "v0.0.7" // 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 ====================
|
||||
|
||||
@@ -84,6 +84,7 @@ func (m *Middleware) Provision(ctx caddy.Context) error {
|
||||
m.logger = ctx.Logger(m)
|
||||
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 == "" {
|
||||
@@ -219,21 +220,40 @@ func (m *Middleware) Provision(ctx caddy.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Configure ASN blocking
|
||||
if m.BlockASNs.Enabled {
|
||||
if !fileExists(m.BlockASNs.GeoIPDBPath) {
|
||||
m.logger.Warn("ASN GeoIP database not found. ASN blocking will be disabled", zap.String("path", m.BlockASNs.GeoIPDBPath))
|
||||
} else {
|
||||
reader, err := maxminddb.Open(m.BlockASNs.GeoIPDBPath)
|
||||
if err != nil {
|
||||
m.logger.Error("Failed to load ASN GeoIP database", zap.String("path", m.BlockASNs.GeoIPDBPath), zap.Error(err))
|
||||
} else {
|
||||
m.logger.Info("ASN GeoIP database loaded successfully", zap.String("path", m.BlockASNs.GeoIPDBPath))
|
||||
m.BlockASNs.geoIP = reader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize config and blacklist loaders
|
||||
m.configLoader = NewConfigLoader(m.logger)
|
||||
m.blacklistLoader = NewBlacklistLoader(m.logger)
|
||||
m.geoIPHandler = NewGeoIPHandler(m.logger)
|
||||
m.requestValueExtractor = NewRequestValueExtractor(m.logger, m.RedactSensitiveData)
|
||||
m.requestValueExtractor = NewRequestValueExtractor(m.logger, m.RedactSensitiveData, m.MaxRequestBodySize)
|
||||
|
||||
// Configure GeoIP handler
|
||||
m.geoIPHandler.WithGeoIPCache(m.geoIPCacheTTL)
|
||||
m.geoIPHandler.WithGeoIPLookupFallbackBehavior(m.geoIPLookupFallbackBehavior)
|
||||
|
||||
// Load configuration from Caddyfile
|
||||
dispenser := caddyfile.NewDispenser([]caddyfile.Token{})
|
||||
err = m.configLoader.UnmarshalCaddyfile(dispenser, m)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load config: %w", err)
|
||||
// Initialize TorConfig with default values if not set
|
||||
if m.Tor.TORIPBlacklistFile == "" {
|
||||
m.Tor.TORIPBlacklistFile = "tor_blacklist.txt"
|
||||
}
|
||||
if m.Tor.UpdateInterval == "" {
|
||||
m.Tor.UpdateInterval = "24h"
|
||||
}
|
||||
if m.Tor.RetryInterval == "" {
|
||||
m.Tor.RetryInterval = "5m"
|
||||
}
|
||||
|
||||
// Load IP blacklist
|
||||
@@ -286,17 +306,13 @@ func (m *Middleware) Shutdown(ctx context.Context) error {
|
||||
m.logger.Debug("Logging worker stopped.")
|
||||
|
||||
var firstError error
|
||||
var errorOccurred bool
|
||||
|
||||
// Close GeoIP databases
|
||||
if m.CountryBlacklist.geoIP != nil {
|
||||
m.logger.Debug("Closing country blacklist GeoIP database...")
|
||||
if err := m.CountryBlacklist.geoIP.Close(); err != nil {
|
||||
m.logger.Error("Error encountered while closing country blacklist GeoIP database", zap.Error(err))
|
||||
if !errorOccurred {
|
||||
firstError = fmt.Errorf("error closing country blacklist GeoIP: %w", err)
|
||||
errorOccurred = true
|
||||
}
|
||||
firstError = fmt.Errorf("error closing country blacklist GeoIP: %w", err)
|
||||
} else {
|
||||
m.logger.Debug("Country blacklist GeoIP database closed successfully.")
|
||||
}
|
||||
@@ -320,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 {
|
||||
@@ -329,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
|
||||
})
|
||||
@@ -467,11 +497,7 @@ func (m *Middleware) loadIPBlacklist(path string, blacklistMap iptrie.Trie) erro
|
||||
|
||||
// Convert the map to CIDRTrie
|
||||
for ip := range blacklist {
|
||||
// Add /32 suffix if the IP doesn't have CIDR notation
|
||||
if !strings.Contains(ip, "/") {
|
||||
ip = ip + "/32"
|
||||
}
|
||||
prefix, err := netip.ParsePrefix(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
|
||||
@@ -504,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
|
||||
|
||||
@@ -4,7 +4,7 @@ import "net/http"
|
||||
|
||||
const (
|
||||
geoIPdata = "GeoLite2-Country.mmdb"
|
||||
localIP = "127.0.0.1"
|
||||
localIP = "127.0.0.1:32555"
|
||||
aliCNIP = "47.88.198.38"
|
||||
googleUSIP = "74.125.131.105"
|
||||
googleBRIP = "128.201.228.12"
|
||||
|
||||
27
config.go
27
config.go
@@ -145,6 +145,7 @@ func (cl *ConfigLoader) UnmarshalCaddyfile(d *caddyfile.Dispenser, m *Middleware
|
||||
m.LogFilePath = "debug.json"
|
||||
m.RedactSensitiveData = false
|
||||
m.LogBuffer = 1000
|
||||
m.BlockASNs.Enabled = false // Default to false
|
||||
|
||||
directiveHandlers := map[string]func(d *caddyfile.Dispenser, m *Middleware) error{
|
||||
"metrics_endpoint": cl.parseMetricsEndpoint,
|
||||
@@ -152,6 +153,7 @@ func (cl *ConfigLoader) UnmarshalCaddyfile(d *caddyfile.Dispenser, m *Middleware
|
||||
"rate_limit": cl.parseRateLimit,
|
||||
"block_countries": cl.parseCountryBlockDirective(true), // Use directive-specific helper
|
||||
"whitelist_countries": cl.parseCountryBlockDirective(false), // Use directive-specific helper
|
||||
"block_asns": cl.parseBlockASNsDirective, // Add ASN block directive
|
||||
"log_severity": cl.parseLogSeverity,
|
||||
"log_json": cl.parseLogJSON,
|
||||
"rule_file": cl.parseRuleFile,
|
||||
@@ -300,6 +302,31 @@ func (cl *ConfigLoader) parseCountryBlockDirective(isBlock bool) func(d *caddyfi
|
||||
}
|
||||
}
|
||||
|
||||
// parseBlockASNsDirective handles the block_asns directive
|
||||
func (cl *ConfigLoader) parseBlockASNsDirective(d *caddyfile.Dispenser, m *Middleware) error {
|
||||
target := &m.BlockASNs
|
||||
target.Enabled = true
|
||||
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
target.GeoIPDBPath = d.Val()
|
||||
target.BlockedASNs = []string{}
|
||||
|
||||
for d.NextArg() {
|
||||
asn := d.Val()
|
||||
target.BlockedASNs = append(target.BlockedASNs, asn)
|
||||
}
|
||||
|
||||
cl.logger.Debug("ASN block list configured",
|
||||
zap.Strings("asns", target.BlockedASNs),
|
||||
zap.String("geoip_db_path", target.GeoIPDBPath),
|
||||
zap.String("file", d.File()),
|
||||
zap.Int("line", d.Line()),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cl *ConfigLoader) parseLogSeverity(d *caddyfile.Dispenser, m *Middleware) error {
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
81
geoip.go
81
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)
|
||||
@@ -151,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 {
|
||||
@@ -214,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)
|
||||
|
||||
|
||||
73
go.mod
73
go.mod
@@ -2,8 +2,6 @@ module github.com/fabriziosalmi/caddy-waf
|
||||
|
||||
go 1.25
|
||||
|
||||
toolchain go1.25.3
|
||||
|
||||
require (
|
||||
github.com/caddyserver/caddy/v2 v2.10.2
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
@@ -22,31 +20,37 @@ require (
|
||||
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.4.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.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.25.0 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/ccoveille/go-safecast v1.6.1 // indirect
|
||||
github.com/ccoveille/go-safecast/v2 v2.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.16.0 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/badger v1.6.2 // indirect
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4 // 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-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
@@ -55,16 +59,20 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // 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.6 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // 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.0 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
@@ -78,22 +86,25 @@ require (
|
||||
github.com/mitchellh/reflectwalk v1.0.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.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/prometheus/procfs v0.18.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.55.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/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/slackhq/nebula v1.9.7 // indirect
|
||||
github.com/smallstep/certificates v0.28.4 // indirect
|
||||
github.com/smallstep/cli-utils v0.12.1 // indirect
|
||||
github.com/smallstep/linkedca v0.24.0 // 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
|
||||
@@ -104,36 +115,50 @@ require (
|
||||
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.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.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.step.sm/crypto v0.71.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.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20251009181029-0b7aa0cfb07b // indirect
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect
|
||||
golang.org/x/exp v0.0.0-20251017212417-90e834f514db // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/oauth2 v0.32.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/oauth2 v0.33.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/api v0.252.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251007200510-49b9836ed3ff // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/api v0.256.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.1 // indirect
|
||||
)
|
||||
|
||||
210
go.sum
210
go.sum
@@ -4,8 +4,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
|
||||
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
|
||||
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
|
||||
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
|
||||
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
@@ -14,10 +14,10 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
|
||||
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
|
||||
cloud.google.com/go/kms v1.23.0 h1:WaqAZsUptyHwOo9II8rFC1Kd2I+yvNsNP2IJ14H2sUw=
|
||||
cloud.google.com/go/kms v1.23.0/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g=
|
||||
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||
cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k=
|
||||
cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g=
|
||||
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
|
||||
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
@@ -30,6 +30,7 @@ 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.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=
|
||||
@@ -44,40 +45,48 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
||||
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.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.45.6 h1:Br3kil4j7RPW+7LoLVkYt8SuhIWlg6ylmbmzXJ7PgXY=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.45.6/go.mod h1:FKXkHzw1fJZtg1P1qoAIiwen5thz/cDRTTDCIu8ljxc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8=
|
||||
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
|
||||
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/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=
|
||||
@@ -89,8 +98,10 @@ github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx6
|
||||
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q=
|
||||
github.com/ccoveille/go-safecast v1.6.1/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8=
|
||||
github.com/ccoveille/go-safecast/v2 v2.0.0 h1:+5eyITXAUj3wMjad6cRVJKGnC7vDS55zk0INzJagub0=
|
||||
github.com/ccoveille/go-safecast/v2 v2.0.0/go.mod h1:JIYA4CAR33blIDuE6fSwCp2sz1oOBahXnvmdBhOAABs=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -109,8 +120,8 @@ github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
|
||||
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
@@ -131,6 +142,10 @@ github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/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=
|
||||
@@ -144,8 +159,12 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.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=
|
||||
@@ -175,6 +194,7 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
|
||||
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=
|
||||
@@ -184,8 +204,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA=
|
||||
github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA=
|
||||
github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm-tools v0.4.6 h1:hwIwPG7w4z5eQEBq11gYw8YYr9xXLfBQ/0JsKyq5AJM=
|
||||
github.com/google/go-tpm-tools v0.4.6/go.mod h1:MsVQbJnRhKDfWwf5zgr3cDGpj13P1uLAFF0wMEP/n5w=
|
||||
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
||||
@@ -196,8 +216,8 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/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.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
@@ -205,7 +225,11 @@ github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld
|
||||
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=
|
||||
@@ -225,8 +249,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -283,6 +307,8 @@ github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsU
|
||||
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=
|
||||
@@ -300,12 +326,12 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q
|
||||
github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
|
||||
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/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.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
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=
|
||||
@@ -349,14 +375,14 @@ github.com/slackhq/nebula v1.9.7 h1:v5u46efIyYHGdfjFnozQbRRhMdaB9Ma1SSTcUcE2lfE=
|
||||
github.com/slackhq/nebula v1.9.7/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
||||
github.com/smallstep/certificates v0.28.4 h1:JTU6/A5Xes6m+OsR6fw1RACSA362vJc9SOFVG7poBEw=
|
||||
github.com/smallstep/certificates v0.28.4/go.mod h1:LUqo+7mKZE7FZldlTb0zhU4A0bq4G4+akieFMcTaWvA=
|
||||
github.com/smallstep/cli-utils v0.12.1 h1:D9QvfbFqiKq3snGZ2xDcXEFrdFJ1mQfPHZMq/leerpE=
|
||||
github.com/smallstep/cli-utils v0.12.1/go.mod h1:skV2Neg8qjiKPu2fphM89H9bIxNpKiiRTnX9Q6Lc+20=
|
||||
github.com/smallstep/certificates v0.29.0 h1:f90szTKYTW62bmCc+qE5doGqIGPVxTQb8Ba37e/K8Zs=
|
||||
github.com/smallstep/certificates v0.29.0/go.mod h1:27WI0od6gu84mvE4mYQ/QZGyYwHXvhsiSRNC+y3t+mo=
|
||||
github.com/smallstep/cli-utils v0.12.2 h1:lGzM9PJrH/qawbzMC/s2SvgLdJPKDWKwKzx9doCVO+k=
|
||||
github.com/smallstep/cli-utils v0.12.2/go.mod h1:uCPqefO29goHLGqFnwk0i8W7XJu18X3WHQFRtOm/00Y=
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca h1:VX8L0r8vybH0bPeaIxh4NQzafKQiqvlOn8pmOXbFLO4=
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
|
||||
github.com/smallstep/linkedca v0.24.0 h1:7nQuHLrI7XQVbZUgvNsUiW35mskyK1itsZyboZxor3E=
|
||||
github.com/smallstep/linkedca v0.24.0/go.mod h1:7VovSkUuLpO4sJPUxp25aEo9+3XIcgEEMoj2noEQFcI=
|
||||
github.com/smallstep/linkedca v0.25.0 h1:txT9QHGbCsJq0MhAghBq7qhurGY727tQuqUi+n4BVBo=
|
||||
github.com/smallstep/linkedca v0.25.0/go.mod h1:Q3jVAauFKNlF86W5/RFtgQeyDKz98GL/KN3KG4mJOvc=
|
||||
github.com/smallstep/nosql v0.7.0 h1:YiWC9ZAHcrLCrayfaF+QJUv16I2bZ7KdLC3RpJcnAnE=
|
||||
github.com/smallstep/nosql v0.7.0/go.mod h1:H5VnKMCbeq9QA6SRY5iqPylfxLfYcLwvUff3onQ8+HU=
|
||||
github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA=
|
||||
@@ -408,8 +434,15 @@ 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=
|
||||
@@ -425,8 +458,22 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6
|
||||
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=
|
||||
@@ -435,8 +482,10 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6
|
||||
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.step.sm/crypto v0.71.0 h1:rAvlQMckgRXjgc8QuwxrbExW/jiX+57mHgX0ka5IGrw=
|
||||
go.step.sm/crypto v0.71.0/go.mod h1:pCXPI1/bChFwKY3eU8V29P3d9nwQfqraF/I9f74mNU8=
|
||||
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=
|
||||
@@ -462,13 +511,13 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20251009181029-0b7aa0cfb07b h1:YjNArlzCQB2fDkuKSxMwY1ZUQeRXFIFa23Ov9Wa7TUE=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20251009181029-0b7aa0cfb07b/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
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=
|
||||
@@ -495,14 +544,14 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/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.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
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=
|
||||
@@ -515,8 +564,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -530,6 +579,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -538,8 +588,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -549,8 +599,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -561,8 +611,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/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.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
@@ -585,8 +635,8 @@ gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/api v0.252.0 h1:xfKJeAJaMwb8OC9fesr369rjciQ704AjU/psjkKURSI=
|
||||
google.golang.org/api v0.252.0/go.mod h1:dnHOv81x5RAmumZ7BWLShB/u7JZNeyalImxHmtTHxqw=
|
||||
google.golang.org/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=
|
||||
@@ -598,16 +648,16 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
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-20251007200510-49b9836ed3ff h1:8Zg5TdmcbU8A7CXGjGXF1Slqu/nIFCRaR3S5gT2plIA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251007200510-49b9836ed3ff/go.mod h1:dbWfpVPvW/RqafStmRWBUpMN14puDezDMHxNYiRfQu0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff h1:A90eA31Wq6HOMIQlLfzFwzqGKBTuaVztYu/g8sn+8Zc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251007200510-49b9836ed3ff/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
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.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
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=
|
||||
|
||||
124
handler.go
124
handler.go
@@ -32,7 +32,14 @@ func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next cadd
|
||||
)
|
||||
// Return 500 error to client
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("Internal Server Error"))
|
||||
if _, err := w.Write([]byte("Internal Server Error")); err != nil {
|
||||
m.logger.Error(err.Error(),
|
||||
zap.String("log_id", logID),
|
||||
zap.Any("panic", rec),
|
||||
zap.Stack("stack"),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -175,7 +182,7 @@ func (m *Middleware) handleResponseBodyPhase(recorder *responseRecorder, r *http
|
||||
|
||||
for _, rule := range rules {
|
||||
if rule.regex.MatchString(body) {
|
||||
if m.processRuleMatch(recorder, r, &rule, body, state) {
|
||||
if m.processRuleMatch(recorder, r, &rule, "RESPONSE_BODY", body, state) { // Pass RESPONSE_BODY as target
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -252,7 +259,6 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
)
|
||||
|
||||
if phase == 1 {
|
||||
|
||||
// IP blacklisting - the highest priority
|
||||
m.logger.Debug("Checking for IP blacklisting", zap.String("remote_addr", r.RemoteAddr)) // Added log for checking before to isIPBlacklisted call
|
||||
xForwardedFor := r.Header.Get("X-Forwarded-For")
|
||||
@@ -263,7 +269,7 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
m.logger.Debug("Checking IP blacklist with X-Forwarded-For", zap.String("remote_addr_xff", firstIP), zap.String("r.RemoteAddr", r.RemoteAddr))
|
||||
if m.isIPBlacklisted(firstIP) {
|
||||
m.logger.Debug("Starting IP blacklist phase")
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "ip_blacklist", "ip_blacklist_rule", firstIP,
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "ip_blacklist", "ip_blacklist_rule",
|
||||
zap.String("message", "Request blocked by IP blacklist"),
|
||||
)
|
||||
if m.CustomResponses != nil {
|
||||
@@ -274,12 +280,11 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
} else {
|
||||
m.logger.Debug("X-Forwarded-For header present but empty or invalid")
|
||||
}
|
||||
|
||||
} else {
|
||||
m.logger.Debug("X-Forwarded-For header not present using r.RemoteAddr")
|
||||
if m.isIPBlacklisted(r.RemoteAddr) {
|
||||
m.logger.Debug("Starting IP blacklist phase")
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "ip_blacklist", "ip_blacklist_rule", r.RemoteAddr,
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "ip_blacklist", "ip_blacklist_rule",
|
||||
zap.String("message", "Request blocked by IP blacklist"),
|
||||
)
|
||||
if m.CustomResponses != nil {
|
||||
@@ -292,7 +297,7 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
// DNS blacklisting
|
||||
if m.isDNSBlacklisted(r.Host) {
|
||||
m.logger.Debug("Starting DNS blacklist phase")
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "dns_blacklist", "dns_blacklist_rule", r.Host,
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "dns_blacklist", "dns_blacklist_rule",
|
||||
zap.String("message", "Request blocked by DNS blacklist"),
|
||||
zap.String("host", r.Host),
|
||||
)
|
||||
@@ -305,11 +310,11 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
// Rate limiting
|
||||
if 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
|
||||
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", r.RemoteAddr,
|
||||
m.blockRequest(w, r, state, http.StatusTooManyRequests, "rate_limit", "rate_limit_rule",
|
||||
zap.String("message", "Request blocked by rate limit"),
|
||||
)
|
||||
if m.CustomResponses != nil {
|
||||
@@ -329,14 +334,18 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
r,
|
||||
zap.Error(err),
|
||||
)
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "internal_error", "country_block_rule", r.RemoteAddr,
|
||||
zap.String("message", "Request blocked due to internal error"),
|
||||
)
|
||||
m.logger.Debug("Country whitelisting phase completed - blocked due to error")
|
||||
m.incrementGeoIPRequestsMetric(false) // Increment with false for error
|
||||
return
|
||||
if m.GeoIPFailOpen {
|
||||
m.logger.Warn("GeoIP lookup failed (Whitelist); Failing OPEN")
|
||||
} else {
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "internal_error", "country_block_rule",
|
||||
zap.String("message", "Request blocked due to internal error"),
|
||||
)
|
||||
m.logger.Debug("Country whitelisting phase completed - blocked due to error")
|
||||
m.incrementGeoIPRequestsMetric(false) // Increment with false for error
|
||||
return
|
||||
}
|
||||
} else if !allowed {
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "country_block", "country_block_rule", r.RemoteAddr,
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "country_block", "country_block_rule",
|
||||
zap.String("message", "Request blocked by country"))
|
||||
m.incrementGeoIPRequestsMetric(true) // Increment with true for blocked
|
||||
if m.CustomResponses != nil {
|
||||
@@ -348,6 +357,40 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
m.incrementGeoIPRequestsMetric(false) // Increment with false for no block
|
||||
}
|
||||
|
||||
// ASN Blocking
|
||||
if m.BlockASNs.Enabled {
|
||||
m.logger.Debug("Starting ASN blocking phase")
|
||||
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")
|
||||
@@ -357,15 +400,18 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
r,
|
||||
zap.Error(err),
|
||||
)
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "internal_error", "country_block_rule", r.RemoteAddr,
|
||||
zap.String("message", "Request blocked due to internal error"),
|
||||
)
|
||||
m.logger.Debug("Country blacklisting phase completed - blocked due to error")
|
||||
m.incrementGeoIPRequestsMetric(false) // Increment with false for error
|
||||
return
|
||||
if m.GeoIPFailOpen {
|
||||
m.logger.Warn("GeoIP lookup failed (Blacklist); Failing OPEN")
|
||||
} else {
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "internal_error", "country_block_rule",
|
||||
zap.String("message", "Request blocked due to internal error"),
|
||||
)
|
||||
m.logger.Debug("Country blacklisting phase completed - blocked due to error")
|
||||
m.incrementGeoIPRequestsMetric(false) // Increment with false for error
|
||||
return
|
||||
}
|
||||
} else if blocked {
|
||||
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "country_block", "country_block_rule", r.RemoteAddr,
|
||||
m.blockRequest(w, r, state, http.StatusForbidden, "country_block", "country_block_rule",
|
||||
zap.String("message", "Request blocked by country"))
|
||||
m.incrementGeoIPRequestsMetric(true) // Increment with true for blocked
|
||||
if m.CustomResponses != nil {
|
||||
@@ -388,14 +434,14 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
m.logger.Debug("Starting rule evaluation for phase", zap.Int("phase", phase), zap.Int("rule_count", len(rules)))
|
||||
|
||||
for _, rule := range rules {
|
||||
m.logger.Debug("Processing rule", zap.String("rule_id", string(rule.ID)), zap.Int("target_count", len(rule.Targets)))
|
||||
m.logger.Debug("Processing rule", zap.String("rule_id", rule.ID), zap.Int("target_count", len(rule.Targets)))
|
||||
|
||||
// Use the custom type as the key
|
||||
ctx := context.WithValue(r.Context(), ContextKeyRule("rule_id"), rule.ID)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
for _, target := range rule.Targets {
|
||||
m.logger.Debug("Extracting value for target", zap.String("target", target), zap.String("rule_id", string(rule.ID)))
|
||||
m.logger.Debug("Extracting value for target", zap.String("target", target), zap.String("rule_id", rule.ID))
|
||||
var value string
|
||||
var err error
|
||||
|
||||
@@ -413,42 +459,44 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
if err != nil {
|
||||
m.logger.Debug("Failed to extract value for target, skipping rule for this target",
|
||||
zap.String("target", target),
|
||||
zap.String("rule_id", string(rule.ID)),
|
||||
zap.String("rule_id", rule.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
redactedValue := m.requestValueExtractor.RedactValueIfSensitive(target, value)
|
||||
|
||||
m.logger.Debug("Extracted value",
|
||||
zap.String("rule_id", string(rule.ID)),
|
||||
zap.String("rule_id", rule.ID),
|
||||
zap.String("target", target),
|
||||
zap.String("value", value),
|
||||
zap.String("value", redactedValue),
|
||||
)
|
||||
|
||||
if rule.regex.MatchString(value) {
|
||||
m.logger.Debug("Rule matched",
|
||||
zap.String("rule_id", string(rule.ID)),
|
||||
zap.String("rule_id", rule.ID),
|
||||
zap.String("target", target),
|
||||
zap.String("value", value),
|
||||
zap.String("value", redactedValue),
|
||||
)
|
||||
|
||||
// FIXED: Correctly interpret processRuleMatch return value
|
||||
var shouldContinue bool
|
||||
if phase == 3 || phase == 4 {
|
||||
if recorder, ok := w.(*responseRecorder); ok {
|
||||
shouldContinue = m.processRuleMatch(recorder, r, &rule, value, state)
|
||||
shouldContinue = m.processRuleMatch(recorder, r, &rule, target, value, state)
|
||||
} else {
|
||||
shouldContinue = m.processRuleMatch(w, r, &rule, value, state)
|
||||
shouldContinue = m.processRuleMatch(w, r, &rule, target, value, state)
|
||||
}
|
||||
} else {
|
||||
shouldContinue = m.processRuleMatch(w, r, &rule, value, state)
|
||||
shouldContinue = m.processRuleMatch(w, r, &rule, target, value, state)
|
||||
}
|
||||
|
||||
// If processRuleMatch returned false or state is now blocked, stop processing
|
||||
if !shouldContinue || state.Blocked || state.ResponseWritten {
|
||||
m.logger.Debug("Rule evaluation stopping due to blocking or rule directive",
|
||||
zap.Int("phase", phase),
|
||||
zap.String("rule_id", string(rule.ID)),
|
||||
zap.String("rule_id", rule.ID),
|
||||
zap.Bool("continue", shouldContinue),
|
||||
zap.Bool("blocked", state.Blocked),
|
||||
)
|
||||
@@ -460,9 +508,9 @@ func (m *Middleware) handlePhase(w http.ResponseWriter, r *http.Request, phase i
|
||||
}
|
||||
} else {
|
||||
m.logger.Debug("Rule did not match",
|
||||
zap.String("rule_id", string(rule.ID)),
|
||||
zap.String("rule_id", rule.ID),
|
||||
zap.String("target", target),
|
||||
zap.String("value", value),
|
||||
zap.String("value", redactedValue),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
104
handler_test.go
104
handler_test.go
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -26,8 +27,9 @@ func TestBlockedRequestPhase1_DNSBlacklist(t *testing.T) {
|
||||
dnsBlacklist: map[string]struct{}{
|
||||
"malicious.domain": {},
|
||||
},
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
CustomResponses: customResponse,
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
@@ -60,6 +62,9 @@ func TestBlockedRequestPhase1_DNSBlacklist(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBlockedRequestPhase1_GeoIPBlocking(t *testing.T) {
|
||||
if _, err := os.Stat(geoIPdata); os.IsNotExist(err) {
|
||||
t.Skip("GeoIP database not found, skipping test")
|
||||
}
|
||||
logger, err := zap.NewDevelopment()
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -77,7 +82,8 @@ func TestBlockedRequestPhase1_GeoIPBlocking(t *testing.T) {
|
||||
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
|
||||
geoIP: geoIPBlock,
|
||||
},
|
||||
CustomResponses: customResponse,
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
wlMiddleware := &Middleware{
|
||||
@@ -90,7 +96,8 @@ func TestBlockedRequestPhase1_GeoIPBlocking(t *testing.T) {
|
||||
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
|
||||
geoIP: geoIPBlock,
|
||||
},
|
||||
CustomResponses: customResponse,
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
blackWhiteMw := &Middleware{
|
||||
@@ -109,7 +116,8 @@ func TestBlockedRequestPhase1_GeoIPBlocking(t *testing.T) {
|
||||
GeoIPDBPath: geoIPdata, // Path to a test GeoIP database
|
||||
geoIP: geoIPBlock,
|
||||
},
|
||||
CustomResponses: customResponse,
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -205,9 +213,10 @@ func TestBlockedRequestPhase1_IPBlocking(t *testing.T) {
|
||||
|
||||
t.Run("Allow unblocked CIDR", func(t *testing.T) {
|
||||
middleware := &Middleware{
|
||||
logger: logger,
|
||||
ipBlacklist: blackList,
|
||||
CustomResponses: customResponse,
|
||||
logger: logger,
|
||||
ipBlacklist: blackList,
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -222,9 +231,10 @@ func TestBlockedRequestPhase1_IPBlocking(t *testing.T) {
|
||||
|
||||
t.Run("Blocks blacklisted CIDR", func(t *testing.T) {
|
||||
middleware := &Middleware{
|
||||
logger: logger,
|
||||
ipBlacklist: blackList,
|
||||
CustomResponses: customResponse,
|
||||
logger: logger,
|
||||
ipBlacklist: blackList,
|
||||
CustomResponses: customResponse,
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -260,7 +270,7 @@ func TestHandlePhase_Phase2_NiktoUserAgent(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
CustomResponses: customResponse,
|
||||
}
|
||||
|
||||
@@ -315,7 +325,7 @@ func TestBlockedRequestPhase1_HeaderRegex(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -368,7 +378,7 @@ func TestBlockedRequestPhase1_HeaderRegex_SpecificValue(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -421,7 +431,7 @@ func TestBlockedRequestPhase1_HeaderRegex_CommaSeparatedTargets(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -475,7 +485,7 @@ func TestBlockedRequestPhase1_CombinedConditions(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://bad-host.com", nil)
|
||||
@@ -528,7 +538,7 @@ func TestBlockedRequestPhase1_NoMatch(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -581,7 +591,7 @@ func TestBlockedRequestPhase1_HeaderRegex_EmptyHeader(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -633,7 +643,7 @@ func TestBlockedRequestPhase1_HeaderRegex_MissingHeader(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil) // Header not set
|
||||
@@ -685,7 +695,7 @@ func TestBlockedRequestPhase1_HeaderRegex_ComplexPattern(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -738,7 +748,7 @@ func TestBlockedRequestPhase1_MultiTargetMatch(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -791,7 +801,7 @@ func TestBlockedRequestPhase1_MultiTargetNoMatch(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -845,7 +855,7 @@ func TestBlockedRequestPhase1_URLParameterRegex_NoMatch(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com?param1=good-param-value¶m2=good-value", nil)
|
||||
@@ -905,7 +915,7 @@ func TestBlockedRequestPhase1_MultipleRules(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://bad-host.com", nil)
|
||||
@@ -981,7 +991,7 @@ func TestBlockedRequestPhase2_BodyRegex(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
@@ -1040,7 +1050,7 @@ func TestBlockedRequestPhase2_BodyRegex_JSON(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
@@ -1099,7 +1109,7 @@ func TestBlockedRequestPhase2_BodyRegex_FormURLEncoded(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
@@ -1154,7 +1164,7 @@ func TestBlockedRequestPhase2_BodyRegex_SpecificPattern(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
@@ -1213,7 +1223,7 @@ func TestBlockedRequestPhase2_BodyRegex_NoMatch(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", testURL,
|
||||
@@ -1272,7 +1282,7 @@ func TestBlockedRequestPhase2_BodyRegex_NoMatch_MultipartForm(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
@@ -1340,7 +1350,7 @@ func TestBlockedRequestPhase2_BodyRegex_NoBody(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", testURL, nil)
|
||||
@@ -1386,7 +1396,7 @@ func TestBlockedRequestPhase3_ResponseHeaderRegex_NoMatch(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
mockHandler := func() caddyhttp.Handler {
|
||||
@@ -1442,7 +1452,7 @@ func TestBlockedRequestPhase4_ResponseBodyRegex_EmptyBody(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
mockHandler := func() caddyhttp.Handler {
|
||||
@@ -1498,7 +1508,7 @@ func TestBlockedRequestPhase4_ResponseBodyRegex_NoBody(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
mockHandler := func() caddyhttp.Handler {
|
||||
@@ -1552,7 +1562,7 @@ func TestBlockedRequestPhase3_ResponseHeaderRegex_NoSetCookie(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
mockHandler := func() caddyhttp.Handler {
|
||||
return caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
@@ -1608,7 +1618,7 @@ func TestBlockedRequestPhase1_HeaderRegex_CaseInsensitive(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -1661,7 +1671,7 @@ func TestBlockedRequestPhase1_HeaderRegex_MultipleMatchingHeaders(t *testing.T)
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", testURL, nil)
|
||||
@@ -1747,16 +1757,16 @@ func TestBlockedRequestPhase1_RateLimiting_MultiplePaths(t *testing.T) {
|
||||
middleware := &Middleware{
|
||||
logger: logger,
|
||||
rateLimiter: func() *RateLimiter {
|
||||
rl := &RateLimiter{
|
||||
config: RateLimit{
|
||||
Requests: 1,
|
||||
Window: time.Minute,
|
||||
CleanupInterval: time.Minute,
|
||||
Paths: []string{"/api/v1/.*", "/admin/.*"},
|
||||
MatchAllPaths: false,
|
||||
},
|
||||
requests: make(map[string]map[string]*requestCounter),
|
||||
stopCleanup: make(chan struct{}),
|
||||
config := RateLimit{
|
||||
Requests: 1,
|
||||
Window: time.Minute,
|
||||
CleanupInterval: time.Minute,
|
||||
Paths: []string{"/api/v1/.*", "/admin/.*"},
|
||||
MatchAllPaths: false,
|
||||
}
|
||||
rl, err := NewRateLimiter(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create rate limiter: %v", err)
|
||||
}
|
||||
rl.startCleanup()
|
||||
return rl
|
||||
|
||||
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!")
|
||||
}
|
||||
@@ -129,7 +129,7 @@ func (m *Middleware) redactSensitiveFields(fields []zap.Field) []zap.Field {
|
||||
// prepareLogFields consolidates the logic for preparing log fields, including common fields and log_id.
|
||||
func (m *Middleware) prepareLogFields(r *http.Request, fields []zap.Field) []zap.Field {
|
||||
var logID string
|
||||
var allFields []zap.Field
|
||||
allFields := make([]zap.Field, 0)
|
||||
|
||||
// Initialize with common fields
|
||||
var sourceIP, userAgent, requestMethod, requestPath, queryParams string
|
||||
|
||||
@@ -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)
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
func TestExtractValue(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, true)
|
||||
rve := NewRequestValueExtractor(logger, true, 0)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -144,8 +144,8 @@ func TestRedactValueIfSensitive(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rve := NewRequestValueExtractor(logger, tt.redactSensitive)
|
||||
result := rve.redactValueIfSensitive(tt.target, tt.value)
|
||||
rve := NewRequestValueExtractor(logger, tt.redactSensitive, 0)
|
||||
result := rve.RedactValueIfSensitive(tt.target, tt.value)
|
||||
|
||||
if tt.expectedRedacted && result != "REDACTED" {
|
||||
t.Errorf("Expected REDACTED but got %q", result)
|
||||
@@ -159,7 +159,7 @@ func TestRedactValueIfSensitive(t *testing.T) {
|
||||
|
||||
func TestExtractValue_HeaderCaseInsensitive(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.Header.Set("x-test-header", "test-value")
|
||||
@@ -172,7 +172,7 @@ func TestExtractValue_HeaderCaseInsensitive(t *testing.T) {
|
||||
|
||||
func TestExtractValue_EmptyTarget(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
@@ -184,7 +184,7 @@ func TestExtractValue_EmptyTarget(t *testing.T) {
|
||||
|
||||
func TestExtractValue_Method(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
@@ -196,7 +196,7 @@ func TestExtractValue_Method(t *testing.T) {
|
||||
|
||||
func TestExtractValue_RemoteIP(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.RemoteAddr = localIP
|
||||
@@ -209,7 +209,7 @@ func TestExtractValue_RemoteIP(t *testing.T) {
|
||||
|
||||
func TestExtractValue_Protocol(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.Proto = "HTTP/1.1"
|
||||
@@ -222,7 +222,7 @@ func TestExtractValue_Protocol(t *testing.T) {
|
||||
|
||||
func TestExtractValue_Host(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.Host = "example.com"
|
||||
@@ -235,7 +235,7 @@ func TestExtractValue_Host(t *testing.T) {
|
||||
|
||||
func TestExtractValue_Args(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/?foo=bar&baz=qux", nil)
|
||||
w := httptest.NewRecorder()
|
||||
@@ -247,7 +247,7 @@ func TestExtractValue_Args(t *testing.T) {
|
||||
|
||||
func TestExtractValue_UserAgent(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.Header.Set("User-Agent", "test-agent")
|
||||
@@ -260,7 +260,7 @@ func TestExtractValue_UserAgent(t *testing.T) {
|
||||
|
||||
func TestExtractValue_Path(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/test-path", nil)
|
||||
w := httptest.NewRecorder()
|
||||
@@ -272,7 +272,7 @@ func TestExtractValue_Path(t *testing.T) {
|
||||
|
||||
func TestExtractValue_URI(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/test-path?foo=bar", nil)
|
||||
w := httptest.NewRecorder()
|
||||
@@ -284,7 +284,7 @@ func TestExtractValue_URI(t *testing.T) {
|
||||
|
||||
func TestExtractValue_Body(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
body := bytes.NewBufferString("test body")
|
||||
req := httptest.NewRequest("POST", "/", body)
|
||||
@@ -297,7 +297,7 @@ func TestExtractValue_Body(t *testing.T) {
|
||||
|
||||
func TestExtractValue_Headers(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.Header.Set("X-Test-Header", "test-value")
|
||||
@@ -310,7 +310,7 @@ func TestExtractValue_Headers(t *testing.T) {
|
||||
|
||||
func TestExtractValue_Cookies(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.AddCookie(&http.Cookie{Name: "test-cookie", Value: "test-value"})
|
||||
@@ -323,7 +323,7 @@ func TestExtractValue_Cookies(t *testing.T) {
|
||||
|
||||
func TestExtractValue_UnknownTarget(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
rve := NewRequestValueExtractor(logger, false)
|
||||
rve := NewRequestValueExtractor(logger, false, 0)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
@@ -364,10 +364,11 @@ func newMockLogger() *MockLogger {
|
||||
func TestProcessRuleMatch_HighScore(t *testing.T) {
|
||||
logger := newMockLogger()
|
||||
middleware := &Middleware{
|
||||
logger: logger.Logger,
|
||||
AnomalyThreshold: 100, // High threshold
|
||||
ruleHits: sync.Map{},
|
||||
muMetrics: sync.RWMutex{},
|
||||
logger: logger.Logger,
|
||||
AnomalyThreshold: 100, // High threshold
|
||||
ruleHits: sync.Map{},
|
||||
muMetrics: sync.RWMutex{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger.Logger, false, 0), // Initialize
|
||||
}
|
||||
|
||||
rule := &Rule{
|
||||
@@ -394,7 +395,7 @@ func TestProcessRuleMatch_HighScore(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Test blocking rule with high score
|
||||
shouldContinue := middleware.processRuleMatch(w, req, rule, "value", state)
|
||||
shouldContinue := middleware.processRuleMatch(w, req, rule, "header", "value", state)
|
||||
assert.False(t, shouldContinue)
|
||||
assert.Equal(t, http.StatusForbidden, w.Code)
|
||||
assert.True(t, state.Blocked)
|
||||
@@ -419,7 +420,7 @@ func TestValidateRule_EmptyTargets(t *testing.T) {
|
||||
func TestNewRequestValueExtractor(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
redactSensitiveData := true
|
||||
rve := NewRequestValueExtractor(logger, redactSensitiveData)
|
||||
rve := NewRequestValueExtractor(logger, redactSensitiveData, 0)
|
||||
|
||||
assert.NotNil(t, rve)
|
||||
assert.Equal(t, logger, rve.logger)
|
||||
@@ -447,7 +448,7 @@ func TestConcurrentRuleEvaluation(t *testing.T) {
|
||||
ruleCache: NewRuleCache(),
|
||||
ipBlacklist: iptrie.NewTrie(),
|
||||
dnsBlacklist: map[string]struct{}{},
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false),
|
||||
requestValueExtractor: NewRequestValueExtractor(logger, false, 0),
|
||||
rateLimiter: func() *RateLimiter {
|
||||
rl, err := NewRateLimiter(RateLimit{
|
||||
Requests: 10,
|
||||
|
||||
@@ -18,7 +18,7 @@ func (m *Middleware) allowRequest(state *WAFState) {
|
||||
}
|
||||
|
||||
// blockRequest handles blocking a request and logging the details.
|
||||
func (m *Middleware) blockRequest(recorder http.ResponseWriter, r *http.Request, state *WAFState, statusCode int, reason, ruleID, matchedValue string, fields ...zap.Field) {
|
||||
func (m *Middleware) blockRequest(recorder http.ResponseWriter, r *http.Request, state *WAFState, statusCode int, reason, ruleID string, fields ...zap.Field) {
|
||||
// CRITICAL FIX: Set these flags before any other operations
|
||||
state.Blocked = true
|
||||
state.StatusCode = statusCode
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
2
tor.go
2
tor.go
@@ -135,7 +135,7 @@ func (t *TorConfig) readExistingBlacklist() ([]string, error) {
|
||||
// writeBlacklist writes the updated IP blacklist to the file.
|
||||
func (t *TorConfig) writeBlacklist(ips []string) error {
|
||||
data := strings.Join(ips, "\n")
|
||||
err := os.WriteFile(t.TORIPBlacklistFile, []byte(data), 0o644)
|
||||
err := os.WriteFile(t.TORIPBlacklistFile, []byte(data), 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write IP blacklist file %s: %w", t.TORIPBlacklistFile, err) // Improved error message with filename
|
||||
}
|
||||
|
||||
28
types.go
28
types.go
@@ -5,14 +5,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"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"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
// Package caddywaf is a Caddy module providing web application firewall functionality.
|
||||
@@ -47,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 {
|
||||
@@ -54,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"`
|
||||
@@ -106,6 +119,7 @@ type Middleware struct {
|
||||
AnomalyThreshold int `json:"anomaly_threshold"`
|
||||
CountryBlacklist CountryAccessFilter `json:"country_blacklist"`
|
||||
CountryWhitelist CountryAccessFilter `json:"country_whitelist"`
|
||||
BlockASNs ASNAccessFilter `json:"block_asns"`
|
||||
Rules map[int][]Rule `json:"-"`
|
||||
ipBlacklist *iptrie.Trie `json:"-"`
|
||||
dnsBlacklist map[string]struct{} `json:"-"` // Changed to map[string]struct{}
|
||||
@@ -120,8 +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"`
|
||||
|
||||
Reference in New Issue
Block a user