check / check (push) Failing after 1m14s
`smallwebwaf rproxy` reads the first 64 KiB of each body, asks servewaf over HTTP/1.1, and passes what servewaf allows to the app within its own size and time limits. It reports each request servewaf answered. SWRP_DEFAULT_DISPOSITION decides when servewaf gives no answer. A request it cannot put to servewaf as sent, it refuses itself. Duplication: internal/rproxy copies passing-on code from internal/proxy until #172 removes the originals. Rule suppressed: gosec G704 and G705, on servewaf's URL and the visitor's answer. Rule suppressed: paralleltest, on the test of a switch rproxy refuses. Judgement call: the SWRP_ size and time limits take off; SWRP_TIMEOUT_MS does not. Judgement call: every target not starting with one / gets 400. Model: opus-5-5
100 lines
2.4 KiB
Go
100 lines
2.4 KiB
Go
package smallwebwaf
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/config"
|
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
|
"sneak.berlin/go/smallwebwaf/internal/rproxy"
|
|
)
|
|
|
|
// RunRProxy is `smallwebwaf rproxy`: it reads rproxy's settings, then serves
|
|
// requests until ctx is done. Then it gives the requests in progress
|
|
// shutdownTimeout to finish, and the reports being sent SWRP_TIMEOUT_MS. It
|
|
// returns the process's exit status, 1 when rproxy cannot start. Its process
|
|
// messages name the host's name as instance, and it writes those at info or
|
|
// more severe: rproxy has no setting for either.
|
|
func RunRProxy(ctx context.Context, params Params) int {
|
|
hostname, _ := os.Hostname() // "" when the host has no name to give
|
|
processLog := requestlog.NewProcessLogger(params.Stdout, hostname, slog.LevelInfo)
|
|
|
|
cfg, err := config.RProxyFromEnvironment(params.LookupEnv)
|
|
if err != nil {
|
|
processLog.Error("invalid setting", "error", err.Error())
|
|
|
|
return 1
|
|
}
|
|
|
|
server := rproxy.New(rproxy.Params{
|
|
Config: cfg,
|
|
RequestLog: params.Stdout,
|
|
ProcessLog: processLog,
|
|
})
|
|
|
|
listener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", cfg.ListenAddr)
|
|
if err != nil {
|
|
processLog.Error("cannot listen on SWRP_LISTEN_ADDR", "error", err.Error())
|
|
|
|
return 1
|
|
}
|
|
|
|
processLog.Info("starting",
|
|
"version", params.Version,
|
|
"address", listener.Addr().String(),
|
|
"settings", cfg)
|
|
|
|
return serveRProxy(ctx, server, listener, processLog)
|
|
}
|
|
|
|
// serveRProxy serves requests on listener until ctx is done, and then stops as
|
|
// RunRProxy says.
|
|
func serveRProxy(
|
|
ctx context.Context, server *rproxy.Server, listener net.Listener,
|
|
processLog *slog.Logger,
|
|
) int {
|
|
served := make(chan error, 1)
|
|
|
|
go func() {
|
|
served <- server.Serve(listener)
|
|
}()
|
|
|
|
select {
|
|
case err := <-served:
|
|
processLog.Error("serving failed", "error", err.Error())
|
|
|
|
return 1
|
|
case <-ctx.Done():
|
|
}
|
|
|
|
processLog.Info("stopping")
|
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx),
|
|
shutdownTimeout)
|
|
defer cancel()
|
|
|
|
err := server.Shutdown(shutdownCtx)
|
|
if err != nil {
|
|
processLog.Warn("requests still in progress were cut off",
|
|
"error", err.Error())
|
|
|
|
_ = server.Close()
|
|
}
|
|
|
|
err = <-served
|
|
if !errors.Is(err, http.ErrServerClosed) {
|
|
processLog.Error("serving failed", "error", err.Error())
|
|
|
|
return 1
|
|
}
|
|
|
|
server.WaitForReports()
|
|
processLog.Info("stopped")
|
|
|
|
return 0
|
|
}
|