add api for access log events

This commit is contained in:
pascal
2026-01-29 14:27:57 +01:00
parent f204da0d68
commit 8e0b7b6c25
23 changed files with 745 additions and 189 deletions

View File

@@ -25,6 +25,8 @@ func NewLogger(client gRPCClient) *Logger {
}
type logEntry struct {
ID string
AccountID string
ServiceId string
Host string
Path string
@@ -50,6 +52,8 @@ func (l *Logger) log(ctx context.Context, entry logEntry) {
go func() {
if _, err := l.client.SendAccessLog(context.Background(), &proto.SendAccessLogRequest{
Log: &proto.AccessLog{
LogId: entry.ID,
AccountId: entry.AccountID,
Timestamp: now,
ServiceId: entry.ServiceId,
Host: entry.Host,

View File

@@ -5,6 +5,8 @@ import (
"net/http"
"time"
"github.com/rs/xid"
"github.com/netbirdio/netbird/proxy/internal/auth"
"github.com/netbirdio/netbird/proxy/internal/proxy"
)
@@ -31,8 +33,10 @@ func (l *Logger) Middleware(next http.Handler) http.Handler {
host = r.Host
}
l.log(r.Context(), logEntry{
entry := logEntry{
ID: xid.New().String(),
ServiceId: proxy.ServiceIdFromContext(r.Context()),
AccountID: proxy.AccountIdFromContext(r.Context()),
Host: host,
Path: r.URL.Path,
DurationMs: duration.Milliseconds(),
@@ -42,6 +46,7 @@ func (l *Logger) Middleware(next http.Handler) http.Handler {
AuthMechanism: auth.MethodFromContext(r.Context()).String(),
UserId: auth.UserFromContext(r.Context()),
AuthSuccess: sw.status != http.StatusUnauthorized && sw.status != http.StatusForbidden,
})
}
l.log(r.Context(), entry)
})
}

View File

@@ -2,14 +2,54 @@ package proxy
import (
"context"
"sync"
)
type requestContextKey string
const (
serviceIdKey requestContextKey = "serviceId"
serviceIdKey requestContextKey = "serviceId"
accountIdKey requestContextKey = "accountId"
capturedDataKey requestContextKey = "capturedData"
)
// CapturedData is a mutable struct that allows downstream handlers
// to pass data back up the middleware chain.
type CapturedData struct {
mu sync.RWMutex
ServiceId string
AccountId string
}
// SetServiceId safely sets the service ID
func (c *CapturedData) SetServiceId(serviceId string) {
c.mu.Lock()
defer c.mu.Unlock()
c.ServiceId = serviceId
}
// SetAccountId safely sets the account ID
func (c *CapturedData) SetAccountId(accountId string) {
c.mu.Lock()
defer c.mu.Unlock()
c.AccountId = accountId
}
// WithCapturedData adds a CapturedData struct to the context
func WithCapturedData(ctx context.Context, data *CapturedData) context.Context {
return context.WithValue(ctx, capturedDataKey, data)
}
// CapturedDataFromContext retrieves the CapturedData from context
func CapturedDataFromContext(ctx context.Context) *CapturedData {
v := ctx.Value(capturedDataKey)
data, ok := v.(*CapturedData)
if !ok {
return nil
}
return data
}
func withServiceId(ctx context.Context, serviceId string) context.Context {
return context.WithValue(ctx, serviceIdKey, serviceId)
}
@@ -22,3 +62,15 @@ func ServiceIdFromContext(ctx context.Context) string {
}
return serviceId
}
func withAccountId(ctx context.Context, accountId string) context.Context {
return context.WithValue(ctx, accountIdKey, accountId)
}
func AccountIdFromContext(ctx context.Context) string {
v := ctx.Value(accountIdKey)
accountId, ok := v.(string)
if !ok {
return ""
}
return accountId
}

View File

@@ -26,7 +26,7 @@ func NewReverseProxy(transport http.RoundTripper) *ReverseProxy {
}
func (p *ReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
target, serviceId, exists := p.findTargetForRequest(r)
target, serviceId, accountID, exists := p.findTargetForRequest(r)
if !exists {
// No mapping found so return an error here.
// TODO: prettier error page.
@@ -36,6 +36,8 @@ func (p *ReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Set the serviceId in the context for later retrieval.
ctx := withServiceId(r.Context(), serviceId)
// Set the accountId in the context for later retrieval.
ctx = withAccountId(ctx, accountID)
// Set up a reverse proxy using the transport and then use it to serve the request.
proxy := httputil.NewSingleHostReverseProxy(target)

View File

@@ -8,12 +8,13 @@ import (
)
type Mapping struct {
ID string
Host string
Paths map[string]*url.URL
ID string
AccountID string
Host string
Paths map[string]*url.URL
}
func (p *ReverseProxy) findTargetForRequest(req *http.Request) (*url.URL, string, bool) {
func (p *ReverseProxy) findTargetForRequest(req *http.Request) (*url.URL, string, string, bool) {
p.mappingsMux.RLock()
if p.mappings == nil {
p.mappingsMux.RUnlock()
@@ -21,12 +22,12 @@ func (p *ReverseProxy) findTargetForRequest(req *http.Request) (*url.URL, string
defer p.mappingsMux.Unlock()
p.mappings = make(map[string]Mapping)
// There cannot be any loaded Mappings as we have only just initialized.
return nil, "", false
return nil, "", "", false
}
defer p.mappingsMux.RUnlock()
m, exists := p.mappings[req.Host]
if !exists {
return nil, "", false
return nil, "", "", false
}
// Sort paths by length (longest first) in a naive attempt to match the most specific route first.
@@ -40,10 +41,10 @@ func (p *ReverseProxy) findTargetForRequest(req *http.Request) (*url.URL, string
for _, path := range paths {
if strings.HasPrefix(req.URL.Path, path) {
return m.Paths[path], m.ID, true
return m.Paths[path], m.ID, m.AccountID, true
}
}
return nil, "", false
return nil, "", "", false
}
func (p *ReverseProxy) AddMapping(m Mapping) {

View File

@@ -3,12 +3,14 @@ package roundtrip
import (
"context"
"fmt"
"io"
"net/http"
"sync"
"time"
"github.com/netbirdio/netbird/client/embed"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/embed"
)
const deviceNamePrefix = "ingress-"
@@ -34,6 +36,7 @@ func (n *NetBird) AddPeer(ctx context.Context, domain, key string) error {
DeviceName: deviceNamePrefix + domain,
ManagementURL: n.mgmtAddr,
SetupKey: key,
LogOutput: io.Discard,
})
if err != nil {
return fmt.Errorf("create netbird client: %w", err)

View File

@@ -334,6 +334,7 @@ func (s *Server) protoToMapping(mapping *proto.ProxyMapping) proxy.Mapping {
// TODO: Should we warn management about this so it can be bubbled up to a user to reconfigure?
log.WithFields(log.Fields{
"service_id": mapping.GetId(),
"account_id": mapping.GetAccountId(),
"domain": mapping.GetDomain(),
"path": pathMapping.GetPath(),
"target": pathMapping.GetTarget(),
@@ -343,8 +344,9 @@ func (s *Server) protoToMapping(mapping *proto.ProxyMapping) proxy.Mapping {
paths[pathMapping.GetPath()] = targetURL
}
return proxy.Mapping{
ID: mapping.GetId(),
Host: mapping.GetDomain(),
Paths: paths,
ID: mapping.GetId(),
AccountID: mapping.AccountId,
Host: mapping.GetDomain(),
Paths: paths,
}
}