diff --git a/changes/issue-2456-add-policies-yaml b/changes/issue-2456-add-policies-yaml new file mode 100644 index 0000000000..2f52dcb006 --- /dev/null +++ b/changes/issue-2456-add-policies-yaml @@ -0,0 +1 @@ +* Add a way to apply policy changes with fleetctl diff --git a/cmd/fleetctl/apply.go b/cmd/fleetctl/apply.go index 525dd963c5..23ccc2889d 100644 --- a/cmd/fleetctl/apply.go +++ b/cmd/fleetctl/apply.go @@ -24,10 +24,11 @@ type specMetadata struct { } type specGroup struct { - Queries []*fleet.QuerySpec - Teams []*fleet.TeamSpec - Packs []*fleet.PackSpec - Labels []*fleet.LabelSpec + Queries []*fleet.QuerySpec + Teams []*fleet.TeamSpec + Packs []*fleet.PackSpec + Labels []*fleet.LabelSpec + Policies []*fleet.PolicySpec // This needs to be interface{} to allow for the patch logic. Otherwise we send a request that looks to the // server like the user explicitly set the zero values. AppConfig interface{} @@ -80,6 +81,13 @@ func specGroupFromBytes(b []byte) (*specGroup, error) { } specs.Labels = append(specs.Labels, labelSpec) + case fleet.PolicyKind: + var policySpec *fleet.PolicySpec + if err := yaml.Unmarshal(s.Spec, &policySpec); err != nil { + return nil, fmt.Errorf("unmarshaling "+kind+" spec: %w", err) + } + specs.Policies = append(specs.Policies, policySpec) + case fleet.AppConfigKind: if specs.AppConfig != nil { return nil, errors.New("config defined twice in the same file") @@ -178,6 +186,13 @@ func applyCommand() *cli.Command { logf(c, "[+] applied %d labels\n", len(specs.Labels)) } + if len(specs.Policies) > 0 { + if err := fleetClient.ApplyPolicies(specs.Policies); err != nil { + return fmt.Errorf("applying policies: %w", err) + } + logf(c, "[+] applied %d policies\n", len(specs.Policies)) + } + if len(specs.Packs) > 0 { if err := fleetClient.ApplyPacks(specs.Packs); err != nil { return fmt.Errorf("applying packs: %w", err) diff --git a/cmd/fleetctl/apply_test.go b/cmd/fleetctl/apply_test.go index a23227229c..566aaac6b5 100644 --- a/cmd/fleetctl/apply_test.go +++ b/cmd/fleetctl/apply_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/json" + "fmt" "io/ioutil" "os" "testing" @@ -273,3 +274,57 @@ spec: ) require.Nil(t, savedAppConfig) } + +func TestApplyPolicies(t *testing.T) { + _, ds := runServerWithMockedDS(t) + + var appliedPolicySpecs []*fleet.PolicySpec + ds.ApplyPolicySpecsFunc = func(ctx context.Context, authorID uint, specs []*fleet.PolicySpec) error { + appliedPolicySpecs = specs + return nil + } + ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) { + if name == "Team1" { + return &fleet.Team{ID: 123}, nil + } + return nil, fmt.Errorf("unexpected team name!") + } + + name := writeTmpYml(t, `--- +apiVersion: v1 +kind: policy +spec: + name: Is Gatekeeper enabled on macOS devices? + query: SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1; + description: Checks to make sure that the Gatekeeper feature is enabled on macOS devices. Gatekeeper tries to ensure only trusted software is run on a mac machine. + resolution: "Run the following command in the Terminal app: /usr/sbin/spctl --master-enable" + platform: darwin + team: Team1 +--- +apiVersion: v1 +kind: policy +spec: + name: Is disk encryption enabled on Windows devices? + query: SELECT 1 FROM bitlocker_info where protection_status = 1; + description: Checks to make sure that device encryption is enabled on Windows devices. + resolution: "Option 1: Select the Start button. Select Settings > Update & Security > Device encryption. If Device encryption doesn't appear, skip to Option 2. If device encryption is turned off, select Turn on. Option 2: Select the Start button. Under Windows System, select Control Panel. Select System and Security. Under BitLocker Drive Encryption, select Manage BitLocker. Select Turn on BitLocker and then follow the instructions." + platform: windows +--- +apiVersion: v1 +kind: policy +spec: + name: Is Filevault enabled on macOS devices? + query: SELECT 1 FROM disk_encryption WHERE user_uuid IS NOT “” AND filevault_status = ‘on’ LIMIT 1; + description: Checks to make sure that the Filevault feature is enabled on macOS devices. + resolution: "Choose Apple menu > System Preferences, then click Security & Privacy. Click the FileVault tab. Click the Lock icon, then enter an administrator name and password. Click Turn On FileVault." + platform: darwin +`) + + assert.Equal(t, "[+] applied 3 policies\n", runAppForTest(t, []string{"apply", "-f", name})) + assert.True(t, ds.ApplyPolicySpecsFuncInvoked) + assert.Len(t, appliedPolicySpecs, 3) + for _, p := range appliedPolicySpecs { + assert.NotEmpty(t, p.Platform) + } + assert.True(t, ds.TeamByNameFuncInvoked) +} diff --git a/cmd/fleetctl/preview.go b/cmd/fleetctl/preview.go index f5771b8ada..9695590f11 100644 --- a/cmd/fleetctl/preview.go +++ b/cmd/fleetctl/preview.go @@ -226,11 +226,6 @@ Use the stop and reset subcommands to manage the server and dependencies once st return fmt.Errorf("failed to apply updated app config: %w", err) } - fmt.Println("Applying Policies...") - if err := loadPolicies(client); err != nil { - fmt.Println("WARNING: Couldn't load policies:", err) - } - secrets, err := client.GetEnrollSecretSpec() if err != nil { return fmt.Errorf("Error retrieving enroll secret: %w", err) @@ -700,43 +695,6 @@ func killFromPIDFile(destDir string, pidFileName string, expectedExecName string return nil } -func loadPolicies(client *service.Client) error { - policies := []struct { - name, query, description, resolution, platform string - }{ - { - "Is Gatekeeper enabled on macOS devices?", - "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;", - "Checks to make sure that the Gatekeeper feature is enabled on macOS devices. Gatekeeper tries to ensure only trusted software is run on a mac machine.", - "Run the following command in the Terminal app: /usr/sbin/spctl --master-enable", - "darwin", - }, - { - "Is disk encryption enabled on Windows devices?", - "SELECT 1 FROM bitlocker_info where protection_status = 1;", - "Checks to make sure that device encryption is enabled on Windows devices.", - "Option 1: Select the Start button. Select Settings > Update & Security > Device encryption. If Device encryption doesn't appear, skip to Option 2. If device encryption is turned off, select Turn on. Option 2: Select the Start button. Under Windows System, select Control Panel. Select System and Security. Under BitLocker Drive Encryption, select Manage BitLocker. Select Turn on BitLocker and then follow the instructions.", - "windows", - }, - { - "Is Filevault enabled on macOS devices?", - `SELECT 1 FROM disk_encryption WHERE user_uuid IS NOT "" AND filevault_status = 'on' LIMIT 1;`, - "Checks to make sure that the Filevault feature is enabled on macOS devices.", - "Choose Apple menu > System Preferences, then click Security & Privacy. Click the FileVault tab. Click the Lock icon, then enter an administrator name and password. Click Turn On FileVault.", - "darwin", - }, - } - - for _, policy := range policies { - err := client.CreateGlobalPolicy(policy.name, policy.query, policy.description, policy.resolution, policy.platform) - if err != nil { - return fmt.Errorf("creating policy: %w", err) - } - } - - return nil -} - func openBrowser(url string) error { var cmd *exec.Cmd switch runtime.GOOS { diff --git a/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml b/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml index ab682895a1..06451e9ec3 100644 --- a/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml +++ b/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml @@ -633,3 +633,30 @@ spec: query: SELECT * FROM apps WHERE path LIKE '/Applications/%' AND name IN ("Photoshop.app", "Adobe XD.app", "Sketch.app", "Illustrator.app") AND last_opened_time < (( SELECT unix_time FROM time ) - 2592000000000 ); purpose: Informational contributors: DominusKelvin +--- +apiVersion: v1 +kind: policy +spec: + name: Is Gatekeeper enabled on macOS devices? + query: SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1; + description: Checks to make sure that the Gatekeeper feature is enabled on macOS devices. Gatekeeper tries to ensure only trusted software is run on a mac machine. + resolution: "Run the following command in the Terminal app: /usr/sbin/spctl --master-enable" + platform: darwin +--- +apiVersion: v1 +kind: policy +spec: + name: Is disk encryption enabled on Windows devices? + query: SELECT 1 FROM bitlocker_info where protection_status = 1; + description: Checks to make sure that device encryption is enabled on Windows devices. + resolution: "Option 1: Select the Start button. Select Settings > Update & Security > Device encryption. If Device encryption doesn't appear, skip to Option 2. If device encryption is turned off, select Turn on. Option 2: Select the Start button. Under Windows System, select Control Panel. Select System and Security. Under BitLocker Drive Encryption, select Manage BitLocker. Select Turn on BitLocker and then follow the instructions." + platform: windows +--- +apiVersion: v1 +kind: policy +spec: + name: Is Filevault enabled on macOS devices? + query: SELECT 1 FROM disk_encryption WHERE user_uuid IS NOT “” AND filevault_status = ‘on’ LIMIT 1; + description: Checks to make sure that the Filevault feature is enabled on macOS devices. + resolution: "Choose Apple menu > System Preferences, then click Security & Privacy. Click the FileVault tab. Click the Lock icon, then enter an administrator name and password. Click Turn On FileVault." + platform: darwin diff --git a/docs/03-Contributing/07-API-for-contributors.md b/docs/03-Contributing/07-API-for-contributors.md index 74b4a3ee66..d5edb10e4c 100644 --- a/docs/03-Contributing/07-API-for-contributors.md +++ b/docs/03-Contributing/07-API-for-contributors.md @@ -21,6 +21,7 @@ Unlike the [Fleet REST API documentation](../01-Using-Fleet/03-REST-API.md), the - [Retrieve live query results (standard WebSocket API)](#retrieve-live-query-results-standard-websocket-api) - [Retrieve live query results (SockJS)](#retrieve-live-query-results-sockjs) - [Run live query by name](#run-live-query-by-name) +- [Apply policies spec](#apply-policies-spec) ### Get queries spec @@ -1225,3 +1226,47 @@ o } ] ``` + +### Apply policies spec + +Creates and/or modifies the policies included in the specs list. To modify an existing policy, the name of the query included in `specs` must already be used by an existing policy. If a policy with the specified name doesn't exist in Fleet, a new policy will be created. + +NOTE: when updating a policy, team and platform will be ignored. + +`POST /api/v1/fleet/spec/policies` + +#### Parameters + +| Name | Type | In | Description | +| ----- | ---- | ---- | ----------------------------------------------------------------- | +| specs | list | body | **Required.** The list of the policies to be created or modified. | + +#### Example + +`POST /api/v1/fleet/spec/policies` + +##### Request body + +```json +{ + "specs": [ + { + "name": "new policy", + "description": "This will be a new policy because a policy with the name 'new policy' doesn't exist in Fleet.", + "query": "SELECT * FROM osquery_info", + "resolution": "some resolution steps here" + }, + { + "name": "Is Filevault enabled on macOS devices?", + "query": "SELECT 1 FROM disk_encryption WHERE user_uuid IS NOT “” AND filevault_status = ‘on’ LIMIT 1;", + "description": "Checks to make sure that the Filevault feature is enabled on macOS devices.", + "resolution": "Choose Apple menu > System Preferences, then click Security & Privacy. Click the FileVault tab. Click the Lock icon, then enter an administrator name and password. Click Turn On FileVault.", + "platform": "darwin" + } + ] +} +``` + +##### Default response + +`Status: 200` \ No newline at end of file diff --git a/server/datastore/mysql/policies.go b/server/datastore/mysql/policies.go index 3681d6ba8c..2b791e6f3a 100644 --- a/server/datastore/mysql/policies.go +++ b/server/datastore/mysql/policies.go @@ -386,7 +386,7 @@ func (ds *Datastore) TeamPolicy(ctx context.Context, teamID uint, policyID uint) } // ApplyPolicySpecs applies the given policy specs, creating new policies and updating the ones that -// already exist (a policy is identified by its name and the team it belongs to). +// already exist (a policy is identified by its name). // // NOTE: Similar to ApplyQueries, ApplyPolicySpecs will update the author_id of the policies // that are updated.