mirror of
https://github.com/fosrl/newt.git
synced 2026-10-23 04:59:08 +02:00
Add support for client identities in http requests
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package netstack2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/fosrl/newt/logger"
|
||||
)
|
||||
|
||||
// Header names match the defaults that Badger sets for resources behind
|
||||
// Traefik, so downstream services see the same identity headers regardless of
|
||||
// whether a request came through Pangolin's public proxy or through newt.
|
||||
const (
|
||||
remoteUserIDHeader = "Remote-User-Id"
|
||||
remoteUserHeader = "Remote-User"
|
||||
remoteEmailHeader = "Remote-Email"
|
||||
remoteNameHeader = "Remote-Name"
|
||||
remoteRoleHeader = "Remote-Role"
|
||||
)
|
||||
|
||||
// ClientIdentity describes who is behind a client's tunnel IP. The server
|
||||
// sends one per client that can reach an HTTP resource; the HTTP handler maps
|
||||
// a request's source address to it to set the Remote-* headers. Machine
|
||||
// clients have no user, so only Name and Role are set for them.
|
||||
type ClientIdentity struct {
|
||||
IP string `json:"ip"`
|
||||
UserID string `json:"userId,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
}
|
||||
|
||||
// clientIdentityStore maps client tunnel IPs to their identity.
|
||||
type clientIdentityStore struct {
|
||||
mu sync.RWMutex
|
||||
identities map[netip.Addr]ClientIdentity
|
||||
}
|
||||
|
||||
func parseIdentityAddr(id ClientIdentity) (netip.Addr, bool) {
|
||||
addr, err := netip.ParseAddr(id.IP)
|
||||
if err != nil {
|
||||
logger.Warn("Ignoring client identity with invalid IP %q: %v", id.IP, err)
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
return addr.Unmap(), true
|
||||
}
|
||||
|
||||
// replace swaps in the full set of identities, as sent on every full sync.
|
||||
func (s *clientIdentityStore) replace(ids []ClientIdentity) {
|
||||
m := make(map[netip.Addr]ClientIdentity, len(ids))
|
||||
for _, id := range ids {
|
||||
if addr, ok := parseIdentityAddr(id); ok {
|
||||
m[addr] = id
|
||||
}
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.identities = m
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// upsert adds or overwrites the given identities, keeping all others.
|
||||
func (s *clientIdentityStore) upsert(ids []ClientIdentity) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.identities == nil {
|
||||
s.identities = make(map[netip.Addr]ClientIdentity, len(ids))
|
||||
}
|
||||
for _, id := range ids {
|
||||
if addr, ok := parseIdentityAddr(id); ok {
|
||||
s.identities[addr] = id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lookup returns the identity for the source address of a request
|
||||
// ("ip:port", as found in http.Request.RemoteAddr).
|
||||
func (s *clientIdentityStore) lookup(remoteAddr string) (ClientIdentity, bool) {
|
||||
addrPort, err := netip.ParseAddrPort(remoteAddr)
|
||||
if err != nil {
|
||||
return ClientIdentity{}, false
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
id, ok := s.identities[addrPort.Addr().Unmap()]
|
||||
return id, ok
|
||||
}
|
||||
|
||||
// setRemoteHeaders strips any client-supplied Remote-* headers from out, so a
|
||||
// client can never spoof them, then sets them from the identity of the client
|
||||
// the request came from, if known.
|
||||
func (s *clientIdentityStore) setRemoteHeaders(out http.Header, remoteAddr string) {
|
||||
out.Del(remoteUserIDHeader)
|
||||
out.Del(remoteUserHeader)
|
||||
out.Del(remoteEmailHeader)
|
||||
out.Del(remoteNameHeader)
|
||||
out.Del(remoteRoleHeader)
|
||||
|
||||
id, ok := s.lookup(remoteAddr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
set := func(name, value string) {
|
||||
if value != "" {
|
||||
out.Set(name, value)
|
||||
}
|
||||
}
|
||||
set(remoteUserIDHeader, id.UserID)
|
||||
set(remoteUserHeader, id.Username)
|
||||
set(remoteEmailHeader, id.Email)
|
||||
set(remoteNameHeader, id.Name)
|
||||
set(remoteRoleHeader, id.Role)
|
||||
}
|
||||
Reference in New Issue
Block a user