Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
369f5cea1a | ||
|
|
bde047f2a3 |
@@ -22,6 +22,8 @@ https://git.eeqj.de/sneak/dnswatcher/issues/104
|
|||||||
|
|
||||||
- 2026-10-01: `/metrics` allows each client address 30 requests a minute,
|
- 2026-10-01: `/metrics` allows each client address 30 requests a minute,
|
||||||
counted before Basic Auth, and answers 429 beyond that (closes #101).
|
counted before Basic Auth, and answers 429 beyond that (closes #101).
|
||||||
|
- 2026-10-01: `script/install-precommit` asks git for the repository's git
|
||||||
|
directory, so `make hooks` also works where `.git` is a file (closes #129).
|
||||||
- 2026-10-01: `TODO.md` brought up to date: open issues listed by URL, every
|
- 2026-10-01: `TODO.md` brought up to date: open issues listed by URL, every
|
||||||
Completed Steps entry cut to at most two lines (closes #146).
|
Completed Steps entry cut to at most two lines (closes #146).
|
||||||
- 2026-10-01: wildcard CORS now applies only to the public routes, not to
|
- 2026-10-01: wildcard CORS now applies only to the public routes, not to
|
||||||
@@ -106,7 +108,5 @@ https://git.eeqj.de/sneak/dnswatcher/issues/104
|
|||||||
- README accuracy sweep: https://git.eeqj.de/sneak/dnswatcher/issues/108
|
- README accuracy sweep: https://git.eeqj.de/sneak/dnswatcher/issues/108
|
||||||
- README sections required by policy:
|
- README sections required by policy:
|
||||||
https://git.eeqj.de/sneak/dnswatcher/issues/173
|
https://git.eeqj.de/sneak/dnswatcher/issues/173
|
||||||
- `script/install-precommit` in a linked worktree:
|
|
||||||
https://git.eeqj.de/sneak/dnswatcher/issues/129
|
|
||||||
- fixed root server order: https://git.eeqj.de/sneak/dnswatcher/issues/138
|
- fixed root server order: https://git.eeqj.de/sneak/dnswatcher/issues/138
|
||||||
- review toward 1.0: https://git.eeqj.de/sneak/dnswatcher/issues/144
|
- review toward 1.0: https://git.eeqj.de/sneak/dnswatcher/issues/144
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -286,13 +287,22 @@ func (m *Middleware) SecurityHeaders() func(http.Handler) http.Handler {
|
|||||||
// trusted proxy cannot get a fresh allowance by sending its own
|
// trusted proxy cannot get a fresh allowance by sending its own
|
||||||
// X-Real-IP or X-Forwarded-For. CanonicalizeIP counts all IPv6
|
// X-Real-IP or X-Forwarded-For. CanonicalizeIP counts all IPv6
|
||||||
// addresses in one /64 as one client, since a client usually holds a
|
// addresses in one /64 as one client, since a client usually holds a
|
||||||
// whole /64.
|
// whole /64. An IPv4 address a proxy reports in IPv6-mapped form
|
||||||
|
// (::ffff:203.0.113.1) is turned back into plain IPv4 first, as every
|
||||||
|
// such address is in the same /64.
|
||||||
func (m *Middleware) MetricsRateLimit() func(http.Handler) http.Handler {
|
func (m *Middleware) MetricsRateLimit() func(http.Handler) http.Handler {
|
||||||
return httprate.LimitBy(
|
return httprate.LimitBy(
|
||||||
metricsRequestLimit,
|
metricsRequestLimit,
|
||||||
metricsRequestWindow,
|
metricsRequestWindow,
|
||||||
func(request *http.Request) (string, error) {
|
func(request *http.Request) (string, error) {
|
||||||
return httprate.CanonicalizeIP(realIP(request)), nil
|
ip := realIP(request)
|
||||||
|
|
||||||
|
addr, err := netip.ParseAddr(ip)
|
||||||
|
if err == nil {
|
||||||
|
ip = addr.Unmap().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return httprate.CanonicalizeIP(ip), nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -443,6 +443,12 @@ func TestMetricsRateLimitKeysOnClientAddress(t *testing.T) {
|
|||||||
trustedProxy, "203.0.113.2",
|
trustedProxy, "203.0.113.2",
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"another client behind the proxy, IPv6-mapped",
|
||||||
|
trustedProxy, "::ffff:203.0.113.1",
|
||||||
|
trustedProxy, "::ffff:203.0.113.2",
|
||||||
|
http.StatusOK,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"same IPv6 /64",
|
"same IPv6 /64",
|
||||||
"[2001:db8::1]:4000", "",
|
"[2001:db8::1]:4000", "",
|
||||||
|
|||||||
@@ -7,7 +7,20 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
hook=".git/hooks/pre-commit"
|
# Stop if this directory is not the top of its own git checkout, for
|
||||||
|
# example a copy inside another repository, whose hook must not be
|
||||||
|
# replaced.
|
||||||
|
if [ "$(git rev-parse --show-toplevel)" != "$ROOT" ]; then
|
||||||
|
echo "install-precommit: $ROOT is not the top of a git checkout" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Ask git for the repository's own git directory: .git is a file, not
|
||||||
|
# a directory, in some checkouts (for example a clone made with
|
||||||
|
# --separate-git-dir). core.hooksPath is deliberately not followed, so
|
||||||
|
# the hook is never written outside this repository.
|
||||||
|
hooks="$(git rev-parse --git-common-dir)/hooks"
|
||||||
|
mkdir -p "$hooks"
|
||||||
|
hook="$hooks/pre-commit"
|
||||||
printf '#!/bin/sh\nset -e\nscript/precommit\n' > "$hook"
|
printf '#!/bin/sh\nset -e\nscript/precommit\n' > "$hook"
|
||||||
chmod +x "$hook"
|
chmod +x "$hook"
|
||||||
echo "pre-commit hook installed: runs script/precommit"
|
echo "pre-commit hook installed: runs script/precommit"
|
||||||
|
|||||||
Reference in New Issue
Block a user