Skip to content
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
2 changes: 2 additions & 0 deletions changes/14722-activity-feed-webhooks
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added `activities_webhook` configuration option to allow for a webhook to be called when an activity is recorded. This can be used to send activity data to external services.
If the webhook response is a 429 error code, the webhook retries for up to 30 minutes.
88 changes: 79 additions & 9 deletions cmd/fleetctl/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ func TestApplyTeamSpecs(t *testing.T) {
return nil
}

ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down Expand Up @@ -545,7 +547,9 @@ func TestApplyAppConfig(t *testing.T) {
return userRoleSpecList, nil
}

ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down Expand Up @@ -705,7 +709,9 @@ func TestApplyAppConfigDryRunIssue(t *testing.T) {
return userRoleSpecList[1], nil
}

ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down Expand Up @@ -1051,7 +1057,9 @@ func TestApplyPolicies(t *testing.T) {
}
return nil, errors.New("unexpected team name!")
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down Expand Up @@ -1139,7 +1147,9 @@ func TestApplyAsGitOps(t *testing.T) {
ds.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) {
return gitOps, nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down Expand Up @@ -1664,7 +1674,9 @@ func TestApplyPacks(t *testing.T) {
ds.ListPacksFunc = func(ctx context.Context, opt fleet.PackListOptions) ([]*fleet.Pack, error) {
return nil, nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down Expand Up @@ -1714,7 +1726,9 @@ func TestApplyQueries(t *testing.T) {
appliedQueries = queries
return nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down Expand Up @@ -1819,7 +1833,9 @@ func TestApplyMacosSetup(t *testing.T) {
teamsByID := map[uint]*fleet.Team{
tm1.ID: tm1,
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
Expand Down Expand Up @@ -2585,7 +2601,9 @@ func TestApplySpecs(t *testing.T) {
}

// activities
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down Expand Up @@ -3647,6 +3665,58 @@ spec:
`,
wantErr: `422 Validation Failed: Couldn't turn on Windows MDM. Please configure Fleet with a certificate and key pair first.`,
},
{
desc: "activities_webhook empty destination_url",
spec: `
apiVersion: v1
kind: config
spec:
webhook_settings:
activities_webhook:
enable_activities_webhook: true
destination_url: ""
`,
wantErr: `422 Validation Failed: destination_url is required`,
},
{
desc: "activities_webhook bad destination_url 1",
spec: `
apiVersion: v1
kind: config
spec:
webhook_settings:
activities_webhook:
enable_activities_webhook: true
destination_url: ftp://host
`,
wantErr: `422 Validation Failed: destination_url must be http`,
},
{
desc: "activities_webhook bad destination_url 2",
spec: `
apiVersion: v1
kind: config
spec:
webhook_settings:
activities_webhook:
enable_activities_webhook: true
destination_url: /foo
`,
wantErr: `422 Validation Failed: destination_url must be http`,
},
{
desc: "activities_webhook bad destination_url 3",
spec: `
apiVersion: v1
kind: config
spec:
webhook_settings:
activities_webhook:
enable_activities_webhook: true
destination_url: foo
`,
wantErr: `422 Validation Failed: parse "foo": invalid URI`,
},
}
// NOTE: Integrations required fields are not tested (Jira/Zendesk) because
// they require a complex setup to mock the client that would communicate
Expand Down
9 changes: 7 additions & 2 deletions cmd/fleetctl/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"testing"
"time"

"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -52,7 +53,9 @@ func TestDeletePack(t *testing.T) {
Disabled: false,
}, true, nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down Expand Up @@ -95,7 +98,9 @@ func TestDeleteQuery(t *testing.T) {
ObserverCanRun: false,
}, nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}

Expand Down
4 changes: 3 additions & 1 deletion cmd/fleetctl/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2204,7 +2204,9 @@ func TestGetTeamsYAMLAndApply(t *testing.T) {
ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
return nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
Expand Down
26 changes: 20 additions & 6 deletions cmd/fleetctl/gitops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ func TestBasicGlobalGitOps(t *testing.T) {
return nil
}
ds.BatchSetScriptsFunc = func(ctx context.Context, tmID *uint, scripts []*fleet.Script) error { return nil }
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
ds.ListGlobalPoliciesFunc = func(ctx context.Context, opts fleet.ListOptions) ([]*fleet.Policy, error) { return nil, nil }
Expand Down Expand Up @@ -161,7 +163,9 @@ func TestBasicTeamGitOps(t *testing.T) {
) error {
return nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
ds.ListTeamPoliciesFunc = func(
Expand Down Expand Up @@ -274,7 +278,9 @@ func TestFullGlobalGitOps(t *testing.T) {
appliedScripts = scripts
return nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
var appliedMacProfiles []*fleet.MDMAppleConfigProfile
Expand Down Expand Up @@ -406,6 +412,8 @@ func TestFullGlobalGitOps(t *testing.T) {
assert.True(t, savedAppConfig.ActivityExpirySettings.ActivityExpiryEnabled)
assert.Equal(t, 60, savedAppConfig.ActivityExpirySettings.ActivityExpiryWindow)
assert.True(t, savedAppConfig.ServerSettings.AIFeaturesDisabled)
assert.True(t, savedAppConfig.WebhookSettings.ActivitiesWebhook.Enable)
assert.Equal(t, "https://activities_webhook_url", savedAppConfig.WebhookSettings.ActivitiesWebhook.DestinationURL)
}

func TestFullTeamGitOps(t *testing.T) {
Expand Down Expand Up @@ -447,7 +455,9 @@ func TestFullTeamGitOps(t *testing.T) {
appliedScripts = scripts
return nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
var appliedMacProfiles []*fleet.MDMAppleConfigProfile
Expand Down Expand Up @@ -720,7 +730,9 @@ func TestBasicGlobalAndTeamGitOps(t *testing.T) {
return nil, nil
}
ds.ListQueriesFunc = func(ctx context.Context, opts fleet.ListQueryOptions) ([]*fleet.Query, error) { return nil, nil }
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
Expand Down Expand Up @@ -1062,7 +1074,9 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
return nil, nil
}
ds.ListQueriesFunc = func(ctx context.Context, opts fleet.ListQueryOptions) ([]*fleet.Query, error) { return nil, nil }
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
ds.NewMDMAppleConfigProfileFunc = func(ctx context.Context, p fleet.MDMAppleConfigProfile) (*fleet.MDMAppleConfigProfile, error) {
Expand Down
17 changes: 13 additions & 4 deletions cmd/fleetctl/hosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"testing"
"time"

"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -54,7 +55,9 @@ func TestHostsTransferByHosts(t *testing.T) {
return &fleet.Team{ID: tid, Name: "team1"}, nil
}

ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
require.IsType(t, fleet.ActivityTypeTransferredHostsToTeam{}, activity)
return nil
}
Expand Down Expand Up @@ -123,7 +126,9 @@ func TestHostsTransferByLabel(t *testing.T) {
return &fleet.Team{ID: tid, Name: "team1"}, nil
}

ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
require.IsType(t, fleet.ActivityTypeTransferredHostsToTeam{}, activity)
return nil
}
Expand Down Expand Up @@ -191,7 +196,9 @@ func TestHostsTransferByStatus(t *testing.T) {
return &fleet.Team{ID: tid, Name: "team1"}, nil
}

ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
require.IsType(t, fleet.ActivityTypeTransferredHostsToTeam{}, activity)
return nil
}
Expand Down Expand Up @@ -248,7 +255,9 @@ func TestHostsTransferByStatusAndSearchQuery(t *testing.T) {
return &fleet.Team{ID: tid, Name: "team1"}, nil
}

ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
require.IsType(t, fleet.ActivityTypeTransferredHostsToTeam{}, activity)
return nil
}
Expand Down
4 changes: 3 additions & 1 deletion cmd/fleetctl/mdm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,9 @@ func setupDSMocks(ds *mock.Store, hostByUUID map[string]*fleet.Host, hostsByID m

return h.MDMInfo, nil
}
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
}
Expand Down
Loading