fleet/server/policies/failing_policies.go

225 lines
7.3 KiB
Go

// 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 {
appConfig, err := ds.AppConfig(ctx)
if err != nil {
return ctxerr.Wrap(ctx, err, "getting app config")
}
// 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
getTeam := makeTeamConfigCache(ds, appConfig.Integrations)
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
}
func makeTeamConfigCache(ds fleet.Datastore, globalIntgs fleet.Integrations) func(ctx context.Context, teamID uint) (FailingPolicyAutomationConfig, error) {
teamCfgs := make(map[uint]FailingPolicyAutomationConfig)
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)
}
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)
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 ""
}