mirror of
https://github.com/vee1e/krply.git
synced 2026-09-01 09:49:51 +00:00
feat(server): add query api, cli, metrics, audit, and web ui
- grpc query server for events, snapshots, diffs, and replay plans - server metrics, request audit logging, and identity endpoint - krply-server and krply cli entrypoints - dependency-light dark web ui backed by the query api
This commit is contained in:
parent
21f6a56cb9
commit
2e15d84dd6
39 changed files with 5859 additions and 0 deletions
82
internal/audit/audit.go
Normal file
82
internal/audit/audit.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Package audit correlates Kubernetes audit-log entries with events stored in
|
||||
// the journal. Correlation is best-effort: an audit entry is only persisted as
|
||||
// a TypeAuditCorrelation record when it matches an already-recorded watch
|
||||
// event on (cluster, namespace, name, uid, resourceVersion).
|
||||
package audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/krply/krply/internal/event"
|
||||
"github.com/krply/krply/internal/storage"
|
||||
)
|
||||
|
||||
// ErrNoMatch is returned when no stored event matches an audit entry.
|
||||
var ErrNoMatch = errors.New("no matching journal event")
|
||||
|
||||
// AuditEvent is a normalized view of one Kubernetes audit-log entry.
|
||||
// Resource is the plural resource name (e.g. "configmaps").
|
||||
type AuditEvent struct {
|
||||
ClusterID string
|
||||
RequestID string
|
||||
Verb string
|
||||
Resource string
|
||||
Namespace string
|
||||
Name string
|
||||
UID string
|
||||
ResourceVersion string
|
||||
User string
|
||||
UserAgent string
|
||||
SourceIPs []string
|
||||
ResponseCode int
|
||||
Stage string
|
||||
Object json.RawMessage
|
||||
ResponseObject json.RawMessage
|
||||
Annotations map[string]string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// Correlator matches audit entries against stored events and writes
|
||||
// TypeAuditCorrelation records into a Store.
|
||||
type Correlator struct {
|
||||
store storage.Store
|
||||
}
|
||||
|
||||
// NewCorrelator returns a Correlator backed by the given Store.
|
||||
func NewCorrelator(store storage.Store) *Correlator {
|
||||
return &Correlator{store: store}
|
||||
}
|
||||
|
||||
// Match searches the store for an EVENT record whose (ClusterID,
|
||||
// Resource.Namespace, Resource.Name, Resource.UID) equal the audit event's and
|
||||
// whose Resource.ResourceVersion equals evt.ResourceVersion. It returns the
|
||||
// matched record's EventID, or ErrNoMatch when no event matches.
|
||||
func (c *Correlator) Match(ctx context.Context, evt AuditEvent) (string, error) {
|
||||
rec, err := c.matchRecord(ctx, evt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return rec.EventID, nil
|
||||
}
|
||||
|
||||
func (c *Correlator) matchRecord(ctx context.Context, evt AuditEvent) (*event.Record, error) {
|
||||
recs, err := c.store.Events(ctx, storage.EventFilter{
|
||||
ClusterID: evt.ClusterID,
|
||||
Namespace: evt.Namespace,
|
||||
Name: evt.Name,
|
||||
RecordType: event.TypeEvent,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range recs {
|
||||
r := &recs[i]
|
||||
if r.Resource.UID == evt.UID && r.Resource.ResourceVersion == evt.ResourceVersion {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoMatch
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue