snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
149
server/pkg/actionlog/service.go
Normal file
149
server/pkg/actionlog/service.go
Normal file
@@ -0,0 +1,149 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user