rproxy and servewaf replace the one-process smallwebwaf (closes #172, closes #165)
check / check (push) Successful in 17m6s
check / check (push) Successful in 17m6s
`smallwebwaf` with no command now prints the commands and exits with status 2. The image runs rproxy, or servewaf with SMALLWEBWAF_COMMAND=servewaf. The passing-on code, its settings and its log fields and metric go. internal/proxy keeps the checks, answers, reports and own endpoints; its tests run through the real rproxy. The documents describe the pair. Judgement call: clients.json keeps token_refused, since state files refuse unknown fields. Deviation: script/gitea-requests sets SWWAF_LOG_REQUEST_HEADERS, to label requests. Model: opus-5-5
This commit is contained in:
+12
-92
@@ -5,10 +5,9 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// peerAddress is the address of the request's TCP peer, normally traefik.
|
||||
// peerAddress is the address of the request's TCP peer.
|
||||
func peerAddress(r *http.Request) netip.Addr {
|
||||
addrPort, err := netip.ParseAddrPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
@@ -18,58 +17,24 @@ func peerAddress(r *http.Request) netip.Addr {
|
||||
return addrPort.Addr().Unmap()
|
||||
}
|
||||
|
||||
// clientAddress works out who the client is. A peer outside the trusted
|
||||
// proxies is the client, and what it says in X-Forwarded-For is ignored.
|
||||
// For a peer inside them, X-Forwarded-For is read from the right, and the
|
||||
// first address outside them is the client; if every address in it is
|
||||
// inside, the leftmost is, and with no header, the peer. An entry that is
|
||||
// not an address ends the reading, since nothing to its left can be
|
||||
// believed.
|
||||
func clientAddress(
|
||||
peer netip.Addr, forwardedFor []string, trusted []netip.Prefix,
|
||||
) netip.Addr {
|
||||
client := peer
|
||||
if !isInside(peer, trusted) {
|
||||
return client
|
||||
}
|
||||
|
||||
entries := strings.Split(strings.Join(forwardedFor, ","), ",")
|
||||
for _, entry := range slices.Backward(entries) {
|
||||
addr, err := netip.ParseAddr(strings.TrimSpace(entry))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
client = addr.Unmap()
|
||||
if !isInside(client, trusted) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// requestIDHeader carries the request's id, from traefik and to the app.
|
||||
// requestIDHeader carries the request's id, from rproxy.
|
||||
const requestIDHeader = "X-Request-ID"
|
||||
|
||||
// requestID is the request's id: the one a trusted proxy sent, or a new
|
||||
// random one. A peer outside the trusted proxies did not come through
|
||||
// traefik, so the id it sends is its own claim, and is replaced.
|
||||
func requestID(r *http.Request, peerTrusted bool) string {
|
||||
id := r.Header.Get(requestIDHeader)
|
||||
if !peerTrusted || id == "" {
|
||||
id = rand.Text()
|
||||
// requestID is the request's id: the one a question gives, or for any other
|
||||
// request a new random one.
|
||||
func requestID(r *http.Request, isQuestion bool) string {
|
||||
if isQuestion {
|
||||
return r.Header.Get(requestIDHeader)
|
||||
}
|
||||
|
||||
return id
|
||||
return rand.Text()
|
||||
}
|
||||
|
||||
// scheme is how the client reached traefik, as a trusted proxy says in
|
||||
// X-Forwarded-Proto, or otherwise http, the only scheme smallwebwaf
|
||||
// serves.
|
||||
func scheme(r *http.Request, peerTrusted bool) string {
|
||||
// scheme is how the visitor reached rproxy, or the trusted proxy in front of
|
||||
// it, as a question says in X-Forwarded-Proto, or otherwise http.
|
||||
func scheme(r *http.Request, isQuestion bool) string {
|
||||
proto := r.Header.Get("X-Forwarded-Proto")
|
||||
if !peerTrusted || proto == "" {
|
||||
if !isQuestion || proto == "" {
|
||||
return "http"
|
||||
}
|
||||
|
||||
@@ -95,48 +60,3 @@ func isInside(addr netip.Addr, netblocks []netip.Prefix) bool {
|
||||
return netblock.Contains(addr)
|
||||
})
|
||||
}
|
||||
|
||||
// setForwardedHeaders sets the headers in which the app learns about the
|
||||
// client, so that it sees what it would see from traefik directly. A
|
||||
// trusted proxy's forwarded headers pass on, with the proxy's own address
|
||||
// added to X-Forwarded-For. Those of any other peer are its own claims and
|
||||
// are replaced: X-Forwarded-For names the peer, X-Forwarded-Host the host
|
||||
// it asked for, and X-Forwarded-Proto plain http, which is how it reached
|
||||
// smallwebwaf.
|
||||
func setForwardedHeaders(in, out *http.Request, peer netip.Addr, trusted bool) {
|
||||
forwardedFor := peer.String()
|
||||
|
||||
if trusted {
|
||||
// ReverseProxy removes these from out before Rewrite.
|
||||
for _, name := range []string{"Forwarded", "X-Forwarded-Host", "X-Forwarded-Proto"} {
|
||||
values, ok := in.Header[name]
|
||||
if ok {
|
||||
out.Header[name] = values
|
||||
}
|
||||
}
|
||||
|
||||
prior := in.Header.Values("X-Forwarded-For")
|
||||
if len(prior) > 0 {
|
||||
forwardedFor = strings.Join(prior, ", ") + ", " + forwardedFor
|
||||
}
|
||||
|
||||
out.Header.Set("X-Forwarded-For", forwardedFor)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReverseProxy has removed Forwarded and the three set below; these
|
||||
// are the other headers in which traefik tells the app about the
|
||||
// client and its request.
|
||||
for _, name := range []string{
|
||||
"X-Forwarded-Port", "X-Forwarded-Server", "X-Forwarded-Uri",
|
||||
"X-Forwarded-Method", "X-Forwarded-Prefix", "X-Forwarded-Tls-Client-Cert",
|
||||
"X-Forwarded-Tls-Client-Cert-Info", "X-Real-Ip",
|
||||
} {
|
||||
out.Header.Del(name)
|
||||
}
|
||||
|
||||
out.Header.Set("X-Forwarded-For", forwardedFor)
|
||||
out.Header.Set("X-Forwarded-Host", in.Host)
|
||||
out.Header.Set("X-Forwarded-Proto", "http")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user