Files
headscale/hscontrol/util/zlog/hostinfo.go
Kristoffer Dalby 58020696fe zlog: add utility package for safe and consistent logging
Add hscontrol/util/zlog package with:

- zf subpackage: field name constants for compile-time safety
- SafeHostinfo: wrapper that redacts device fingerprinting data
- SafeMapRequest: wrapper that redacts client endpoints

The zf (zerolog fields) subpackage provides short constant names
(e.g., zf.NodeID instead of inline "node.id" strings) ensuring
consistent field naming across all log statements.

Security considerations:
- SafeHostinfo never logs: OSVersion, DeviceModel, DistroName
- SafeMapRequest only logs endpoint counts, not actual IPs
2026-02-06 07:40:29 +01:00

62 lines
2.0 KiB
Go

package zlog
import (
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
"github.com/rs/zerolog"
"tailscale.com/tailcfg"
)
// SafeHostinfo wraps tailcfg.Hostinfo for safe logging.
//
// SECURITY: This wrapper intentionally redacts device fingerprinting data
// that could be used to identify or track specific devices:
// - OSVersion, DeviceModel, DistroName, DistroVersion (device fingerprinting)
// - IPNVersion (client version fingerprinting)
// - Machine, FrontendLogID (device identifiers)
//
// Only safe fields are logged:
// - hostname: The device hostname
// - os: The OS family (e.g., "linux", "windows") without version
// - routable_ips_count: Number of advertised routes (not the actual routes)
// - request_tags: Tags requested by the client
// - derp: Preferred DERP region ID
type SafeHostinfo struct {
hi *tailcfg.Hostinfo
}
// Hostinfo creates a SafeHostinfo wrapper for safe logging.
func Hostinfo(hi *tailcfg.Hostinfo) SafeHostinfo {
return SafeHostinfo{hi: hi}
}
// MarshalZerologObject implements zerolog.LogObjectMarshaler.
func (s SafeHostinfo) MarshalZerologObject(e *zerolog.Event) {
if s.hi == nil {
return
}
// Safe fields only - no device fingerprinting data.
e.Str(zf.Hostname, s.hi.Hostname)
e.Str(zf.OS, s.hi.OS) // OS family only, NOT version
if len(s.hi.RoutableIPs) > 0 {
e.Int(zf.RoutableIPCount, len(s.hi.RoutableIPs))
}
if len(s.hi.RequestTags) > 0 {
e.Strs(zf.RequestTags, s.hi.RequestTags)
}
if s.hi.NetInfo != nil && s.hi.NetInfo.PreferredDERP != 0 {
e.Int(zf.DERP, s.hi.NetInfo.PreferredDERP)
}
// SECURITY: The following fields are intentionally NOT logged:
// - OSVersion, DistroName, DistroVersion, DistroCodeName: device fingerprinting
// - DeviceModel: device fingerprinting
// - IPNVersion: client version fingerprinting
// - Machine, FrontendLogID: device identifiers
// - GoArch, GoArchVar, GoVersion: build environment fingerprinting
// - Userspace, UserspaceRouter: network configuration details
}