forked from siderolabs/omni
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR implements audit logs. To enable it you have to set the `--audit-log-dir` flag to a directory where the audit logs will be stored. The audit logs are stored in a JSON format. Example: ```json {"event_type":"update","resource_type":"PublicKeys.omni.sidero.dev","event_ts":1722537710182,"event_data":{"user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36","ip_address":"<snip>","user_id":"a19a7a38-1793-4262-a9ef-97bc00c7a155","role":"Admin","email":"[email protected]","confirmation_type":"auth0","fingerprint":"15acb974f769bdccd38a4b28f282b78736b80bc7","public_key_expiration":1722565909}} ``` Keep in mind that `event_ts` are in milliseconds instead of seconds. Field `event_data` contains all relevant information about the event. To enabled it in the development environment you will have to add the `--audit-log-dir /tmp/omni-data/audit-logs` line to `docker-compose.override.yml` or run `generate-certs` again. For siderolabs#37 Signed-off-by: Dmitriy Matrenichev <[email protected]>
- Loading branch information
Showing
22 changed files
with
674 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
// Copyright (c) 2024 Sidero Labs, Inc. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the LICENSE file. | ||
|
||
package audit | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
|
||
"github.com/cosi-project/runtime/pkg/resource" | ||
"github.com/siderolabs/gen/pair" | ||
) | ||
|
||
// Check is a function that checks if the event is allowed. | ||
type Check = func(ctx context.Context, eventType EventType, args ...any) bool | ||
|
||
// Gate is a gate that checks if the event is allowed. | ||
// | ||
//nolint:govet | ||
type Gate struct { | ||
mu sync.RWMutex | ||
fns [10]map[resource.Type]Check | ||
} | ||
|
||
// Check checks if the event is allowed. | ||
func (g *Gate) Check(ctx context.Context, eventType EventType, typ resource.Type, args ...any) bool { | ||
fn := g.check(eventType, typ) | ||
if fn == nil { | ||
return false | ||
} | ||
|
||
return fn(ctx, eventType, args...) | ||
} | ||
|
||
func (g *Gate) check(eventType EventType, typ resource.Type) Check { | ||
g.mu.RLock() | ||
defer g.mu.RUnlock() | ||
|
||
if g.fns[0] == nil { | ||
return nil | ||
} | ||
|
||
for i, e := range allEvents { | ||
if eventType == e.typ { | ||
return g.fns[i][typ] | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// AddChecks adds checks for the event types. It's allowed to pass several at once using bitwise OR. | ||
func (g *Gate) AddChecks(eventTypes EventType, pairs []pair.Pair[resource.Type, Check]) { | ||
g.mu.Lock() | ||
defer g.mu.Unlock() | ||
|
||
if g.fns[0] == nil { | ||
for i := range g.fns { | ||
g.fns[i] = map[resource.Type]Check{} | ||
} | ||
} | ||
|
||
for _, p := range pairs { | ||
g.addCheck(eventTypes, p) | ||
} | ||
} | ||
|
||
func (g *Gate) addCheck(eventTypes EventType, p pair.Pair[resource.Type, Check]) { | ||
for i, e := range allEvents { | ||
if e.typ&eventTypes != 0 { | ||
if _, ok := g.fns[i][p.F1]; ok { | ||
panic("duplicate check") | ||
} | ||
|
||
g.fns[i][p.F1] = p.F2 | ||
} | ||
} | ||
} | ||
|
||
// AllowAll is a check that allows all events for certain event type. | ||
func AllowAll(context.Context, EventType, ...any) bool { | ||
return true | ||
} | ||
|
||
const ( | ||
// EventGet is the get event type. | ||
EventGet EventType = 1 << iota | ||
// EventList is the list event type. | ||
EventList | ||
// EventCreate is the create event type. | ||
EventCreate | ||
// EventUpdate is the update event type. | ||
EventUpdate | ||
// EventDestroy is the destroy event type. | ||
EventDestroy | ||
// EventWatch is the watch event type. | ||
EventWatch | ||
// EventWatchKind is the watch kind event type. | ||
EventWatchKind | ||
// EventWatchKindAggregated is the watch kind aggregated event type. | ||
EventWatchKindAggregated | ||
// EventUpdateWithConflicts is the update with conflicts event type. | ||
EventUpdateWithConflicts | ||
// EventWatchFor is the watch for event type. | ||
EventWatchFor | ||
) | ||
|
||
// EventType represents the type of event. | ||
type EventType int | ||
|
||
// MarshalJSON marshals the event type to JSON. | ||
func (e *EventType) MarshalJSON() ([]byte, error) { | ||
return []byte(`"` + e.String() + `"`), nil | ||
} | ||
|
||
// String returns the string representation of the event type. | ||
func (e *EventType) String() string { | ||
for _, ev := range allEvents { | ||
if *e == ev.typ { | ||
return ev.str | ||
} | ||
} | ||
|
||
return "<unknown>" | ||
} | ||
|
||
var allEvents = []struct { | ||
str string | ||
typ EventType | ||
}{ | ||
{"get", EventGet}, | ||
{"list", EventList}, | ||
{"create", EventCreate}, | ||
{"update", EventUpdate}, | ||
{"destroy", EventDestroy}, | ||
{"watch", EventWatch}, | ||
{"watch_kind", EventWatchKind}, | ||
{"watch_kind_aggregated", EventWatchKindAggregated}, | ||
{"update_with_conflicts", EventUpdateWithConflicts}, | ||
{"watch_for", EventWatchFor}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Copyright (c) 2024 Sidero Labs, Inc. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the LICENSE file. | ||
|
||
package audit | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/cosi-project/runtime/pkg/resource" | ||
"github.com/siderolabs/gen/pair" | ||
"go.uber.org/zap" | ||
|
||
"github.com/siderolabs/omni/internal/pkg/ctxstore" | ||
) | ||
|
||
// NewLogger creates a new audit logger. | ||
func NewLogger(auditLogDir string, logger *zap.Logger) (*Logger, error) { | ||
err := os.MkdirAll(auditLogDir, 0o755) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create audit logger: %w", err) | ||
} | ||
|
||
return &Logger{ | ||
logFile: NewLogFile(auditLogDir), | ||
logger: logger, | ||
}, nil | ||
} | ||
|
||
// Logger logs audit events. | ||
type Logger struct { | ||
gate Gate | ||
logFile *LogFile | ||
logger *zap.Logger | ||
} | ||
|
||
// LogEvent logs an audit event. | ||
func (l *Logger) LogEvent(ctx context.Context, eventType EventType, resType resource.Type, args ...any) { | ||
if !l.gate.Check(ctx, eventType, resType, args...) { | ||
return | ||
} | ||
|
||
value, ok := ctxstore.Value[*Data](ctx) | ||
if !ok { | ||
return | ||
} | ||
|
||
err := l.logFile.Dump(&event{ | ||
Type: eventType, | ||
ResourceType: resType, | ||
Time: time.Now().UnixMilli(), | ||
Data: value, | ||
}) | ||
if err == nil { | ||
return | ||
} | ||
|
||
l.logger.Error("failed to dump audit log", zap.Error(err)) | ||
} | ||
|
||
// ShoudLog adds checks that allow event type to be logged. | ||
func (l *Logger) ShoudLog(eventType EventType, p ...pair.Pair[resource.Type, Check]) { | ||
l.gate.AddChecks(eventType, p) | ||
} | ||
|
||
//nolint:govet | ||
type event struct { | ||
Type EventType `json:"event_type,omitempty"` | ||
ResourceType resource.Type `json:"resource_type,omitempty"` | ||
Time int64 `json:"event_ts,omitempty"` | ||
Data *Data `json:"event_data,omitempty"` | ||
} |
Oops, something went wrong.