v0.22.0 several enhancements and fixes for security, network, doc, test, packaging
Test / test (push) Canceled after 0s
Test / e2e (push) Canceled after 0s

This commit is contained in:
lixen committed 2026-10-03 14:52:16 -04:00
1 parent 9ab8c356d2
commit 71d1b82f83
102 files changed
+7102 -2178

No files matched your search

+11 -7
View File
@@ -9,6 +9,7 @@ import (
"maps"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
@@ -103,6 +104,10 @@ func NewHTTPChainSinkPlugin(
if err := lconfig.Port(opts.Port); err != nil {
return nil, fmt.Errorf("port: %w", err)
}
network, err := core.Network(opts.Host)
if err != nil {
return nil, fmt.Errorf("host: %w", err)
}
if opts.IngestPath == "" {
opts.IngestPath = DefaultHTTPChainSinkIngestPath
} else if !strings.HasPrefix(opts.IngestPath, "/") {
@@ -158,10 +163,9 @@ func NewHTTPChainSinkPlugin(
addr := net.JoinHostPort(opts.Host, strconv.FormatInt(opts.Port, 10))
transport := &http.Transport{
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
// IPv4-only, aligns with tcp/http sinks
DialContext: func(ctx context.Context, _, address string) (net.Conn, error) {
d := net.Dialer{}
return d.DialContext(ctx, "tcp4", address)
return d.DialContext(ctx, network, address)
},
MaxIdleConnsPerHost: 2,
IdleConnTimeout: 90 * time.Second,
@@ -172,9 +176,9 @@ func NewHTTPChainSinkPlugin(
// NDJSON POSTs gain nothing from it
}
scheme := "http"
base := url.URL{Scheme: "http", Host: addr} // escapes an IPv6 zone
if tlsCfg != nil {
scheme = "https"
base.Scheme = "https"
}
t := &HTTPChainSink{
@@ -185,8 +189,8 @@ func NewHTTPChainSinkPlugin(
tlsEnabled: tlsCfg != nil,
mtls: tlsCfg != nil && len(tlsCfg.Certificates) > 0,
auth: authPolicy,
baseURL: scheme + "://" + addr,
url: scheme + "://" + addr + opts.IngestPath,
baseURL: base.String(),
url: base.String() + opts.IngestPath,
// A redirect would resend the batch, even from https to plaintext http
client: &http.Client{
Transport: transport,
+74
View File
@@ -1,6 +1,7 @@
package httpchain
import (
"context"
"crypto/tls"
"net"
"net/http"
@@ -16,6 +17,7 @@ import (
"logwisp/internal/config"
"logwisp/internal/core"
"logwisp/internal/session"
ingest "logwisp/internal/source/httpchain"
"logwisp/internal/testutil"
"github.com/lixenwraith/auth"
@@ -126,3 +128,75 @@ func TestRefusedTokenLogsInAgainAndDeliversOnce(t *testing.T) {
t.Fatalf("logins = %v, want 2", logins)
}
}
// The target URL brackets an IPv6 host and escapes its zone: a URL built
// from the raw host could not even form a request.
func TestTargetURLBracketsAnIPv6Host(t *testing.T) {
manager := session.NewManager(time.Hour)
t.Cleanup(manager.Stop)
for host, want := range map[string]string{
"::1": "http://[::1]:9000/ingest",
"fe80::1%eth0": "http://[fe80::1%25eth0]:9000/ingest",
} {
created, err := NewHTTPChainSinkPlugin("fwd", map[string]any{"host": host, "port": int64(9000)},
log.NewLogger(), session.NewProxy(manager, "fwd"))
if err != nil {
t.Fatal(err)
}
if got := created.(*HTTPChainSink).url; got != want {
t.Errorf("host %q: target %q, want %q", host, got, want)
}
}
}
// A link runs over the IPv6 loopback with TLS: the source binds ::1, and the
// sink dials it and verifies the certificate's ::1 address.
func TestLinkOverIPv6LoopbackWithTLS(t *testing.T) {
testutil.RequireIPv6(t)
pki := testutil.NewPKI(t, "edge-01")
probe, err := net.Listen("tcp6", "[::1]:0")
if err != nil {
t.Fatal(err)
}
_, port, _ := net.SplitHostPort(probe.Addr().String())
probe.Close()
p, _ := strconv.ParseInt(port, 10, 64)
manager := session.NewManager(time.Hour)
t.Cleanup(manager.Stop)
src, err := ingest.NewHTTPChainSourcePlugin("in", map[string]any{
"host": "::1", "port": p,
"tls": map[string]any{"enabled": true, "cert_file": pki.ServerCert, "key_file": pki.ServerKey},
}, log.NewLogger(), session.NewProxy(manager, "in"))
if err != nil {
t.Fatal(err)
}
entries := src.Subscribe()
if err := src.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(src.Stop)
created, err := NewHTTPChainSinkPlugin("fwd", map[string]any{
"host": "::1", "port": p,
"tls": map[string]any{"enabled": true, "ca_file": pki.CA},
}, log.NewLogger(), session.NewProxy(manager, "fwd"))
if err != nil {
t.Fatal(err)
}
sink := created.(*HTTPChainSink)
sink.append(core.TransportEvent{Time: time.Now(), Payload: []byte("over IPv6")})
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
if !sink.flush(ctx) || sink.droppedBatches.Load() != 0 {
t.Fatalf("batch not delivered: %d request errors", sink.requestErrors.Load())
}
select {
case e := <-entries:
if e.Message != "over IPv6" {
t.Fatalf("message %q", e.Message)
}
case <-time.After(2 * time.Second):
t.Fatal("the source published nothing")
}
}