2022-06-06 14:41:51 +00:00
|
|
|
// Package policies implements features to handle policy-related processing.
|
|
|
|
package policies
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
|
|
|
"errors"
|
|
|
|
"net/url"
|
|
|
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
|
|
kitlog "github.com/go-kit/kit/log"
|
|
|
|
"github.com/go-kit/kit/log/level"
|
|
|
|
)
|
|
|
|
|
|
|
|
// FailingPolicyAutomationType is the type of automations supported for
|
|
|
|
// failing policies.
|
|
|
|
type FailingPolicyAutomationType string
|
|
|
|
|
|
|
|
// List of supported failing policy automation types.
|
|
|
|
const (
|
|
|
|
FailingPolicyWebhook FailingPolicyAutomationType = "webhook"
|
|
|
|
FailingPolicyJira FailingPolicyAutomationType = "jira"
|
|
|
|
FailingPolicyZendesk FailingPolicyAutomationType = "zendesk"
|
|
|
|
)
|
|
|
|
|
|
|
|
// FailingPolicyAutomationConfig holds the configuration for proessing a
|
|
|
|
// failing policy to send to the configured automation.
|
|
|
|
type FailingPolicyAutomationConfig struct {
|
|
|
|
AutomationType FailingPolicyAutomationType
|
|
|
|
PolicyIDs map[uint]bool
|
|
|
|
WebhookURL *url.URL // for webhook automation type only
|
|
|
|
HostBatchSize int // for webhook automation type only
|
|
|
|
}
|
|
|
|
|
|
|
|
// TriggerFailingPoliciesAutomation triggers an automation for failing
|
|
|
|
// policies. It receives a function that takes care of sending the failed
|
|
|
|
// policy as argument, that function receives the type of automation that is
|
|
|
|
// enabled for that policy.
|
|
|
|
func TriggerFailingPoliciesAutomation(
|
|
|
|
ctx context.Context,
|
|
|
|
ds fleet.Datastore,
|
|
|
|
logger kitlog.Logger,
|
|
|
|
failingPoliciesSet fleet.FailingPolicySet,
|
|
|
|
sendFunc func(*fleet.Policy, FailingPolicyAutomationConfig) error,
|
|
|
|
) error {
|
2022-09-20 19:26:36 +00:00
|
|
|
appConfig, err := ds.AppConfig(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "getting app config")
|
|
|
|
}
|
2022-06-06 14:41:51 +00:00
|
|
|
|
|
|
|
// build the global automation configuration
|
|
|
|
var globalCfg FailingPolicyAutomationConfig
|
|
|
|
|
|
|
|
globalAutomation := getActiveAutomation(appConfig.WebhookSettings.FailingPoliciesWebhook, appConfig.Integrations)
|
|
|
|
if globalAutomation != "" {
|
|
|
|
level.Debug(logger).Log("global_failing_policy", "enabled", "automation", globalAutomation)
|
|
|
|
} else {
|
|
|
|
level.Debug(logger).Log("global_failing_policy", "disabled")
|
|
|
|
}
|
|
|
|
|
|
|
|
if globalAutomation != "" {
|
|
|
|
// if global failing policies automation is enabled, keep a set of
|
|
|
|
// policies for which the automation must run.
|
|
|
|
globalCfg.AutomationType = globalAutomation
|
|
|
|
globalSettings := appConfig.WebhookSettings.FailingPoliciesWebhook
|
|
|
|
|
|
|
|
polIDs := make(map[uint]bool, len(globalSettings.PolicyIDs))
|
|
|
|
for _, pID := range globalSettings.PolicyIDs {
|
|
|
|
polIDs[pID] = true
|
|
|
|
}
|
|
|
|
globalCfg.PolicyIDs = polIDs
|
|
|
|
|
|
|
|
if globalAutomation == FailingPolicyWebhook {
|
|
|
|
wurl, err := url.Parse(globalSettings.DestinationURL)
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrapf(ctx, err, "parse global webhook url: %s", globalSettings.DestinationURL)
|
|
|
|
}
|
|
|
|
globalCfg.WebhookURL = wurl
|
|
|
|
globalCfg.HostBatchSize = globalSettings.HostBatchSize
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare the per-team configuration caches
|
2022-06-13 14:04:47 +00:00
|
|
|
getTeam := makeTeamConfigCache(ds, appConfig.Integrations)
|
2022-06-06 14:41:51 +00:00
|
|
|
|
|
|
|
policySets, err := failingPoliciesSet.ListSets()
|
|
|
|
if err != nil {
|
|
|
|
return ctxerr.Wrap(ctx, err, "list policies set")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, policyID := range policySets {
|
|
|
|
policy, err := ds.Policy(ctx, policyID)
|
|
|
|
switch {
|
|
|
|
case errors.Is(err, sql.ErrNoRows):
|
|
|
|
level.Debug(logger).Log("msg", "skipping failing policy, deleted", "policyID", policyID)
|
|
|
|
if err := failingPoliciesSet.RemoveSet(policyID); err != nil {
|
|
|
|
level.Error(logger).Log("msg", "failed to remove policy from set", "policyID", policyID, "err", err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
case err != nil:
|
|
|
|
return ctxerr.Wrapf(ctx, err, "get policy: %d", policyID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if policy.TeamID != nil {
|
|
|
|
// handle team policy
|
|
|
|
teamCfg, err := getTeam(ctx, *policy.TeamID)
|
|
|
|
switch {
|
|
|
|
case errors.Is(err, sql.ErrNoRows):
|
|
|
|
// shouldn't happen, unless the team was deleted after the policy was retrieved above
|
|
|
|
level.Debug(logger).Log("msg", "team does not exist", "teamID", *policy.TeamID)
|
|
|
|
if err := failingPoliciesSet.RemoveSet(policy.ID); err != nil {
|
|
|
|
level.Error(logger).Log("msg", "failed to remove policy from set", "policyID", policy.ID, "err", err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
case err != nil:
|
|
|
|
level.Error(logger).Log("msg", "failed to get team", "teamID", *policy.TeamID, "err", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if teamCfg.AutomationType == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !teamCfg.PolicyIDs[policy.ID] {
|
|
|
|
level.Debug(logger).Log("msg", "skipping failing policy, not found in team policy IDs", "policyID", policyID)
|
|
|
|
if err := failingPoliciesSet.RemoveSet(policy.ID); err != nil {
|
|
|
|
level.Error(logger).Log("msg", "failed to remove policy from set", "policyID", policyID, "err", err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := sendFunc(policy, teamCfg); err != nil {
|
|
|
|
level.Error(logger).Log("msg", "failed to send failing policies", "policyID", policy.ID, "err", err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle global policy
|
|
|
|
if !globalCfg.PolicyIDs[policy.ID] {
|
|
|
|
level.Debug(logger).Log("msg", "skipping failing policy, not found in global policy IDs", "policyID", policyID)
|
|
|
|
if err := failingPoliciesSet.RemoveSet(policy.ID); err != nil {
|
|
|
|
level.Error(logger).Log("msg", "failed to remove policy from set", "policyID", policyID, "err", err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := sendFunc(policy, globalCfg); err != nil {
|
|
|
|
level.Error(logger).Log("msg", "failed to send failing policies", "policyID", policy.ID, "err", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-13 14:04:47 +00:00
|
|
|
func makeTeamConfigCache(ds fleet.Datastore, globalIntgs fleet.Integrations) func(ctx context.Context, teamID uint) (FailingPolicyAutomationConfig, error) {
|
2022-06-06 14:41:51 +00:00
|
|
|
teamCfgs := make(map[uint]FailingPolicyAutomationConfig)
|
2022-06-13 14:04:47 +00:00
|
|
|
|
2022-06-06 14:41:51 +00:00
|
|
|
return func(ctx context.Context, teamID uint) (FailingPolicyAutomationConfig, error) {
|
|
|
|
cfg, ok := teamCfgs[teamID]
|
|
|
|
if ok {
|
|
|
|
return cfg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
team, err := ds.Team(ctx, teamID)
|
|
|
|
if err != nil {
|
|
|
|
return cfg, ctxerr.Wrapf(ctx, err, "get team: %d", teamID)
|
|
|
|
}
|
|
|
|
|
2022-06-13 14:04:47 +00:00
|
|
|
intgs, err := team.Config.Integrations.MatchWithIntegrations(globalIntgs)
|
|
|
|
if err != nil {
|
|
|
|
return cfg, ctxerr.Wrap(ctx, err, "map team integrations to global integrations")
|
|
|
|
}
|
|
|
|
|
|
|
|
teamAutomation := getActiveAutomation(team.Config.WebhookSettings.FailingPoliciesWebhook, intgs)
|
2022-06-06 14:41:51 +00:00
|
|
|
teamCfg := FailingPolicyAutomationConfig{
|
|
|
|
AutomationType: teamAutomation,
|
|
|
|
}
|
|
|
|
|
|
|
|
if teamAutomation != "" {
|
|
|
|
settings := team.Config.WebhookSettings.FailingPoliciesWebhook
|
|
|
|
polIDs := make(map[uint]bool, len(settings.PolicyIDs))
|
|
|
|
for _, pID := range settings.PolicyIDs {
|
|
|
|
polIDs[pID] = true
|
|
|
|
}
|
|
|
|
teamCfg.PolicyIDs = polIDs
|
|
|
|
|
|
|
|
if teamAutomation == FailingPolicyWebhook {
|
|
|
|
wurl, err := url.Parse(settings.DestinationURL)
|
|
|
|
if err != nil {
|
|
|
|
return cfg, ctxerr.Wrapf(ctx, err, "parse webhook url: %s", settings.DestinationURL)
|
|
|
|
}
|
|
|
|
teamCfg.WebhookURL = wurl
|
|
|
|
teamCfg.HostBatchSize = settings.HostBatchSize
|
|
|
|
}
|
|
|
|
}
|
|
|
|
teamCfgs[teamID] = teamCfg
|
|
|
|
|
|
|
|
return teamCfg, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getActiveAutomation(webhook fleet.FailingPoliciesWebhookSettings, intgs fleet.Integrations) FailingPolicyAutomationType {
|
|
|
|
// only one automation (i.e. webhook or integration) can be enabled at a
|
|
|
|
// time, enforced when updating the appconfig or the team config.
|
|
|
|
if webhook.Enable {
|
|
|
|
return FailingPolicyWebhook
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for jira integrations
|
|
|
|
for _, j := range intgs.Jira {
|
|
|
|
if j.EnableFailingPolicies {
|
|
|
|
return FailingPolicyJira
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for zendesk integrations
|
|
|
|
for _, z := range intgs.Zendesk {
|
|
|
|
if z.EnableFailingPolicies {
|
|
|
|
return FailingPolicyZendesk
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|