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

feat: add audit logging for rollout actions #1834

Merged
merged 2 commits into from
Jul 7, 2023
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
1 change: 1 addition & 0 deletions internal/server/audit/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
DistributionType Type = "distribution"
FlagType Type = "flag"
NamespaceType Type = "namespace"
RolloutType Type = "rollout"
RuleType Type = "rule"
SegmentType Type = "segment"
TokenType Type = "token"
Expand Down
43 changes: 43 additions & 0 deletions internal/server/audit/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,46 @@ func NewRule(r *flipt.Rule) *Rule {
NamespaceKey: r.NamespaceKey,
}
}

type Rollout struct {
NamespaceKey string `json:"namespace_key"`
FlagKey string `json:"flag_key"`
Rank int32 `json:"rank"`
Description string `json:"description"`
Threshold *RolloutThreshold `json:"threshold,omitempty"`
Segment *RolloutSegment `json:"segment,omitempty"`
}

type RolloutThreshold struct {
Percentage float32 `json:"percentage"`
Value bool `json:"value"`
}

type RolloutSegment struct {
Key string `json:"key"`
Value bool `json:"value"`
}

func NewRollout(r *flipt.Rollout) *Rollout {
rollout := &Rollout{
NamespaceKey: r.NamespaceKey,
FlagKey: r.FlagKey,
Rank: r.Rank,
Description: r.Description,
}

switch rout := r.Rule.(type) {
case *flipt.Rollout_Segment:
rollout.Segment = &RolloutSegment{
Key: rout.Segment.SegmentKey,
Value: rout.Segment.Value,
}
case *flipt.Rollout_Threshold:
rollout.Threshold = &RolloutThreshold{
Percentage: rout.Threshold.Percentage,
Value: rout.Threshold.Value,
}
}

return rollout
}
6 changes: 6 additions & 0 deletions internal/server/middleware/grpc/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ func AuditUnaryInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor {
event = audit.NewEvent(audit.NamespaceType, audit.Delete, actor, r)
case *flipt.DeleteRuleRequest:
event = audit.NewEvent(audit.RuleType, audit.Delete, actor, r)
case *flipt.DeleteRolloutRequest:
event = audit.NewEvent(audit.RolloutType, audit.Delete, actor, r)
}

// Short circuiting the middleware here since we have a non-nil event from
Expand Down Expand Up @@ -304,6 +306,10 @@ func AuditUnaryInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor {
if action != "" {
event = audit.NewEvent(audit.NamespaceType, action, actor, audit.NewNamespace(r))
}
case *flipt.Rollout:
if action != "" {
event = audit.NewEvent(audit.RolloutType, action, actor, audit.NewRollout(r))
}
case *flipt.Rule:
if action != "" {
event = audit.NewEvent(audit.RuleType, action, actor, audit.NewRule(r))
Expand Down
129 changes: 129 additions & 0 deletions internal/server/middleware/grpc/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,135 @@ func TestAuditUnaryInterceptor_DeleteConstraint(t *testing.T) {
assert.Equal(t, 1, exporterSpy.GetSendAuditsCalled())
}

func TestAuditUnaryInterceptor_CreateRollout(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
exporterSpy = newAuditExporterSpy(logger)
s = server.New(logger, store)
req = &flipt.CreateRolloutRequest{
FlagKey: "flagkey",
Rank: 1,
Rule: &flipt.CreateRolloutRequest_Threshold{
Threshold: &flipt.RolloutThreshold{
Percentage: 50.0,
Value: true,
},
},
}
)

store.On("CreateRollout", mock.Anything, req).Return(&flipt.Rollout{
Id: "1",
NamespaceKey: "default",
Rank: 1,
FlagKey: req.FlagKey,
}, nil)

unaryInterceptor := AuditUnaryInterceptor(logger)

handler := func(ctx context.Context, r interface{}) (interface{}, error) {
return s.CreateRollout(ctx, r.(*flipt.CreateRolloutRequest))
}

info := &grpc.UnaryServerInfo{
FullMethod: "CreateRollout",
}

tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
tp.RegisterSpanProcessor(sdktrace.NewSimpleSpanProcessor(exporterSpy))

tr := tp.Tracer("SpanProcessor")
ctx, span := tr.Start(context.Background(), "OnStart")

got, err := unaryInterceptor(ctx, req, info, handler)
require.NoError(t, err)
assert.NotNil(t, got)

span.End()
assert.Equal(t, 1, exporterSpy.GetSendAuditsCalled())
}

func TestAuditUnaryInterceptor_UpdateRollout(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
exporterSpy = newAuditExporterSpy(logger)
s = server.New(logger, store)
req = &flipt.UpdateRolloutRequest{
Description: "desc",
}
)

store.On("UpdateRollout", mock.Anything, req).Return(&flipt.Rollout{
Description: "desc",
FlagKey: "flagkey",
NamespaceKey: "default",
Rank: 1,
}, nil)

unaryInterceptor := AuditUnaryInterceptor(logger)

handler := func(ctx context.Context, r interface{}) (interface{}, error) {
return s.UpdateRollout(ctx, r.(*flipt.UpdateRolloutRequest))
}

info := &grpc.UnaryServerInfo{
FullMethod: "UpdateRollout",
}

tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
tp.RegisterSpanProcessor(sdktrace.NewSimpleSpanProcessor(exporterSpy))

tr := tp.Tracer("SpanProcessor")
ctx, span := tr.Start(context.Background(), "OnStart")

got, err := unaryInterceptor(ctx, req, info, handler)
require.NoError(t, err)
assert.NotNil(t, got)

span.End()
assert.Equal(t, 1, exporterSpy.GetSendAuditsCalled())
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mind adding a test for delete?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markphelps Right! will do that

func TestAuditUnaryInterceptor_DeleteRollout(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
exporterSpy = newAuditExporterSpy(logger)
s = server.New(logger, store)
req = &flipt.DeleteRolloutRequest{
Id: "1",
FlagKey: "flagKey",
}
)

store.On("DeleteRollout", mock.Anything, req).Return(nil)

unaryInterceptor := AuditUnaryInterceptor(logger)

handler := func(ctx context.Context, r interface{}) (interface{}, error) {
return s.DeleteRollout(ctx, r.(*flipt.DeleteRolloutRequest))
}

info := &grpc.UnaryServerInfo{
FullMethod: "DeleteRollout",
}

tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()))
tp.RegisterSpanProcessor(sdktrace.NewSimpleSpanProcessor(exporterSpy))

tr := tp.Tracer("SpanProcessor")
ctx, span := tr.Start(context.Background(), "OnStart")

got, err := unaryInterceptor(ctx, req, info, handler)
require.NoError(t, err)
assert.NotNil(t, got)

span.End()
assert.Equal(t, 1, exporterSpy.GetSendAuditsCalled())
}

func TestAuditUnaryInterceptor_CreateRule(t *testing.T) {
var (
store = &storeMock{}
Expand Down
Loading