types: add MarshalZerologObject to domain types
Implement zerolog.LogObjectMarshaler interface on domain types for structured logging: - Node: logs node.id, node.name, machine.key (short), node.key (short), node.is_tagged, node.expired, node.online, node.tags, user.name - User: logs user.id, user.name, user.display, user.provider - PreAuthKey: logs pak.id, pak.prefix (masked), pak.reusable, pak.ephemeral, pak.used, pak.is_tagged, pak.tags - APIKey: logs api_key.id, api_key.prefix (masked), api_key.expiration Security: PreAuthKey and APIKey only log masked prefixes, never full keys or hashes. Uses zf.* constants for consistent field naming.
This commit is contained in:
parent
58020696fe
commit
cf3d30b6f6
@ -4,6 +4,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
|
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,3 +56,33 @@ func (key *APIKey) Proto() *v1.ApiKey {
|
|||||||
|
|
||||||
return &protoKey
|
return &protoKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maskedPrefix returns the API key prefix in masked format for safe logging.
|
||||||
|
// SECURITY: Never log the full key or hash, only the masked prefix.
|
||||||
|
func (k *APIKey) maskedPrefix() string {
|
||||||
|
if len(k.Prefix) == NewAPIKeyPrefixLength {
|
||||||
|
return "hskey-api-" + k.Prefix + "-***"
|
||||||
|
}
|
||||||
|
|
||||||
|
return k.Prefix + "***"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalZerologObject implements zerolog.LogObjectMarshaler for safe logging.
|
||||||
|
// SECURITY: This method intentionally does NOT log the full key or hash.
|
||||||
|
// Only the masked prefix is logged for identification purposes.
|
||||||
|
func (k *APIKey) MarshalZerologObject(e *zerolog.Event) {
|
||||||
|
if k == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Uint64(zf.APIKeyID, k.ID)
|
||||||
|
e.Str(zf.APIKeyPrefix, k.maskedPrefix())
|
||||||
|
|
||||||
|
if k.Expiration != nil {
|
||||||
|
e.Time(zf.APIKeyExpiration, *k.Expiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.LastSeen != nil {
|
||||||
|
e.Time(zf.APIKeyLastSeen, *k.LastSeen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import (
|
|||||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/juanfont/headscale/hscontrol/policy/matcher"
|
"github.com/juanfont/headscale/hscontrol/policy/matcher"
|
||||||
"github.com/juanfont/headscale/hscontrol/util"
|
"github.com/juanfont/headscale/hscontrol/util"
|
||||||
|
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
@ -487,6 +489,36 @@ func (node *Node) String() string {
|
|||||||
return node.Hostname
|
return node.Hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalZerologObject implements zerolog.LogObjectMarshaler for safe logging.
|
||||||
|
// This method is used with zerolog's EmbedObject() for flat field embedding
|
||||||
|
// or Object() for nested logging when multiple nodes are logged.
|
||||||
|
func (node *Node) MarshalZerologObject(e *zerolog.Event) {
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Uint64(zf.NodeID, node.ID.Uint64())
|
||||||
|
e.Str(zf.NodeName, node.Hostname)
|
||||||
|
e.Str(zf.MachineKey, node.MachineKey.ShortString())
|
||||||
|
e.Str(zf.NodeKey, node.NodeKey.ShortString())
|
||||||
|
e.Bool(zf.NodeIsTagged, node.IsTagged())
|
||||||
|
e.Bool(zf.NodeExpired, node.IsExpired())
|
||||||
|
|
||||||
|
if node.IsOnline != nil {
|
||||||
|
e.Bool(zf.NodeOnline, *node.IsOnline)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(node.Tags) > 0 {
|
||||||
|
e.Strs(zf.NodeTags, node.Tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.User != nil {
|
||||||
|
e.Str(zf.UserName, node.User.Username())
|
||||||
|
} else if node.UserID != nil {
|
||||||
|
e.Uint(zf.UserID, *node.UserID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PeerChangeFromMapRequest takes a MapRequest and compares it to the node
|
// PeerChangeFromMapRequest takes a MapRequest and compares it to the node
|
||||||
// to produce a PeerChange struct that can be used to updated the node and
|
// to produce a PeerChange struct that can be used to updated the node and
|
||||||
// inform peers about smaller changes to the node.
|
// inform peers about smaller changes to the node.
|
||||||
@ -719,6 +751,16 @@ func (node Node) DebugString() string {
|
|||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalZerologObject implements zerolog.LogObjectMarshaler for NodeView.
|
||||||
|
// This delegates to the underlying Node's implementation.
|
||||||
|
func (nv NodeView) MarshalZerologObject(e *zerolog.Event) {
|
||||||
|
if !nv.Valid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nv.ж.MarshalZerologObject(e)
|
||||||
|
}
|
||||||
|
|
||||||
// Owner returns the owner for display purposes.
|
// Owner returns the owner for display purposes.
|
||||||
// For tagged nodes, returns TaggedDevices. For user-owned nodes, returns the user.
|
// For tagged nodes, returns TaggedDevices. For user-owned nodes, returns the user.
|
||||||
func (nv NodeView) Owner() UserView {
|
func (nv NodeView) Owner() UserView {
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
|
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
@ -120,19 +122,10 @@ func (pak *PreAuthKey) Validate() error {
|
|||||||
return PAKError("invalid authkey")
|
return PAKError("invalid authkey")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use EmbedObject for safe logging - never log full key
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Caller().
|
Caller().
|
||||||
Str("key", pak.Key).
|
EmbedObject(pak).
|
||||||
Bool("hasExpiration", pak.Expiration != nil).
|
|
||||||
Time("expiration", func() time.Time {
|
|
||||||
if pak.Expiration != nil {
|
|
||||||
return *pak.Expiration
|
|
||||||
}
|
|
||||||
return time.Time{}
|
|
||||||
}()).
|
|
||||||
Time("now", time.Now()).
|
|
||||||
Bool("reusable", pak.Reusable).
|
|
||||||
Bool("used", pak.Used).
|
|
||||||
Msg("PreAuthKey.Validate: checking key")
|
Msg("PreAuthKey.Validate: checking key")
|
||||||
|
|
||||||
if pak.Expiration != nil && pak.Expiration.Before(time.Now()) {
|
if pak.Expiration != nil && pak.Expiration.Before(time.Now()) {
|
||||||
@ -156,3 +149,45 @@ func (pak *PreAuthKey) Validate() error {
|
|||||||
func (pak *PreAuthKey) IsTagged() bool {
|
func (pak *PreAuthKey) IsTagged() bool {
|
||||||
return len(pak.Tags) > 0
|
return len(pak.Tags) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maskedPrefix returns the key prefix in masked format for safe logging.
|
||||||
|
// SECURITY: Never log the full key or hash, only the masked prefix.
|
||||||
|
func (pak *PreAuthKey) maskedPrefix() string {
|
||||||
|
if pak.Prefix != "" {
|
||||||
|
return "hskey-auth-" + pak.Prefix + "-***"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalZerologObject implements zerolog.LogObjectMarshaler for safe logging.
|
||||||
|
// SECURITY: This method intentionally does NOT log the full key or hash.
|
||||||
|
// Only the masked prefix is logged for identification purposes.
|
||||||
|
func (pak *PreAuthKey) MarshalZerologObject(e *zerolog.Event) {
|
||||||
|
if pak == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Uint64(zf.PAKID, pak.ID)
|
||||||
|
e.Bool(zf.PAKReusable, pak.Reusable)
|
||||||
|
e.Bool(zf.PAKEphemeral, pak.Ephemeral)
|
||||||
|
e.Bool(zf.PAKUsed, pak.Used)
|
||||||
|
e.Bool(zf.PAKIsTagged, pak.IsTagged())
|
||||||
|
|
||||||
|
// SECURITY: Only log masked prefix, never full key or hash
|
||||||
|
if masked := pak.maskedPrefix(); masked != "" {
|
||||||
|
e.Str(zf.PAKPrefix, masked)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pak.Tags) > 0 {
|
||||||
|
e.Strs(zf.PAKTags, pak.Tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pak.User != nil {
|
||||||
|
e.Str(zf.UserName, pak.User.Username())
|
||||||
|
}
|
||||||
|
|
||||||
|
if pak.Expiration != nil {
|
||||||
|
e.Time(zf.PAKExpiration, *pak.Expiration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import (
|
|||||||
|
|
||||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/juanfont/headscale/hscontrol/util"
|
"github.com/juanfont/headscale/hscontrol/util"
|
||||||
|
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -194,6 +196,30 @@ func (u *User) Proto() *v1.User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalZerologObject implements zerolog.LogObjectMarshaler for safe logging.
|
||||||
|
func (u *User) MarshalZerologObject(e *zerolog.Event) {
|
||||||
|
if u == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Uint(zf.UserID, u.ID)
|
||||||
|
e.Str(zf.UserName, u.Username())
|
||||||
|
e.Str(zf.UserDisplay, u.Display())
|
||||||
|
|
||||||
|
if u.Provider != "" {
|
||||||
|
e.Str(zf.UserProvider, u.Provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalZerologObject implements zerolog.LogObjectMarshaler for UserView.
|
||||||
|
func (u UserView) MarshalZerologObject(e *zerolog.Event) {
|
||||||
|
if !u.Valid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u.ж.MarshalZerologObject(e)
|
||||||
|
}
|
||||||
|
|
||||||
// JumpCloud returns a JSON where email_verified is returned as a
|
// JumpCloud returns a JSON where email_verified is returned as a
|
||||||
// string "true" or "false" instead of a boolean.
|
// string "true" or "false" instead of a boolean.
|
||||||
// This maps bool to a specific type with a custom unmarshaler to
|
// This maps bool to a specific type with a custom unmarshaler to
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user