This is due to us introducing the web console and the uints needing to be string encoded (because of JavaScript).
150 lines
3.6 KiB
Go
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
|
|
}
|