151 lines
3.6 KiB
Go
151 lines
3.6 KiB
Go
package actionlog
|
|
|
|
import (
|
|
"context"
|
|
"github.com/cortezaproject/corteza-server/pkg/id"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/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),
|
|
zap.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
|
|
}
|