Skip to content

Commit

Permalink
Integrate the selectors into the executor
Browse files Browse the repository at this point in the history
When an entity is not selected for a profile, let's save that into a
profile-global status override and use that for all the statuses instead
calling eval.

Fixes: mindersec#3724
  • Loading branch information
jhrozek committed Jul 25, 2024
1 parent 522f4cf commit b668c93
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 3 deletions.
52 changes: 49 additions & 3 deletions internal/engine/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import (
"github.com/stacklok/minder/internal/engine/ingestcache"
engif "github.com/stacklok/minder/internal/engine/interfaces"
"github.com/stacklok/minder/internal/engine/rtengine"
"github.com/stacklok/minder/internal/engine/selectors"
"github.com/stacklok/minder/internal/history"
minderlogger "github.com/stacklok/minder/internal/logger"
"github.com/stacklok/minder/internal/profiles"
"github.com/stacklok/minder/internal/profiles/models"
"github.com/stacklok/minder/internal/providers/manager"
provsel "github.com/stacklok/minder/internal/providers/selectors"
pb "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1"
provinfv1 "github.com/stacklok/minder/pkg/providers/v1"
)
Expand All @@ -55,6 +57,7 @@ type executor struct {
historyService history.EvaluationHistoryService
featureFlags openfeature.IClient
profileStore profiles.ProfileStore
selBuilder selectors.SelectionBuilder
}

// NewExecutor creates a new executor
Expand All @@ -65,6 +68,7 @@ func NewExecutor(
historyService history.EvaluationHistoryService,
featureFlags openfeature.IClient,
profileStore profiles.ProfileStore,
selBuilder selectors.SelectionBuilder,
) Executor {
return &executor{
querier: querier,
Expand All @@ -73,6 +77,7 @@ func NewExecutor(
historyService: historyService,
featureFlags: featureFlags,
profileStore: profileStore,
selBuilder: selBuilder,
}
}

Expand Down Expand Up @@ -131,10 +136,14 @@ func (e *executor) EvalEntityEvent(ctx context.Context, inf *entities.EntityInfo
return fmt.Errorf("error while retrieving profiles and rule instances: %w", err)
}

// For each profile, evaluate each rule and store the outcome in the database
// For each profile, get the profile-override status. Then, if there is no profile-override status,
// evaluate each rule and store the outcome in the database or store the override status for all rules
for _, profile := range profileAggregates {

profileEvalStatus := e.profileEvalStatus(ctx, provider, inf, profile)

for _, rule := range profile.Rules {
if err := e.evaluateRule(ctx, inf, provider, &profile, &rule, ruleEngineCache); err != nil {
if err := e.evaluateRule(ctx, inf, provider, &profile, &rule, ruleEngineCache, profileEvalStatus); err != nil {
return fmt.Errorf("error evaluating entity event: %w", err)
}
}
Expand All @@ -150,6 +159,7 @@ func (e *executor) evaluateRule(
profile *models.ProfileAggregate,
rule *models.RuleInstance,
ruleEngineCache rtengine.Cache,
profileEvalStatus error,
) error {
// Create eval status params
evalParams, err := e.createEvalStatusParams(ctx, inf, profile, rule)
Expand All @@ -176,7 +186,12 @@ func (e *executor) evaluateRule(
defer e.updateLockLease(ctx, *inf.ExecutionID, evalParams)

// Evaluate the rule
evalErr := ruleEngine.Eval(ctx, inf, evalParams)
var evalErr error
if profileEvalStatus != nil {
evalErr = profileEvalStatus
} else {
evalErr = ruleEngine.Eval(ctx, inf, evalParams)
}
evalParams.SetEvalErr(evalErr)

// Perform actionEngine, if any
Expand All @@ -190,6 +205,37 @@ func (e *executor) evaluateRule(
return e.createOrUpdateEvalStatus(ctx, evalParams)
}

func (e *executor) profileEvalStatus(
ctx context.Context,
provider provinfv1.Provider,
eiw *entities.EntityInfoWrapper,
aggregate models.ProfileAggregate,
) error {
// so far this function only handles selectors. In the future we can extend it to handle other
// profile-global evaluations

selection, err := e.selBuilder.NewSelectionFromProfile(eiw.Type, aggregate.Selectors)
if err != nil {
return fmt.Errorf("error creating selection from profile: %w", err)
}

selEnt := provsel.EntityToSelectorEntity(ctx, provider, eiw.Type, eiw.Entity)
if selEnt == nil {
return fmt.Errorf("error converting entity to selector entity")
}

selected, err := selection.Select(selEnt)
if err != nil {
return fmt.Errorf("error selecting entity: %w", err)
}

if !selected {
return evalerrors.NewErrEvaluationSkipped("entity not applicable due to profile selector")
}

return nil
}

func (e *executor) updateLockLease(
ctx context.Context,
executionID uuid.UUID,
Expand Down
14 changes: 14 additions & 0 deletions internal/engine/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/stacklok/minder/internal/engine/actions/alert"
"github.com/stacklok/minder/internal/engine/actions/remediate"
"github.com/stacklok/minder/internal/engine/entities"
mock_selectors "github.com/stacklok/minder/internal/engine/selectors/mock"
"github.com/stacklok/minder/internal/flags"
mockhistory "github.com/stacklok/minder/internal/history/mock"
"github.com/stacklok/minder/internal/logger"
Expand Down Expand Up @@ -357,13 +358,26 @@ default allow = true`,
return fn(mockStore)
})

mockSelection := mock_selectors.NewMockSelection(ctrl)
mockSelection.EXPECT().
Select(gomock.Any(), gomock.Any()).
Return(true, nil).
AnyTimes()

mockSelectionBuilder := mock_selectors.NewMockSelectionBuilder(ctrl)
mockSelectionBuilder.EXPECT().
NewSelectionFromProfile(gomock.Any(), gomock.Any()).
Return(mockSelection, nil).
AnyTimes()

executor := engine.NewExecutor(
mockStore,
providerManager,
execMetrics,
historyService,
&flags.FakeClient{},
profiles.NewProfileStore(mockStore),
mockSelectionBuilder,
)

eiw := entities.NewEntityInfoWrapper().
Expand Down
3 changes: 3 additions & 0 deletions internal/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/stacklok/minder/internal/email/awsses"
"github.com/stacklok/minder/internal/email/noop"
"github.com/stacklok/minder/internal/engine"
"github.com/stacklok/minder/internal/engine/selectors"
"github.com/stacklok/minder/internal/events"
"github.com/stacklok/minder/internal/flags"
"github.com/stacklok/minder/internal/history"
Expand Down Expand Up @@ -190,6 +191,7 @@ func AllInOneServerService(
}

profileStore := profiles.NewProfileStore(store)
selEnv := selectors.NewEnv()

// Register the executor to handle entity evaluations
exec := engine.NewExecutor(
Expand All @@ -199,6 +201,7 @@ func AllInOneServerService(
history.NewEvaluationHistoryService(),
featureFlagClient,
profileStore,
selEnv,
)

handler := engine.NewExecutorEventHandler(
Expand Down

0 comments on commit b668c93

Please sign in to comment.