v0.26.1 per-client connection caps

This commit is contained in:
lixen committed 2026-10-05 02:22:30 -04:00
1 parent ee01cb00fe
commit cdd4fc01a7
36 files changed
+795 -417

No files matched your search

+15 -7
View File
@@ -156,11 +156,15 @@ func NewHTTPSinkPlugin(
if err != nil {
return nil, err
}
authPolicy, err := authz.New(opts.Auth, tlsCfg, authz.RoleListener, authz.HTTP)
kind := netacl.HTTP
if opts.Auth != nil && len(opts.Auth.TrustedProxies) > 0 {
kind = netacl.HTTPProxied
}
aclPolicy, err := netacl.New(opts.ACL, opts.Host, kind, logger, "http_sink", id)
if err != nil {
return nil, err
}
aclPolicy, err := netacl.New(opts.ACL, opts.Host, logger, "http_sink", id)
authPolicy, err := authz.New(opts.Auth, tlsCfg, aclPolicy, authz.RoleListener, authz.HTTP)
if err != nil {
return nil, err
}
@@ -264,14 +268,16 @@ func (h *HTTPSink) serve(ctx context.Context, ln net.Listener) error {
// One wrapper covers stream and status, and keeps the handlers themselves
// unaware of authorization; a nil policy admits every request. Login, the
// browser files and the root sit outside it; in proxy mode everything sits
// behind the proxy gate.
// browser files and the root sit outside it; all but login, which SCRAM
// throttling limits, count in the per-client request rate. In proxy mode
// everything sits behind the proxy gate.
limit := func(next http.Handler) http.Handler { return h.acl.Requests(next, clientAddr) }
outer := http.NewServeMux()
outer.HandleFunc(http.MethodPost+" "+chain.AuthPath, h.handleAuth)
for p, file := range h.web {
outer.Handle(http.MethodGet+" "+p, file)
outer.Handle(http.MethodGet+" "+p, limit(file))
}
outer.Handle("/", h.authMiddleware(mux))
outer.Handle("/", limit(h.authMiddleware(mux)))
var handler http.Handler = outer
if h.auth.BehindProxy() {
handler = h.proxyGate(outer)
@@ -572,6 +578,7 @@ func (h *HTTPSink) armWrite(rc *http.ResponseController) {
// handleStatus provides a JSON status report
func (h *HTTPSink) handleStatus(w http.ResponseWriter, r *http.Request) {
denied, limited := h.acl.Refused()
status := map[string]any{
"service": "LogWisp",
"version": version.Short(),
@@ -599,7 +606,8 @@ func (h *HTTPSink) handleStatus(w http.ResponseWriter, r *http.Request) {
"dropped_writes": h.droppedWrites.Load(),
"rejected_clients": h.rejectedClients.Load(),
"auth_rejected": h.auth.Rejected(),
"acl_denied": h.acl.Denied(),
"acl_denied": denied,
"acl_limited": limited,
},
}
+13 -8
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"embed"
"errors"
"html"
"net/http"
"path"
@@ -12,6 +13,7 @@ import (
"github.com/lixenwraith/logwisp/internal/authz"
"github.com/lixenwraith/logwisp/internal/chain"
"github.com/lixenwraith/logwisp/internal/config"
"github.com/lixenwraith/logwisp/internal/netacl"
)
//go:embed web/*.js web/*.html web/*.css
@@ -101,18 +103,21 @@ func serveWebFile(data []byte, contentType string) http.Handler {
// clientKey carries the forwarded client address from proxyGate
type clientKey struct{}
// proxyGate admits only the trusted proxies, and records the client they
// forward for logs and sessions. ServeAuth and AuthorizeRequest check again.
// proxyGate admits only the trusted proxies, forwarding a client the acl
// rules admit, and records that client for logs, sessions and the request
// rate. ServeAuth and AuthorizeRequest check again.
func (h *HTTPSink) proxyGate(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
addr, err := h.auth.ClientAddr(r)
if err != nil {
h.logger.Warn("msg", "Request refused: not through a trusted proxy over https",
"component", "http_sink",
"instance_id", h.id,
"remote_addr", r.RemoteAddr,
"path", r.URL.Path,
"error", err)
if !errors.Is(err, netacl.ErrDenied) { // reported by netacl, once a minute
h.logger.Warn("msg", "Request refused: not through a trusted proxy over https",
"component", "http_sink",
"instance_id", h.id,
"remote_addr", r.RemoteAddr,
"path", r.URL.Path,
"error", err)
}
authz.Refuse(w, http.StatusForbidden)
return
}
+1 -1
View File
@@ -150,7 +150,7 @@ func NewHTTPChainSinkPlugin(
if err != nil {
return nil, err
}
authPolicy, err := authz.New(opts.Auth, tlsCfg, authz.RoleDialer, authz.HTTP)
authPolicy, err := authz.New(opts.Auth, tlsCfg, nil, authz.RoleDialer, authz.HTTP)
if err != nil {
return nil, err
}
+1 -1
View File
@@ -80,7 +80,7 @@ func TestRefusedTokenLogsInAgainAndDeliversOnce(t *testing.T) {
t.Fatal(err)
}
serverTLS := &tls.Config{Certificates: []tls.Certificate{serverCert}}
listener, err := authz.New(&config.AuthOptions{Type: authz.MethodSCRAM, CredentialsFile: creds}, serverTLS, authz.RoleChainListener, authz.HTTP)
listener, err := authz.New(&config.AuthOptions{Type: authz.MethodSCRAM, CredentialsFile: creds}, serverTLS, nil, authz.RoleChainListener, authz.HTTP)
if err != nil {
t.Fatal(err)
}
+2 -2
View File
@@ -147,11 +147,11 @@ func NewTCPSinkPlugin(
if err != nil {
return nil, err
}
authPolicy, err := authz.New(opts.Auth, tlsCfg, authz.RoleListener, authz.TCP)
aclPolicy, err := netacl.New(opts.ACL, opts.Host, netacl.TCP, logger, "tcp_sink", id)
if err != nil {
return nil, err
}
aclPolicy, err := netacl.New(opts.ACL, opts.Host, logger, "tcp_sink", id)
authPolicy, err := authz.New(opts.Auth, tlsCfg, aclPolicy, authz.RoleListener, authz.TCP)
if err != nil {
return nil, err
}
+1 -1
View File
@@ -138,7 +138,7 @@ func NewTCPChainSinkPlugin(
if err != nil {
return nil, err
}
authPolicy, err := authz.New(opts.Auth, tlsCfg, authz.RoleDialer, authz.TCP)
authPolicy, err := authz.New(opts.Auth, tlsCfg, nil, authz.RoleDialer, authz.TCP)
if err != nil {
return nil, err
}