v0.21.0 scram password auth, cred and token management, browser tls-terminating proxy, viewer client
Test / test (push) Canceled after 0s
Test / test (push) Canceled after 0s
This commit is contained in:
1 parent
f192c26fa2
commit
9ab8c356d2
60 files changed
+6791
-747
No files matched your search
@@ -95,6 +95,8 @@ func NewHTTPChainSourcePlugin(
|
||||
opts.IngestPath = DefaultHTTPChainSourceIngestPath
|
||||
} else if !strings.HasPrefix(opts.IngestPath, "/") {
|
||||
return nil, fmt.Errorf("ingest_path: must start with '/'")
|
||||
} else if opts.IngestPath == chain.AuthPath {
|
||||
return nil, fmt.Errorf("ingest_path: %s is reserved for authentication", chain.AuthPath)
|
||||
}
|
||||
if opts.BufferSize <= 0 {
|
||||
opts.BufferSize = DefaultHTTPChainSourceBufferSize
|
||||
@@ -109,7 +111,7 @@ func NewHTTPChainSourcePlugin(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authPolicy, err := authz.New(opts.Auth, opts.TLS, authz.RoleChainListener)
|
||||
authPolicy, err := authz.New(opts.Auth, tlsCfg, authz.RoleChainListener, authz.HTTP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -163,16 +165,22 @@ func (s *HTTPChainSource) Subscribe() <-chan core.LogEntry {
|
||||
|
||||
// Start binds the listener and serves the ingest endpoint
|
||||
func (s *HTTPChainSource) Start() error {
|
||||
if err := s.auth.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
addr := net.JoinHostPort(s.config.Host, strconv.FormatInt(s.config.Port, 10))
|
||||
// IPv4-only, aligns with tcp/http sinks
|
||||
ln, err := net.Listen("tcp4", addr)
|
||||
if err != nil {
|
||||
s.auth.Close()
|
||||
return fmt.Errorf("listen %s: %w", addr, err)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
// Method-scoped pattern: mux answers 405 with Allow header on non-POST
|
||||
mux.HandleFunc(http.MethodPost+" "+s.config.IngestPath, s.handleIngest)
|
||||
// Answers 404 unless the policy is scram
|
||||
mux.HandleFunc(http.MethodPost+" "+chain.AuthPath, s.handleAuth)
|
||||
|
||||
s.server = &http.Server{
|
||||
Handler: mux,
|
||||
@@ -212,6 +220,7 @@ func (s *HTTPChainSource) Stop() {
|
||||
defer cancel()
|
||||
s.server.Shutdown(ctx)
|
||||
}
|
||||
s.auth.Close()
|
||||
|
||||
s.sessionsMu.Lock()
|
||||
for _, id := range s.sessions {
|
||||
@@ -269,10 +278,9 @@ func (s *HTTPChainSource) handleIngest(w http.ResponseWriter, r *http.Request) {
|
||||
s.totalRequests.Add(1)
|
||||
|
||||
// Authorize before the body is read: an unauthorized sender should not get
|
||||
// to stream max_body_bytes into the process. 403 is distinct from the 400
|
||||
// used for protocol errors, so a sender can tell "not allowed" from
|
||||
// "malformed batch".
|
||||
ident, err := s.auth.Authorize(r.TLS)
|
||||
// to stream max_body_bytes into the process. 401 (log in again) and 403
|
||||
// (not allowed) are distinct from the 400 used for protocol errors.
|
||||
ident, status, err := s.auth.AuthorizeRequest(r)
|
||||
if err != nil {
|
||||
s.rejectedRequests.Add(1)
|
||||
s.logger.Warn("msg", "Request rejected by auth policy",
|
||||
@@ -280,7 +288,7 @@ func (s *HTTPChainSource) handleIngest(w http.ResponseWriter, r *http.Request) {
|
||||
"instance_id", s.id,
|
||||
"remote_addr", r.RemoteAddr,
|
||||
"error", err)
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
authz.Refuse(w, status)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -354,6 +362,25 @@ func (s *HTTPChainSource) handleIngest(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// handleAuth runs one step of a SCRAM login and logs its outcome
|
||||
func (s *HTTPChainSource) handleAuth(w http.ResponseWriter, r *http.Request) {
|
||||
ident, err := s.auth.ServeAuth(w, r)
|
||||
switch {
|
||||
case err != nil:
|
||||
s.logger.Warn("msg", "Login rejected",
|
||||
"component", "http_chain_source",
|
||||
"instance_id", s.id,
|
||||
"remote_addr", r.RemoteAddr,
|
||||
"error", err)
|
||||
case ident.Name != "":
|
||||
s.logger.Info("msg", "Login accepted",
|
||||
"component", "http_chain_source",
|
||||
"instance_id", s.id,
|
||||
"remote_addr", r.RemoteAddr,
|
||||
"auth_identity", ident.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// sessionFor returns the cached session for a remote+node+identity,
|
||||
// recreating after idle expiry. Identity is part of the key so two peers
|
||||
// sharing a remote address never share a session.
|
||||
|
||||
@@ -96,7 +96,7 @@ func NewTCPChainSourcePlugin(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authPolicy, err := authz.New(opts.Auth, opts.TLS, authz.RoleChainListener)
|
||||
authPolicy, err := authz.New(opts.Auth, tlsCfg, authz.RoleChainListener, authz.TCP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,11 +149,15 @@ func (s *TCPChainSource) Subscribe() <-chan core.LogEntry {
|
||||
|
||||
// Start binds the listener and begins accepting connections
|
||||
func (s *TCPChainSource) Start() error {
|
||||
if err := s.auth.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
addr := net.JoinHostPort(s.config.Host, strconv.FormatInt(s.config.Port, 10))
|
||||
// IPv4-only. TLS-wrapped when configured; handshake runs explicitly in
|
||||
// handleConn under tlsx.HandshakeTimeout, pre-hello.
|
||||
ln, err := net.Listen("tcp4", addr)
|
||||
if err != nil {
|
||||
s.auth.Close()
|
||||
return fmt.Errorf("listen %s: %w", addr, err)
|
||||
}
|
||||
if s.tlsConfig != nil {
|
||||
@@ -189,6 +193,7 @@ func (s *TCPChainSource) Stop() {
|
||||
s.mu.Unlock()
|
||||
|
||||
s.wg.Wait()
|
||||
s.auth.Close()
|
||||
|
||||
s.mu.Lock()
|
||||
for _, ch := range s.subscribers {
|
||||
@@ -292,56 +297,51 @@ func (s *TCPChainSource) handleConn(conn net.Conn) {
|
||||
tlsState = &cs
|
||||
}
|
||||
|
||||
// Authorize before a preamble is parsed on an unauthorized peer's behalf
|
||||
ident, err := s.auth.Authorize(tlsState)
|
||||
// Certificates are checked before the hello is read; under scram the hello
|
||||
// carries the exchange. Nothing is acknowledged until node binding agrees.
|
||||
adm, err := s.auth.Admit(conn, tlsState, true, time.Duration(s.config.HelloTimeoutMS)*time.Millisecond)
|
||||
if err != nil {
|
||||
s.rejectedConns.Add(1)
|
||||
s.logger.Warn("msg", "Connection rejected by auth policy",
|
||||
"component", "tcp_chain_source",
|
||||
"instance_id", s.id,
|
||||
"remote_addr", remote,
|
||||
"error", err)
|
||||
return // deferred cleanup closes conn
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(conn)
|
||||
// Oversized line (> MaxLogEntryBytes) is a protocol violation; scanner is
|
||||
// unrecoverable after ErrTooLong, connection terminates
|
||||
scanner.Buffer(make([]byte, 0, 64*1024), core.MaxLogEntryBytes)
|
||||
|
||||
// Hello preamble
|
||||
conn.SetReadDeadline(time.Now().Add(time.Duration(s.config.HelloTimeoutMS) * time.Millisecond))
|
||||
if !scanner.Scan() {
|
||||
s.logger.Warn("msg", "Connection closed before hello",
|
||||
"component", "tcp_chain_source",
|
||||
"remote_addr", remote,
|
||||
"error", scanner.Err())
|
||||
return
|
||||
}
|
||||
hello, err := chain.DecodeHello(scanner.Bytes())
|
||||
if err != nil {
|
||||
s.logger.Warn("msg", "Rejected chain connection",
|
||||
"component", "tcp_chain_source",
|
||||
"remote_addr", remote,
|
||||
"error", err)
|
||||
if errors.Is(err, authz.ErrRefused) {
|
||||
s.rejectedConns.Add(1)
|
||||
s.logger.Warn("msg", "Connection rejected by auth policy",
|
||||
"component", "tcp_chain_source",
|
||||
"instance_id", s.id,
|
||||
"remote_addr", remote,
|
||||
"error", err)
|
||||
} else {
|
||||
s.logger.Warn("msg", "Rejected chain connection",
|
||||
"component", "tcp_chain_source",
|
||||
"remote_addr", remote,
|
||||
"error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ident := adm.Identity
|
||||
|
||||
fallbackNode := remote
|
||||
if host, _, splitErr := net.SplitHostPort(remote); splitErr == nil {
|
||||
fallbackNode = host
|
||||
}
|
||||
connNode, err := s.auth.ResolveNode(hello.Node, fallbackNode, s.config.TrustNode, ident)
|
||||
connNode, err := s.auth.ResolveNode(adm.Hello.Node, fallbackNode, s.config.TrustNode, ident)
|
||||
if err != nil {
|
||||
s.rejectedConns.Add(1)
|
||||
adm.Reject("node label rejected")
|
||||
s.logger.Warn("msg", "Connection rejected by node binding",
|
||||
"component", "tcp_chain_source",
|
||||
"instance_id", s.id,
|
||||
"remote_addr", remote,
|
||||
"declared_node", hello.Node,
|
||||
"declared_node", adm.Hello.Node,
|
||||
"error", err)
|
||||
return
|
||||
}
|
||||
if err := adm.Accept(); err != nil {
|
||||
return
|
||||
}
|
||||
scanner := bufio.NewScanner(adm.Reader)
|
||||
// Oversized line (> MaxLogEntryBytes) is a protocol violation; scanner is
|
||||
// unrecoverable after ErrTooLong, connection terminates
|
||||
scanner.Buffer(make([]byte, 0, 64*1024), core.MaxLogEntryBytes)
|
||||
|
||||
// force relabels every entry, so an edge cannot smuggle a foreign origin
|
||||
// through the per-entry node field either
|
||||
trustEntryNode := s.auth.TrustsEntryNode(s.config.TrustNode)
|
||||
|
||||
Reference in new issue
Block a user