diff --git a/internal/server/authz/middleware/grpc/middleware.go b/internal/server/authz/middleware/grpc/middleware.go index 7a2f0d7db5..54c5e091d2 100644 --- a/internal/server/authz/middleware/grpc/middleware.go +++ b/internal/server/authz/middleware/grpc/middleware.go @@ -90,21 +90,21 @@ func AuthorizationRequiredInterceptor(logger *zap.Logger, policyVerifier authz.V return ctx, errUnauthorized } - request := requester.Request() - - allowed, err := policyVerifier.IsAllowed(ctx, map[string]interface{}{ - "request": request, - "authentication": auth, - }) - - if err != nil { - logger.Error("unauthorized", zap.Error(err)) - return ctx, errUnauthorized - } - - if !allowed { - logger.Error("unauthorized", zap.String("reason", "permission denied")) - return ctx, errUnauthorized + for _, request := range requester.Request() { + allowed, err := policyVerifier.IsAllowed(ctx, map[string]interface{}{ + "request": request, + "authentication": auth, + }) + + if err != nil { + logger.Error("unauthorized", zap.Error(err)) + return ctx, errUnauthorized + } + + if !allowed { + logger.Error("unauthorized", zap.String("reason", "permission denied")) + return ctx, errUnauthorized + } } return handler(ctx, req) diff --git a/internal/server/evaluation/data/server.go b/internal/server/evaluation/data/server.go index eaf0583d52..481bc7cc6d 100644 --- a/internal/server/evaluation/data/server.go +++ b/internal/server/evaluation/data/server.go @@ -360,7 +360,3 @@ func (srv *Server) EvaluationSnapshotNamespace(ctx context.Context, r *evaluatio return resp, nil } - -func (srv *Server) SkipsAuthorization(ctx context.Context) bool { - return true -} diff --git a/internal/server/evaluation/data/server_test.go b/internal/server/evaluation/data/server_test.go index ce39cbb3b3..86c04b7c54 100644 --- a/internal/server/evaluation/data/server_test.go +++ b/internal/server/evaluation/data/server_test.go @@ -34,8 +34,3 @@ func TestEvaluationSnapshotNamespace(t *testing.T) { store.AssertExpectations(t) }) } - -func Test_Server_SkipsAuthorization(t *testing.T) { - server := &Server{} - assert.True(t, server.SkipsAuthorization(context.Background())) -} diff --git a/internal/server/middleware/grpc/middleware.go b/internal/server/middleware/grpc/middleware.go index e21e2eea5e..6b2fb192e7 100644 --- a/internal/server/middleware/grpc/middleware.go +++ b/internal/server/middleware/grpc/middleware.go @@ -239,77 +239,78 @@ func EvaluationUnaryInterceptor(analyticsEnabled bool) grpc.UnaryServerIntercept // AuditEventUnaryInterceptor captures events and adds them to the trace span to be consumed downstream. func AuditEventUnaryInterceptor(logger *zap.Logger, eventPairChecker audit.EventPairChecker) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - var request flipt.Request + var requests []flipt.Request r, ok := req.(flipt.Requester) if !ok { return handler(ctx, req) } - request = r.Request() + requests = r.Request() - var event *audit.Event + var events []*audit.Event actor := authn.ActorFromContext(ctx) defer func() { - if event != nil { - eventPair := fmt.Sprintf("%s:%s", event.Type, event.Action) - - exists := eventPairChecker.Check(eventPair) - if exists { - span := trace.SpanFromContext(ctx) - span.AddEvent("event", trace.WithAttributes(event.DecodeToAttributes()...)) + if events != nil { + for _, event := range events { + eventPair := fmt.Sprintf("%s:%s", event.Type, event.Action) + + exists := eventPairChecker.Check(eventPair) + if exists { + span := trace.SpanFromContext(ctx) + span.AddEvent("event", trace.WithAttributes(event.DecodeToAttributes()...)) + } } } }() resp, err := handler(ctx, req) - if err != nil { - var uerr errs.ErrUnauthorized - if errors.As(err, &uerr) { - request.Status = flipt.StatusDenied - event = audit.NewEvent(request, actor, nil) + for _, request := range requests { + if err != nil { + var uerr errs.ErrUnauthorized + if errors.As(err, &uerr) { + request.Status = flipt.StatusDenied + events = append(events, audit.NewEvent(request, actor, nil)) + } + + continue + } + + // Delete and Order request(s) have to be handled separately because they do not + // return the concrete type but rather an *empty.Empty response. + if request.Action == flipt.ActionDelete { + events = append(events, audit.NewEvent(request, actor, r)) + continue } - return resp, err - } - // Delete and Order request(s) have to be handled separately because they do not - // return the concrete type but rather an *empty.Empty response. - if request.Action == flipt.ActionDelete { - event = audit.NewEvent(request, actor, r) - } else { switch r := req.(type) { case *flipt.OrderRulesRequest, *flipt.OrderRolloutsRequest: - event = audit.NewEvent(request, actor, r) + events = append(events, audit.NewEvent(request, actor, r)) + continue } - } - // Short circuiting the middleware here since we have a non-nil event from - // detecting a delete. - if event != nil { - return resp, err - } - - switch r := resp.(type) { - case *flipt.Flag: - event = audit.NewEvent(request, actor, audit.NewFlag(r)) - case *flipt.Variant: - event = audit.NewEvent(request, actor, audit.NewVariant(r)) - case *flipt.Segment: - event = audit.NewEvent(request, actor, audit.NewSegment(r)) - case *flipt.Distribution: - event = audit.NewEvent(request, actor, audit.NewDistribution(r)) - case *flipt.Constraint: - event = audit.NewEvent(request, actor, audit.NewConstraint(r)) - case *flipt.Namespace: - event = audit.NewEvent(request, actor, audit.NewNamespace(r)) - case *flipt.Rollout: - event = audit.NewEvent(request, actor, audit.NewRollout(r)) - case *flipt.Rule: - event = audit.NewEvent(request, actor, audit.NewRule(r)) - case *auth.CreateTokenResponse: - event = audit.NewEvent(request, actor, r.Authentication.Metadata) + switch r := resp.(type) { + case *flipt.Flag: + events = append(events, audit.NewEvent(request, actor, audit.NewFlag(r))) + case *flipt.Variant: + events = append(events, audit.NewEvent(request, actor, audit.NewVariant(r))) + case *flipt.Segment: + events = append(events, audit.NewEvent(request, actor, audit.NewSegment(r))) + case *flipt.Distribution: + events = append(events, audit.NewEvent(request, actor, audit.NewDistribution(r))) + case *flipt.Constraint: + events = append(events, audit.NewEvent(request, actor, audit.NewConstraint(r))) + case *flipt.Namespace: + events = append(events, audit.NewEvent(request, actor, audit.NewNamespace(r))) + case *flipt.Rollout: + events = append(events, audit.NewEvent(request, actor, audit.NewRollout(r))) + case *flipt.Rule: + events = append(events, audit.NewEvent(request, actor, audit.NewRule(r))) + case *auth.CreateTokenResponse: + events = append(events, audit.NewEvent(request, actor, r.Authentication.Metadata)) + } } return resp, err diff --git a/rpc/flipt/auth/request.go b/rpc/flipt/auth/request.go index 682cef798a..7fa648ea63 100644 --- a/rpc/flipt/auth/request.go +++ b/rpc/flipt/auth/request.go @@ -4,18 +4,18 @@ import ( "go.flipt.io/flipt/rpc/flipt" ) -func (req *CreateTokenRequest) Request() flipt.Request { - return flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionCreate, flipt.WithSubject(flipt.SubjectToken)) +func (req *CreateTokenRequest) Request() []flipt.Request { + return []flipt.Request{flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionCreate, flipt.WithSubject(flipt.SubjectToken))} } -func (req *ListAuthenticationsRequest) Request() flipt.Request { - return flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionRead) +func (req *ListAuthenticationsRequest) Request() []flipt.Request { + return []flipt.Request{flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionRead)} } -func (req *GetAuthenticationRequest) Request() flipt.Request { - return flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionRead) +func (req *GetAuthenticationRequest) Request() []flipt.Request { + return []flipt.Request{flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionRead)} } -func (req *DeleteAuthenticationRequest) Request() flipt.Request { - return flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionDelete) +func (req *DeleteAuthenticationRequest) Request() []flipt.Request { + return []flipt.Request{flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionDelete)} } diff --git a/rpc/flipt/evaluation/request.go b/rpc/flipt/evaluation/request.go new file mode 100644 index 0000000000..269914df34 --- /dev/null +++ b/rpc/flipt/evaluation/request.go @@ -0,0 +1,10 @@ +package evaluation + +import "go.flipt.io/flipt/rpc/flipt" + +func (r *EvaluationNamespaceSnapshotRequest) Request() []flipt.Request { + return []flipt.Request{ + flipt.NewRequest(flipt.ResourceFlag, flipt.ActionRead, flipt.WithNamespace(r.Key)), + flipt.NewRequest(flipt.ResourceSegment, flipt.ActionRead, flipt.WithNamespace(r.Key)), + } +} diff --git a/rpc/flipt/request.go b/rpc/flipt/request.go index 1a00da9af5..61e025d8b0 100644 --- a/rpc/flipt/request.go +++ b/rpc/flipt/request.go @@ -1,7 +1,7 @@ package flipt type Requester interface { - Request() Request + Request() []Request } // Resource represents what resource or parent resource is being acted on. @@ -93,153 +93,153 @@ func newSegmentScopedRequest(ns string, s Subject, a Action) Request { } // Namespaces -func (req *GetNamespaceRequest) Request() Request { - return NewRequest(ResourceNamespace, ActionRead, WithNamespace(req.Key)) +func (req *GetNamespaceRequest) Request() []Request { + return []Request{NewRequest(ResourceNamespace, ActionRead, WithNamespace(req.Key))} } -func (req *ListNamespaceRequest) Request() Request { - return NewRequest(ResourceNamespace, ActionRead) +func (req *ListNamespaceRequest) Request() []Request { + return []Request{NewRequest(ResourceNamespace, ActionRead)} } -func (req *CreateNamespaceRequest) Request() Request { - return NewRequest(ResourceNamespace, ActionCreate, WithNamespace(req.Key)) +func (req *CreateNamespaceRequest) Request() []Request { + return []Request{NewRequest(ResourceNamespace, ActionCreate, WithNamespace(req.Key))} } -func (req *UpdateNamespaceRequest) Request() Request { - return NewRequest(ResourceNamespace, ActionUpdate, WithNamespace(req.Key)) +func (req *UpdateNamespaceRequest) Request() []Request { + return []Request{NewRequest(ResourceNamespace, ActionUpdate, WithNamespace(req.Key))} } -func (req *DeleteNamespaceRequest) Request() Request { - return NewRequest(ResourceNamespace, ActionDelete, WithNamespace(req.Key)) +func (req *DeleteNamespaceRequest) Request() []Request { + return []Request{NewRequest(ResourceNamespace, ActionDelete, WithNamespace(req.Key))} } // Flags -func (req *GetFlagRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionRead) +func (req *GetFlagRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionRead)} } -func (req *ListFlagRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionRead) +func (req *ListFlagRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionRead)} } -func (req *CreateFlagRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionCreate) +func (req *CreateFlagRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionCreate)} } -func (req *UpdateFlagRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionUpdate) +func (req *UpdateFlagRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionUpdate)} } -func (req *DeleteFlagRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionDelete) +func (req *DeleteFlagRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectFlag, ActionDelete)} } // Variants -func (req *CreateVariantRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectVariant, ActionCreate) +func (req *CreateVariantRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectVariant, ActionCreate)} } -func (req *UpdateVariantRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectVariant, ActionUpdate) +func (req *UpdateVariantRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectVariant, ActionUpdate)} } -func (req *DeleteVariantRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectVariant, ActionDelete) +func (req *DeleteVariantRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectVariant, ActionDelete)} } // Rules -func (req *ListRuleRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionRead) +func (req *ListRuleRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionRead)} } -func (req *GetRuleRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionRead) +func (req *GetRuleRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionRead)} } -func (req *CreateRuleRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionCreate) +func (req *CreateRuleRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionCreate)} } -func (req *UpdateRuleRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionUpdate) +func (req *UpdateRuleRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionUpdate)} } -func (req *OrderRulesRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionUpdate) +func (req *OrderRulesRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionUpdate)} } -func (req *DeleteRuleRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionDelete) +func (req *DeleteRuleRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRule, ActionDelete)} } // Rollouts -func (req *ListRolloutRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionRead) +func (req *ListRolloutRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionRead)} } -func (req *GetRolloutRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionRead) +func (req *GetRolloutRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionRead)} } -func (req *CreateRolloutRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionCreate) +func (req *CreateRolloutRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionCreate)} } -func (req *UpdateRolloutRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionUpdate) +func (req *UpdateRolloutRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionUpdate)} } -func (req *OrderRolloutsRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionUpdate) +func (req *OrderRolloutsRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionUpdate)} } -func (req *DeleteRolloutRequest) Request() Request { - return newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionDelete) +func (req *DeleteRolloutRequest) Request() []Request { + return []Request{newFlagScopedRequest(req.NamespaceKey, SubjectRollout, ActionDelete)} } // Segments -func (req *GetSegmentRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionRead) +func (req *GetSegmentRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionRead)} } -func (req *ListSegmentRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionRead) +func (req *ListSegmentRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionRead)} } -func (req *CreateSegmentRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionCreate) +func (req *CreateSegmentRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionCreate)} } -func (req *UpdateSegmentRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionUpdate) +func (req *UpdateSegmentRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionUpdate)} } -func (req *DeleteSegmentRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionDelete) +func (req *DeleteSegmentRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectSegment, ActionDelete)} } // Constraints -func (req *CreateConstraintRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectConstraint, ActionCreate) +func (req *CreateConstraintRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectConstraint, ActionCreate)} } -func (req *UpdateConstraintRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectConstraint, ActionUpdate) +func (req *UpdateConstraintRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectConstraint, ActionUpdate)} } -func (req *DeleteConstraintRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectConstraint, ActionDelete) +func (req *DeleteConstraintRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectConstraint, ActionDelete)} } // Distributions -func (req *CreateDistributionRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectDistribution, ActionCreate) +func (req *CreateDistributionRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectDistribution, ActionCreate)} } -func (req *UpdateDistributionRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectDistribution, ActionUpdate) +func (req *UpdateDistributionRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectDistribution, ActionUpdate)} } -func (req *DeleteDistributionRequest) Request() Request { - return newSegmentScopedRequest(req.NamespaceKey, SubjectDistribution, ActionDelete) +func (req *DeleteDistributionRequest) Request() []Request { + return []Request{newSegmentScopedRequest(req.NamespaceKey, SubjectDistribution, ActionDelete)} }