3
0
Tomaž Jerman 462619f2b9 Change logs to encode uint64 values as strings
This is due to us introducing the web console and the uints needing
to be string encoded (because of JavaScript).
2023-05-24 12:26:01 +02:00

150 lines
3.6 KiB
Go

package actionlog
import (
"context"
"strings"
"time"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/cortezaproject/corteza/server/pkg/auth"
)
type (
service struct {
// where the audit log records are kept
store actionlogStore
// Also write audit events here
tee *zap.Logger
// logger for repository errors
logger *zap.Logger
policy policyMatcher
}
Recorder interface {
Record(context.Context, *Action)
Find(context.Context, Filter) (ActionSet, Filter, error)
}
actionlogStore interface {
SearchActionlogs(ctx context.Context, f Filter) (ActionSet, Filter, error)
CreateActionlog(ctx context.Context, rr ...*Action) error
}
)
// NewService initializes action log service
func NewService(s actionlogStore, logger, tee *zap.Logger, policy policyMatcher) (svc *service) {
if tee == nil {
tee = zap.NewNop()
}
svc = &service{
tee: tee.Named("actionlog"),
logger: logger.Named("actionlog"),
store: s,
policy: policy,
}
return
}
func (svc service) Record(ctx context.Context, a *Action) {
if a == nil {
// nothing to record
return
}
a = enrich(ctx, a)
a.ID = id.Next()
svc.log(a)
if !svc.policy.Match(a) {
// policy does not allow us to record this
return
}
// We want to prevent any abrupt cancelation
// (eg canceled request) that would cause
// auditlog to fail...
ctx = context.Background()
if err := svc.store.CreateActionlog(ctx, a); err != nil {
svc.logger.With(zap.Error(err)).Error("could not record audit event")
}
}
func (svc service) log(a *Action) {
zlf := []zap.Field{
zap.Time("timestamp", a.Timestamp),
zap.String("requestOrigin", a.RequestOrigin),
zap.String("requestID", a.RequestID),
zap.String("actorIPAddr", a.ActorIPAddr),
logger.Uint64("actorID", a.ActorID),
zap.String("resource", a.Resource),
zap.String("action", a.Action),
zap.Uint8("severity", uint8(a.Severity)),
zap.String("error", a.Error),
zap.String("description", a.Description),
zap.Bool("policy-match", svc.policy.Match(a)),
zap.Any("meta", a.Meta),
}
svc.tee.
With(zlf...).
// Skipping 3 callers (the most common stack)
// actionlog.service.log()
// actionlog.service.Record()
// (generated service function)
//
// One exception, access control, that calls Record fn directly,
// without going through generated actionlog helpers
WithOptions(zap.AddCallerSkip(3)).
// This is debug logger and we log all recordings as debug
Debug(a.Description)
}
func (svc service) Find(ctx context.Context, flt Filter) (ActionSet, Filter, error) {
return svc.store.SearchActionlogs(ctx, flt)
}
// Enriches action with additional info (ip, actor id, request id...)
func enrich(ctx context.Context, a *Action) *Action {
if a.Timestamp.IsZero() {
a.Timestamp = time.Now()
}
a.RequestOrigin = RequestOriginFromContext(ctx)
// Relies on chi's middleware to get to the request ID
// This does not hurt us for now.
a.RequestID = middleware.GetReqID(ctx)
// uses pkg/auth to extract stored identity from context
a.ActorID = auth.GetIdentityFromContext(ctx).Identity()
// IP from the request,
// we're splitting by space & colon to remove any additional (proxy) IPs
// and ports from the string
if tmp := strings.SplitN(api.RemoteAddrFromContext(ctx), " ", 2); len(tmp) > 0 {
// split by : (ip:port)
if tmp = strings.SplitN(tmp[0], ":", 2); len(tmp) > 0 {
const maxLen = 16
ipAddr := tmp[0]
if len(ipAddr) > maxLen {
ipAddr = ipAddr[:maxLen-1]
}
a.ActorIPAddr = ipAddr
}
}
return a
}