Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement user and group audit events #3467

Merged
merged 1 commit into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions audit/pkg/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ func StartAuditLogger(ctx context.Context, ch <-chan interface{}, log log.Logger
auditEvent = types.SpaceEnabled(ev)
case events.SpaceDeleted:
auditEvent = types.SpaceDeleted(ev)
case events.UserCreated:
auditEvent = types.UserCreated(ev)
case events.UserDeleted:
auditEvent = types.UserDeleted(ev)
case events.UserFeatureChanged:
auditEvent = types.UserFeatureChanged(ev)
case events.GroupCreated:
auditEvent = types.GroupCreated(ev)
case events.GroupDeleted:
auditEvent = types.GroupDeleted(ev)
case events.GroupMemberAdded:
auditEvent = types.GroupMemberAdded(ev)
case events.GroupMemberRemoved:
auditEvent = types.GroupMemberRemoved(ev)
default:
log.Error().Interface("event", ev).Msg(fmt.Sprintf("can't handle event of type '%T'", ev))
continue
Expand Down
64 changes: 63 additions & 1 deletion audit/pkg/types/constants.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package types

import "fmt"
import (
"fmt"
"strings"

"github.com/cs3org/reva/v2/pkg/events"
)

// short identifiers for audit actions
const (
Expand Down Expand Up @@ -30,6 +35,17 @@ const (
ActionSpaceDisabled = "space_disabled"
ActionSpaceEnabled = "space_enabled"
ActionSpaceDeleted = "space_deleted"

// Users
ActionUserCreated = "user_created"
ActionUserDeleted = "user_deleted"
ActionUserFeatureChanged = "user_feature_changed"

// Groups
ActionGroupCreated = "group_created"
ActionGroupDeleted = "group_deleted"
ActionGroupMemberAdded = "group_member_added"
ActionGroupMemberRemoved = "group_member_removed"
)

// MessageShareCreated returns the human readable string that describes the action
Expand Down Expand Up @@ -136,3 +152,49 @@ func MessageSpaceEnabled(spaceID string) string {
func MessageSpaceDeleted(spaceID string) string {
return fmt.Sprintf("Space '%s' was deleted", spaceID)
}

// MessageUserCreated returns the human readable string that describes the action
func MessageUserCreated(userID string) string {
return fmt.Sprintf("User '%s' was created", userID)
}

// MessageUserDeleted returns the human readable string that describes the action
func MessageUserDeleted(userID string) string {
return fmt.Sprintf("User '%s' was deleted", userID)
}

// MessageUserFeatureChanged returns the human readable string that describes the action
func MessageUserFeatureChanged(userID string, features []events.UserFeature) string {
// Result is: "User %username%'s feature changed: %featurename%=%featurevalue% %featurename%=%featurevalue%"
var sb strings.Builder
sb.WriteString("User ")
sb.WriteString(userID)
sb.WriteString("'s feature changed: ")
for _, f := range features {
sb.WriteString(f.Name)
sb.WriteRune('=')
sb.WriteString(f.Value)
sb.WriteRune(' ')
}
return sb.String()
}

// MessageGroupCreated returns the human readable string that describes the action
func MessageGroupCreated(groupID string) string {
return fmt.Sprintf("Group '%s' was created", groupID)
}

// MessageGroupDeleted returns the human readable string that describes the action
func MessageGroupDeleted(groupID string) string {
return fmt.Sprintf("Group '%s' was deleted", groupID)
}

// MessageGroupMemberAdded returns the human readable string that describes the action
func MessageGroupMemberAdded(userID, groupID string) string {
return fmt.Sprintf("User '%s' was added to group '%s'", userID, groupID)
}

// MessageGroupMemberRemoved returns the human readable string that describes the action
func MessageGroupMemberRemoved(userID, groupID string) string {
return fmt.Sprintf("User '%s' was removed from group '%s'", userID, groupID)
}
69 changes: 69 additions & 0 deletions audit/pkg/types/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,75 @@ func SpaceDeleted(ev events.SpaceDeleted) AuditEventSpaceDeleted {
}
}

// UserCreated converts a UserCreated event to an AuditEventUserCreated
func UserCreated(ev events.UserCreated) AuditEventUserCreated {
base := BasicAuditEvent("", "", MessageUserCreated(ev.UserID), ActionUserCreated)
return AuditEventUserCreated{
AuditEvent: base,
UserID: ev.UserID,
}
}

// UserDeleted converts a UserDeleted event to an AuditEventUserDeleted
func UserDeleted(ev events.UserDeleted) AuditEventUserDeleted {
base := BasicAuditEvent("", "", MessageUserDeleted(ev.UserID), ActionUserDeleted)
return AuditEventUserDeleted{
AuditEvent: base,
UserID: ev.UserID,
}
}

// UserFeatureChanged converts a UserFeatureChanged event to an AuditEventUserFeatureChanged
func UserFeatureChanged(ev events.UserFeatureChanged) AuditEventUserFeatureChanged {
msg := MessageUserFeatureChanged(ev.UserID, ev.Features)
base := BasicAuditEvent("", "", msg, ActionUserFeatureChanged)
return AuditEventUserFeatureChanged{
AuditEvent: base,
UserID: ev.UserID,
Features: ev.Features,
}
}

// GroupCreated converts a GroupCreated event to an AuditEventGroupCreated
func GroupCreated(ev events.GroupCreated) AuditEventGroupCreated {
base := BasicAuditEvent("", "", MessageGroupCreated(ev.GroupID), ActionGroupCreated)
return AuditEventGroupCreated{
AuditEvent: base,
GroupID: ev.GroupID,
}
}

// GroupDeleted converts a GroupDeleted event to an AuditEventGroupDeleted
func GroupDeleted(ev events.GroupDeleted) AuditEventGroupDeleted {
base := BasicAuditEvent("", "", MessageGroupDeleted(ev.GroupID), ActionGroupDeleted)
return AuditEventGroupDeleted{
AuditEvent: base,
GroupID: ev.GroupID,
}
}

// GroupMemberAdded converts a GroupMemberAdded event to an AuditEventGroupMemberAdded
func GroupMemberAdded(ev events.GroupMemberAdded) AuditEventGroupMemberAdded {
msg := MessageGroupMemberAdded(ev.GroupID, ev.UserID)
base := BasicAuditEvent("", "", msg, ActionGroupMemberAdded)
return AuditEventGroupMemberAdded{
AuditEvent: base,
GroupID: ev.GroupID,
UserID: ev.UserID,
}
}

// GroupMemberRemoved converts a GroupMemberRemoved event to an AuditEventGroupMemberRemove
func GroupMemberRemoved(ev events.GroupMemberRemoved) AuditEventGroupMemberRemoved {
msg := MessageGroupMemberRemoved(ev.GroupID, ev.UserID)
base := BasicAuditEvent("", "", msg, ActionGroupMemberRemoved)
return AuditEventGroupMemberRemoved{
AuditEvent: base,
GroupID: ev.GroupID,
UserID: ev.UserID,
}
}

func extractGrantee(uid *user.UserId, gid *group.GroupId) (string, string) {
switch {
case uid != nil && uid.OpaqueId != "":
Expand Down
7 changes: 7 additions & 0 deletions audit/pkg/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,12 @@ func RegisteredEvents() []events.Unmarshaller {
events.SpaceEnabled{},
events.SpaceDisabled{},
events.SpaceDeleted{},
events.UserCreated{},
events.UserDeleted{},
events.UserFeatureChanged{},
events.GroupCreated{},
events.GroupDeleted{},
events.GroupMemberAdded{},
events.GroupMemberRemoved{},
}
}
47 changes: 47 additions & 0 deletions audit/pkg/types/types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package types

import "github.com/cs3org/reva/v2/pkg/events"

// AuditEvent is the basic audit event
type AuditEvent struct {
RemoteAddr string // the remote client IP
Expand Down Expand Up @@ -197,3 +199,48 @@ type AuditEventSpaceEnabled struct {
type AuditEventSpaceDeleted struct {
AuditEventSpaces
}

// AuditEventUserCreated is the event logged when a user is created
type AuditEventUserCreated struct {
AuditEvent
UserID string
}

// AuditEventUserDeleted is the event logged when a user is deleted
type AuditEventUserDeleted struct {
AuditEvent
UserID string
}

// AuditEventUserFeatureChanged is the event logged when a user feature is changed
type AuditEventUserFeatureChanged struct {
AuditEvent
UserID string
Features []events.UserFeature
}

// AuditEventGroupCreated is the event logged when a group is created
type AuditEventGroupCreated struct {
AuditEvent
GroupID string
}

// AuditEventGroupDeleted is the event logged when a group is deleted
type AuditEventGroupDeleted struct {
AuditEvent
GroupID string
}

// AuditEventGroupMemberAdded is the event logged when a group member is added
type AuditEventGroupMemberAdded struct {
AuditEvent
GroupID string
UserID string
}

// AuditEventGroupMemberRemoved is the event logged when a group member is removed
type AuditEventGroupMemberRemoved struct {
AuditEvent
GroupID string
UserID string
}
12 changes: 12 additions & 0 deletions changelog/unreleased/user-group-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Enhancement: Implement audit events for user and groups

Added audit events for users and groups. This will log:
* User creation
* User deletion
* User property change (currently only email)
* Group creation
* Group deletion
* Group member add
* Group member remove

https://github.com/owncloud/ocis/pull/3467
2 changes: 2 additions & 0 deletions graph/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ include ../.make/generate.mk
ci-go-generate: $(MOCKERY) # CI runs ci-node-generate automatically before this target
$(MOCKERY) --dir pkg/service/v0 --case underscore --name GatewayClient
$(MOCKERY) --dir pkg/service/v0 --case underscore --name HTTPClient
$(MOCKERY) --dir pkg/service/v0 --case underscore --name Publisher


.PHONY: ci-node-generate
ci-node-generate:
Expand Down
34 changes: 34 additions & 0 deletions graph/mocks/publisher.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions graph/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Config struct {

Spaces Spaces `yaml:"spaces"`
Identity Identity `yaml:"identity"`
Events Events `yaml:"events"`

Context context.Context `yaml:"-"`
}
Expand Down Expand Up @@ -62,3 +63,9 @@ type Identity struct {
Backend string `yaml:"backend" env:"GRAPH_IDENTITY_BACKEND"`
LDAP LDAP `yaml:"ldap"`
}

// Events combines the configuration options for the event bus.
type Events struct {
Endpoint string `yaml:"events_endpoint" env:"GRAPH_EVENTS_ENDPOINT" desc:"the address of the streaming service"`
Cluster string `yaml:"events_cluster" env:"GRAPH_EVENTS_CLUSTER" desc:"the clusterID of the streaming service. Mandatory when using nats"`
}
4 changes: 4 additions & 0 deletions graph/pkg/config/defaults/defaultconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ func DefaultConfig() *config.Config {
GroupIDAttribute: "owncloudUUID",
},
},
Events: config.Events{
Endpoint: "127.0.0.1:9233",
Cluster: "ocis-cluster",
},
}
}

Expand Down
15 changes: 15 additions & 0 deletions graph/pkg/server/http/server.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package http

import (
"github.com/asim/go-micro/plugins/events/natsjs/v4"
"github.com/cs3org/reva/v2/pkg/events/server"
chimiddleware "github.com/go-chi/chi/v5/middleware"
graphMiddleware "github.com/owncloud/ocis/graph/pkg/middleware"
svc "github.com/owncloud/ocis/graph/pkg/service/v0"
"github.com/owncloud/ocis/ocis-pkg/account"
"github.com/owncloud/ocis/ocis-pkg/middleware"
"github.com/owncloud/ocis/ocis-pkg/service/http"
"github.com/owncloud/ocis/ocis-pkg/version"
"github.com/pkg/errors"
"go-micro.dev/v4"
)

Expand All @@ -25,6 +28,17 @@ func Server(opts ...Option) (http.Service, error) {
http.Flags(options.Flags...),
)

publisher, err := server.NewNatsStream(
natsjs.Address(options.Config.Events.Endpoint),
natsjs.ClusterID(options.Config.Events.Cluster),
)
if err != nil {
options.Logger.Error().
Err(err).
Msg("Error initializing events publisher")
return http.Service{}, errors.Wrap(err, "could not initialize events publisher")
}

handle := svc.NewService(
svc.Logger(options.Logger),
svc.Config(options.Config),
Expand All @@ -42,6 +56,7 @@ func Server(opts ...Option) (http.Service, error) {
account.JWTSecret(options.Config.TokenManager.JWTSecret),
),
),
svc.EventsPublisher(publisher),
)

{
Expand Down
Loading