Skip to content

Commit

Permalink
chore: implement audit log for several types
Browse files Browse the repository at this point in the history
This commit implements session tracking and log audit for those types:
- [x] auth.PublicKey
- [x] auth.AccessPolicy
- [x] auth.User
- [x] auth.Identity
- [x] omni.Machine
- [x] omni.MachineLabels
- [x] omni.Cluster
- [x] omni.MachineSet (only empty owners for update, log create and delete in all cases)
- [x] omni.MachineSetNode (only empty owners for update, log create and delete in all cases)
- [x] omni.ConfigPatch
- [x] Talos API Access
- [x] Kubernetes API access

Output example:

```
{"event_type":"update","resource_type":"Machines.omni.sidero.dev","event_ts":1723137771180,"event_data":{"session":{"user_agent":"Omni-Internal-Agent"},"machine":{"id":"18cec051-d975-483d-8d43-10ac6421648a","is_connected":true,"management_address":"fdae:41e4:649b:9303:da9b:1ed:a725:c3dd","labels":{"omni.sidero.dev/address":"fdae:41e4:649b:9303:da9b:1ed:a725:c3dd"}}}}
{"event_type":"update","resource_type":"Machines.omni.sidero.dev","event_ts":1723137771180,"event_data":{"session":{"user_agent":"Omni-Internal-Agent"},"machine":{"id":"18cec051-d975-483d-8d43-10ac6421648a","is_connected":true,"management_address":"fdae:41e4:649b:9303:da9b:1ed:a725:c3dd","labels":{"omni.sidero.dev/address":"fdae:41e4:649b:9303:da9b:1ed:a725:c3dd"}}}}
{"event_type":"update","resource_type":"Machines.omni.sidero.dev","event_ts":1723137771181,"event_data":{"session":{"user_agent":"Omni-Internal-Agent"},"machine":{"id":"18cec051-d975-483d-8d43-10ac6421648a","is_connected":true,"management_address":"fdae:41e4:649b:9303:da9b:1ed:a725:c3dd","labels":{"omni.sidero.dev/address":"fdae:41e4:649b:9303:da9b:1ed:a725:c3dd"}}}}
{"event_type":"create","resource_type":"MachineLabels.omni.sidero.dev","event_ts":1723137787549,"event_data":{"session":{"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":"ea002172-b9da-423f-bd1d-b443b8a7b43c","role":"Admin","email":"[email protected]","fingerprint":"da7b997eb68449a12bebc6a3bf4f59beaf167209"},"machine_labels":{"id":"18cec051-d975-483d-8d43-10ac6421648a","labels":{"222":""}}}}
{"event_type":"update","resource_type":"MachineLabels.omni.sidero.dev","event_ts":1723137787553,"event_data":{"session":{"user_agent":"Omni-Internal-Agent"},"machine_labels":{"id":"18cec051-d975-483d-8d43-10ac6421648a","labels":{"222":""}}}}
{"event_type":"update","resource_type":"MachineLabels.omni.sidero.dev","event_ts":1723137811532,"event_data":{"session":{"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":"ea002172-b9da-423f-bd1d-b443b8a7b43c","role":"Admin","email":"[email protected]","fingerprint":"da7b997eb68449a12bebc6a3bf4f59beaf167209"},"machine_labels":{"id":"18cec051-d975-483d-8d43-10ac6421648a","labels":{"222":"","333":""}}}}
{"event_type":"update","resource_type":"MachineLabels.omni.sidero.dev","event_ts":1723137811610,"event_data":{"session":{"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":"ea002172-b9da-423f-bd1d-b443b8a7b43c","role":"Admin","email":"[email protected]","fingerprint":"da7b997eb68449a12bebc6a3bf4f59beaf167209"},"machine_labels":{"id":"18cec051-d975-483d-8d43-10ac6421648a","labels":{"222":"","333":""}}}}
{"event_type":"update","resource_type":"MachineLabels.omni.sidero.dev","event_ts":1723137811611,"event_data":{"session":{"user_agent":"Omni-Internal-Agent"},"machine_labels":{"id":"18cec051-d975-483d-8d43-10ac6421648a","labels":{"222":"","333":""}}}}
{"event_type":"destroy","resource_type":"MachineLabels.omni.sidero.dev","event_ts":1723137811621,"event_data":{"session":{"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":"ea002172-b9da-423f-bd1d-b443b8a7b43c","role":"Admin","email":"[email protected]","fingerprint":"da7b997eb68449a12bebc6a3bf4f59beaf167209"},"machine_labels":{"id":"18cec051-d975-483d-8d43-10ac6421648a","labels":{"222":"","333":""}}}}
{"event_type":"create","resource_type":"Users.omni.sidero.dev","event_ts":1723141793888,"event_data":{"new_user":{"role":"Admin","id":"7903a72c-87af-43b8-94dc-82bd961ab768"},"session":{"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":"ea002172-b9da-423f-bd1d-b443b8a7b43c","role":"Admin","email":"[email protected]","fingerprint":"da7b997eb68449a12bebc6a3bf4f59beaf167209"}}}
{"event_type":"create","resource_type":"Identities.omni.sidero.dev","event_ts":1723141793981,"event_data":{"new_user":{"id":"7903a72c-87af-43b8-94dc-82bd961ab768","email":"[email protected]"},"session":{"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":"ea002172-b9da-423f-bd1d-b443b8a7b43c","role":"Admin","email":"[email protected]","fingerprint":"da7b997eb68449a12bebc6a3bf4f59beaf167209"}}}
```

Closes siderolabs#37

Signed-off-by: Dmitriy Matrenichev <[email protected]>
  • Loading branch information
DmitriyMV committed Aug 12, 2024
1 parent ee73083 commit 8576fbe
Show file tree
Hide file tree
Showing 27 changed files with 1,683 additions and 795 deletions.
2 changes: 1 addition & 1 deletion client/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/blang/semver v3.5.1+incompatible
github.com/cosi-project/runtime v0.5.5
github.com/fatih/color v1.17.0
github.com/gertd/go-pluralize v0.2.1
github.com/google/uuid v1.6.0
github.com/gosuri/uiprogress v0.0.1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
Expand Down Expand Up @@ -50,7 +51,6 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.4 //indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gertd/go-pluralize v0.2.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions cmd/omni/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ var rootCmd = &cobra.Command{
//nolint:gocognit
func runWithState(logger *zap.Logger) func(context.Context, state.State, *virtual.State) error {
return func(ctx context.Context, resourceState state.State, virtualState *virtual.State) error {
auditWrap, auditErr := omni.NewAuditWrap(resourceState, config.Config, logger)
if auditErr != nil {
return auditErr
}

resourceState = auditWrap.WrapState(resourceState)

talosClientFactory := talos.NewClientFactory(resourceState, logger)
prometheus.MustRegister(talosClientFactory)

Expand Down Expand Up @@ -260,6 +267,7 @@ func runWithState(logger *zap.Logger) func(context.Context, state.State, *virtua
rootCmdArgs.keyFile,
rootCmdArgs.certFile,
backend.NewProxyServer(rootCmdArgs.frontendBind, handler, rootCmdArgs.keyFile, rootCmdArgs.certFile),
auditWrap,
logger,
)
if err != nil {
Expand Down
23 changes: 0 additions & 23 deletions internal/backend/grpc/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,11 @@ import (
"github.com/siderolabs/omni/client/api/omni/specs"
"github.com/siderolabs/omni/client/pkg/omni/resources"
authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
"github.com/siderolabs/omni/internal/backend/runtime/omni/audit"
"github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni"
"github.com/siderolabs/omni/internal/pkg/auth"
"github.com/siderolabs/omni/internal/pkg/auth/actor"
"github.com/siderolabs/omni/internal/pkg/auth/role"
"github.com/siderolabs/omni/internal/pkg/config"
"github.com/siderolabs/omni/internal/pkg/ctxstore"
)

const (
Expand Down Expand Up @@ -145,17 +143,6 @@ func (s *authServer) RegisterPublicKey(ctx context.Context, request *authpb.Regi

newPubKey := authres.NewPublicKey(resources.DefaultNamespace, pubKey.id)

auditData, ok := ctxstore.Value[*audit.Data](ctx)
if !ok {
return nil, errors.New("audit data not found")
}

auditData.UserID = userID
auditData.Fingerprint = pubKey.id
auditData.PublicKeyExpiration = pubKey.expiration.Unix()
auditData.Role = pubKeyRole
auditData.Email = email

_, err = safe.StateGet[*authres.PublicKey](ctx, s.state, newPubKey.Metadata())
if state.IsNotFoundError(err) {
setPubKeyAttributes(newPubKey)
Expand Down Expand Up @@ -249,16 +236,6 @@ func (s *authServer) ConfirmPublicKey(ctx context.Context, request *authpb.Confi
return nil, errors.New("public key <> id mismatch")
}

auditData, ok := ctxstore.Value[*audit.Data](ctx)
if !ok {
return nil, errors.New("audit data not found")
}

auditData.UserID = userID
auditData.Fingerprint = pubKey.Metadata().ID()
auditData.PublicKeyExpiration = pubKey.TypedSpec().Value.Expiration.Seconds
auditData.Role = role.Role(pubKey.TypedSpec().Value.GetRole())

_, err = safe.StateUpdateWithConflicts(ctx, s.state, pubKey.Metadata(), func(pk *authres.PublicKey) error {
pk.TypedSpec().Value.Confirmed = true

Expand Down
23 changes: 17 additions & 6 deletions internal/backend/grpc/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ const (
talosBackendTTL = time.Hour
)

// TalosAuditor is an interface for auditing Talos access.
type TalosAuditor interface {
AuditTalosAccess(context.Context, string, string, string) error
}

// Router wraps grpc-proxy StreamDirector.
type Router struct {
talosBackends *expirable.LRU[string, proxy.Backend]
Expand All @@ -67,6 +72,7 @@ type Router struct {
nodeResolver NodeResolver
verifier grpc.UnaryServerInterceptor
cosiState state.State
talosAuditor TalosAuditor
authEnabled bool
}

Expand All @@ -76,6 +82,7 @@ func NewRouter(
cosiState state.State,
nodeResolver NodeResolver,
authEnabled bool,
talosAuditor TalosAuditor,
verifier grpc.UnaryServerInterceptor,
) (*Router, error) {
omniConn, err := grpc.NewClient(transport.Address(),
Expand All @@ -95,12 +102,6 @@ func NewRouter(

r := &Router{
talosBackends: expirable.NewLRU[string, proxy.Backend](talosBackendLRUSize, nil, talosBackendTTL),
omniBackend: NewOmniBackend("omni", nodeResolver, omniConn),
cosiState: cosiState,
nodeResolver: nodeResolver,
authEnabled: authEnabled,
verifier: verifier,

metricCacheSize: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "omni_grpc_proxy_talos_backend_cache_size",
Help: "Number of Talos clients in the cache of gRPC Proxy.",
Expand All @@ -117,6 +118,12 @@ func NewRouter(
Name: "omni_grpc_proxy_talos_backend_cache_misses_total",
Help: "Number of gRPC Proxy Talos client cache misses.",
}),
omniBackend: NewOmniBackend("omni", nodeResolver, omniConn),
nodeResolver: nodeResolver,
verifier: verifier,
cosiState: cosiState,
talosAuditor: talosAuditor,
authEnabled: authEnabled,
}

return r, nil
Expand Down Expand Up @@ -153,6 +160,10 @@ func (r *Router) Director(ctx context.Context, fullMethodName string) (proxy.Mod
return proxy.One2One, nil, err
}

if err = r.talosAuditor.AuditTalosAccess(ctx, fullMethodName, getClusterName(md), getNodeID(md)); err != nil {
return proxy.One2One, nil, err
}

return proxy.One2One, backends, nil
}

Expand Down
9 changes: 7 additions & 2 deletions internal/backend/k8sproxy/k8sproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@ type Handler struct {
chain http.Handler
}

// MiddlewareWrapper is an interface for middleware wrappers.
type MiddlewareWrapper interface {
Wrap(http.Handler) http.Handler
}

// NewHandler creates a new Handler.
func NewHandler(keyFunc KeyProvider, clusterUUIDResolver ClusterUUIDResolver, logger *zap.Logger) (*Handler, error) {
func NewHandler(keyFunc KeyProvider, clusterUUIDResolver ClusterUUIDResolver, wrapper MiddlewareWrapper, logger *zap.Logger) (*Handler, error) {
multiplexer := newMultiplexer()
proxy := newProxyHandler(multiplexer, logger)
proxy := wrapper.Wrap(newProxyHandler(multiplexer, logger))

handler := &Handler{
multiplexer: multiplexer,
Expand Down
20 changes: 20 additions & 0 deletions internal/backend/k8sproxy/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"go.uber.org/zap"
"k8s.io/client-go/transport"

"github.com/siderolabs/omni/internal/backend/runtime/omni/audit"
"github.com/siderolabs/omni/internal/pkg/ctxstore"
)

Expand Down Expand Up @@ -118,6 +119,25 @@ func AuthorizeRequest(next http.Handler, keyFunc KeyProvider, clusterUUIDResolve

req.Header.Add(transport.ImpersonateUserHeader, claims.Subject)

//nolint:contextcheck
req = req.WithContext(ctxstore.WithValue(
req.Context(),
&audit.Data{
K8SAccess: &audit.K8SAccess{
FullMethodName: req.Method + " " + req.URL.Path,
Command: req.Header.Get("Kubectl-Command"),
Session: req.Header.Get("Kubectl-Session"),
ClusterName: clusterName,
ClusterUUID: clusterUUID,
Body: "",
},
Session: audit.Session{
UserAgent: req.Header.Get("User-Agent"),
Email: claims.Subject,
},
},
))

for _, group := range claims.Groups {
req.Header.Add(transport.ImpersonateGroupHeader, group)
}
Expand Down
Loading

0 comments on commit 8576fbe

Please sign in to comment.