mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Add new fleet_desktop
property to config object (#6151)
This commit is contained in:
parent
7c756bcd44
commit
a3ab5646f5
4
changes/issue-5408-transparency-url
Normal file
4
changes/issue-5408-transparency-url
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
- Add `fleet_desktop.transparency_url` to `app_config_json`
|
||||||
|
- Set default `transparency_url="https://fleetdm.com/transparency`
|
||||||
|
- Enable Fleet Premium licensees to set custom `transparency_url` via REST API and `fleetctl apply`
|
||||||
|
- Add `transparency_url` to `GET /device/{token}` endpoint response
|
@ -440,6 +440,8 @@ func TestGetConfig(t *testing.T) {
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: config
|
kind: config
|
||||||
spec:
|
spec:
|
||||||
|
fleet_desktop:
|
||||||
|
transparency_url: https://fleetdm.com/transparency
|
||||||
host_expiry_settings:
|
host_expiry_settings:
|
||||||
host_expiry_enabled: false
|
host_expiry_enabled: false
|
||||||
host_expiry_window: 0
|
host_expiry_window: 0
|
||||||
@ -499,7 +501,7 @@ spec:
|
|||||||
enable_vulnerabilities_webhook: false
|
enable_vulnerabilities_webhook: false
|
||||||
host_batch_size: 0
|
host_batch_size: 0
|
||||||
`
|
`
|
||||||
expectedJson := `{"kind":"config","apiVersion":"v1","spec":{"org_info":{"org_name":"","org_logo_url":""},"server_settings":{"server_url":"","live_query_disabled":false,"enable_analytics":false,"deferred_save_host":false},"smtp_settings":{"enable_smtp":false,"configured":false,"sender_address":"","server":"","port":0,"authentication_type":"","user_name":"","password":"","enable_ssl_tls":false,"authentication_method":"","domain":"","verify_ssl_certs":false,"enable_start_tls":false},"host_expiry_settings":{"host_expiry_enabled":false,"host_expiry_window":0},"host_settings":{"enable_host_users":true,"enable_software_inventory":false},"sso_settings":{"entity_id":"","issuer_uri":"","idp_image_url":"","metadata":"","metadata_url":"","idp_name":"","enable_sso":false,"enable_sso_idp_login":false},"vulnerability_settings":{"databases_path":"/some/path"},"webhook_settings":{"host_status_webhook":{"enable_host_status_webhook":false,"destination_url":"","host_percentage":0,"days_count":0},"failing_policies_webhook":{"enable_failing_policies_webhook":false,"destination_url":"","policy_ids":null,"host_batch_size":0},"vulnerabilities_webhook":{"enable_vulnerabilities_webhook":false,"destination_url":"","host_batch_size":0},"interval":"0s"},"integrations":{"jira":null,"zendesk":null}}}
|
expectedJson := `{"kind":"config","apiVersion":"v1","spec":{"org_info":{"org_name":"","org_logo_url":""},"server_settings":{"server_url":"","live_query_disabled":false,"enable_analytics":false,"deferred_save_host":false},"smtp_settings":{"enable_smtp":false,"configured":false,"sender_address":"","server":"","port":0,"authentication_type":"","user_name":"","password":"","enable_ssl_tls":false,"authentication_method":"","domain":"","verify_ssl_certs":false,"enable_start_tls":false},"host_expiry_settings":{"host_expiry_enabled":false,"host_expiry_window":0},"host_settings":{"enable_host_users":true,"enable_software_inventory":false},"sso_settings":{"entity_id":"","issuer_uri":"","idp_image_url":"","metadata":"","metadata_url":"","idp_name":"","enable_sso":false,"enable_sso_idp_login":false},"fleet_desktop":{"transparency_url":"https://fleetdm.com/transparency"},"vulnerability_settings":{"databases_path":"/some/path"},"webhook_settings":{"host_status_webhook":{"enable_host_status_webhook":false,"destination_url":"","host_percentage":0,"days_count":0},"failing_policies_webhook":{"enable_failing_policies_webhook":false,"destination_url":"","policy_ids":null,"host_batch_size":0},"vulnerabilities_webhook":{"enable_vulnerabilities_webhook":false,"destination_url":"","host_batch_size":0},"interval":"0s"},"integrations":{"jira":null,"zendesk":null}}}
|
||||||
`
|
`
|
||||||
|
|
||||||
assert.Equal(t, expectedYaml, runAppForTest(t, []string{"get", "config"}))
|
assert.Equal(t, expectedYaml, runAppForTest(t, []string{"get", "config"}))
|
||||||
@ -512,6 +514,8 @@ spec:
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: config
|
kind: config
|
||||||
spec:
|
spec:
|
||||||
|
fleet_desktop:
|
||||||
|
transparency_url: https://fleetdm.com/transparency
|
||||||
host_expiry_settings:
|
host_expiry_settings:
|
||||||
host_expiry_enabled: false
|
host_expiry_enabled: false
|
||||||
host_expiry_window: 0
|
host_expiry_window: 0
|
||||||
@ -602,7 +606,7 @@ spec:
|
|||||||
enable_vulnerabilities_webhook: false
|
enable_vulnerabilities_webhook: false
|
||||||
host_batch_size: 0
|
host_batch_size: 0
|
||||||
`
|
`
|
||||||
expectedJson := `{"kind":"config","apiVersion":"v1","spec":{"org_info":{"org_name":"","org_logo_url":""},"server_settings":{"server_url":"","live_query_disabled":false,"enable_analytics":false,"deferred_save_host":false},"smtp_settings":{"enable_smtp":false,"configured":false,"sender_address":"","server":"","port":0,"authentication_type":"","user_name":"","password":"","enable_ssl_tls":false,"authentication_method":"","domain":"","verify_ssl_certs":false,"enable_start_tls":false},"host_expiry_settings":{"host_expiry_enabled":false,"host_expiry_window":0},"host_settings":{"enable_host_users":true,"enable_software_inventory":false},"sso_settings":{"entity_id":"","issuer_uri":"","idp_image_url":"","metadata":"","metadata_url":"","idp_name":"","enable_sso":false,"enable_sso_idp_login":false},"vulnerability_settings":{"databases_path":"/some/path"},"webhook_settings":{"host_status_webhook":{"enable_host_status_webhook":false,"destination_url":"","host_percentage":0,"days_count":0},"failing_policies_webhook":{"enable_failing_policies_webhook":false,"destination_url":"","policy_ids":null,"host_batch_size":0},"vulnerabilities_webhook":{"enable_vulnerabilities_webhook":false,"destination_url":"","host_batch_size":0},"interval":"0s"},"integrations":{"jira":null,"zendesk":null},"update_interval":{"osquery_detail":"1h0m0s","osquery_policy":"1h0m0s"},"vulnerabilities":{"databases_path":"","periodicity":"0s","cpe_database_url":"","cve_feed_prefix_url":"","current_instance_checks":"","disable_data_sync":false,"recent_vulnerability_max_age":"0s"},"license":{"tier":"free","expiration":"0001-01-01T00:00:00Z"},"logging":{"debug":true,"json":false,"result":{"plugin":"filesystem","config":{"enable_log_compression":false,"enable_log_rotation":false,"result_log_file":"/dev/null","status_log_file":"/dev/null"}},"status":{"plugin":"filesystem","config":{"enable_log_compression":false,"enable_log_rotation":false,"result_log_file":"/dev/null","status_log_file":"/dev/null"}}}}}
|
expectedJson := `{"kind":"config","apiVersion":"v1","spec":{"org_info":{"org_name":"","org_logo_url":""},"server_settings":{"server_url":"","live_query_disabled":false,"enable_analytics":false,"deferred_save_host":false},"smtp_settings":{"enable_smtp":false,"configured":false,"sender_address":"","server":"","port":0,"authentication_type":"","user_name":"","password":"","enable_ssl_tls":false,"authentication_method":"","domain":"","verify_ssl_certs":false,"enable_start_tls":false},"host_expiry_settings":{"host_expiry_enabled":false,"host_expiry_window":0},"host_settings":{"enable_host_users":true,"enable_software_inventory":false},"sso_settings":{"entity_id":"","issuer_uri":"","idp_image_url":"","metadata":"","metadata_url":"","idp_name":"","enable_sso":false,"enable_sso_idp_login":false},"fleet_desktop":{"transparency_url":"https://fleetdm.com/transparency"},"vulnerability_settings":{"databases_path":"/some/path"},"webhook_settings":{"host_status_webhook":{"enable_host_status_webhook":false,"destination_url":"","host_percentage":0,"days_count":0},"failing_policies_webhook":{"enable_failing_policies_webhook":false,"destination_url":"","policy_ids":null,"host_batch_size":0},"vulnerabilities_webhook":{"enable_vulnerabilities_webhook":false,"destination_url":"","host_batch_size":0},"interval":"0s"},"integrations":{"jira":null,"zendesk":null},"update_interval":{"osquery_detail":"1h0m0s","osquery_policy":"1h0m0s"},"vulnerabilities":{"databases_path":"","periodicity":"0s","cpe_database_url":"","cve_feed_prefix_url":"","current_instance_checks":"","disable_data_sync":false,"recent_vulnerability_max_age":"0s"},"license":{"tier":"free","expiration":"0001-01-01T00:00:00Z"},"logging":{"debug":true,"json":false,"result":{"plugin":"filesystem","config":{"enable_log_compression":false,"enable_log_rotation":false,"result_log_file":"/dev/null","status_log_file":"/dev/null"}},"status":{"plugin":"filesystem","config":{"enable_log_compression":false,"enable_log_rotation":false,"result_log_file":"/dev/null","status_log_file":"/dev/null"}}}}}
|
||||||
`
|
`
|
||||||
|
|
||||||
assert.Equal(t, expectedYaml, runAppForTest(t, []string{"get", "config", "--include-server-config"}))
|
assert.Equal(t, expectedYaml, runAppForTest(t, []string{"get", "config", "--include-server-config"}))
|
||||||
|
@ -1014,6 +1014,7 @@ Modifies the Fleet's configuration with the supplied information.
|
|||||||
| host_expiry_enabled | boolean | body | _Host expiry settings_. When enabled, allows automatic cleanup of hosts that have not communicated with Fleet in some number of days. |
|
| host_expiry_enabled | boolean | body | _Host expiry settings_. When enabled, allows automatic cleanup of hosts that have not communicated with Fleet in some number of days. |
|
||||||
| host_expiry_window | integer | body | _Host expiry settings_. If a host has not communicated with Fleet in the specified number of days, it will be removed. |
|
| host_expiry_window | integer | body | _Host expiry settings_. If a host has not communicated with Fleet in the specified number of days, it will be removed. |
|
||||||
| agent_options | objects | body | The agent_options spec that is applied to all hosts. In Fleet 4.0.0 the `api/v1/fleet/spec/osquery_options` endpoints were removed. |
|
| agent_options | objects | body | The agent_options spec that is applied to all hosts. In Fleet 4.0.0 the `api/v1/fleet/spec/osquery_options` endpoints were removed. |
|
||||||
|
| transparency_url | string | body | _Fleet Desktop_. The URL used to display transparency information to users of Fleet Desktop. **Requires Fleet Premium license** |
|
||||||
| enable_host_status_webhook | boolean | body | _webhook_settings.host_status_webhook settings_. Whether or not the host status webhook is enabled. |
|
| enable_host_status_webhook | boolean | body | _webhook_settings.host_status_webhook settings_. Whether or not the host status webhook is enabled. |
|
||||||
| destination_url | string | body | _webhook_settings.host_status_webhook settings_. The URL to deliver the webhook request to. |
|
| destination_url | string | body | _webhook_settings.host_status_webhook settings_. The URL to deliver the webhook request to. |
|
||||||
| host_percentage | integer | body | _webhook_settings.host_status_webhook settings_. The minimum percentage of hosts that must fail to check in to Fleet in order to trigger the webhook request. |
|
| host_percentage | integer | body | _webhook_settings.host_status_webhook settings_. The minimum percentage of hosts that must fail to check in to Fleet in order to trigger the webhook request. |
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
MigrationClient.AddMigration(Up_20220608113128, Down_20220608113128)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Up_20220608113128(tx *sql.Tx) error {
|
||||||
|
err := updateAppConfigJSON(tx, func(config *fleet.AppConfig) error {
|
||||||
|
if config.FleetDesktop.TransparencyURL != "" {
|
||||||
|
return errors.New("unexpected transparency_url value in app_config_json")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Down_20220608113128(tx *sql.Tx) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUp_20220608113128(t *testing.T) {
|
||||||
|
db := applyUpToPrev(t)
|
||||||
|
|
||||||
|
var prevRaw []byte
|
||||||
|
var prevConfig fleet.AppConfig
|
||||||
|
err := db.Get(&prevRaw, `SELECT json_value FROM app_config_json`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = json.Unmarshal(prevRaw, &prevConfig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, prevConfig.FleetDesktop.TransparencyURL)
|
||||||
|
|
||||||
|
applyNext(t, db)
|
||||||
|
|
||||||
|
var newRaw []byte
|
||||||
|
var newConfig fleet.AppConfig
|
||||||
|
err = db.Get(&newRaw, `SELECT json_value FROM app_config_json`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = json.Unmarshal(newRaw, &newConfig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "", newConfig.FleetDesktop.TransparencyURL)
|
||||||
|
}
|
@ -2,13 +2,14 @@ package tables
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||||
"github.com/fleetdm/goose"
|
"github.com/fleetdm/goose"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var MigrationClient = goose.New("migration_status_tables", goose.MySqlDialect{})
|
||||||
MigrationClient = goose.New("migration_status_tables", goose.MySqlDialect{})
|
|
||||||
)
|
|
||||||
|
|
||||||
func columnExists(tx *sql.Tx, table, column string) bool {
|
func columnExists(tx *sql.Tx, table, column string) bool {
|
||||||
var count int
|
var count int
|
||||||
@ -31,3 +32,37 @@ WHERE
|
|||||||
|
|
||||||
return count > 0
|
return count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateAppConfigJSON updates the `json_value` stored in the `app_config_json` after applying the
|
||||||
|
// supplied callback to the current config object.
|
||||||
|
func updateAppConfigJSON(tx *sql.Tx, fn func(config *fleet.AppConfig) error) error {
|
||||||
|
var raw []byte
|
||||||
|
row := tx.QueryRow(`SELECT json_value FROM app_config_json LIMIT 1`)
|
||||||
|
if err := row.Scan(&raw); err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Wrap(err, "select app_config_json")
|
||||||
|
}
|
||||||
|
|
||||||
|
var config fleet.AppConfig
|
||||||
|
if err := json.Unmarshal(raw, &config); err != nil {
|
||||||
|
return errors.Wrap(err, "unmarshal app_config_json")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fn(&config); err != nil {
|
||||||
|
return errors.Wrap(err, "callback app_config_json")
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "marshal updated app_config_json")
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStmt = `UPDATE app_config_json SET json_value = ? WHERE id = 1`
|
||||||
|
if _, err := tx.Exec(updateStmt, b); err != nil {
|
||||||
|
return errors.Wrap(err, "update app_config_json")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -36,7 +36,7 @@ CREATE TABLE `app_config_json` (
|
|||||||
UNIQUE KEY `id` (`id`)
|
UNIQUE KEY `id` (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
INSERT INTO `app_config_json` VALUES (1,'{\"org_info\": {\"org_name\": \"\", \"org_logo_url\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_sso_idp_login\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"host_settings\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"deferred_save_host\": false, \"live_query_disabled\": false}, \"webhook_settings\": {\"interval\": \"24h0m0s\", \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
|
INSERT INTO `app_config_json` VALUES (1,'{\"org_info\": {\"org_name\": \"\", \"org_logo_url\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_sso_idp_login\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"host_settings\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"deferred_save_host\": false, \"live_query_disabled\": false}, \"webhook_settings\": {\"interval\": \"24h0m0s\", \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
|
||||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
/*!40101 SET character_set_client = utf8 */;
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
CREATE TABLE `carve_blocks` (
|
CREATE TABLE `carve_blocks` (
|
||||||
|
@ -114,6 +114,8 @@ type AppConfig struct {
|
|||||||
SMTPTest bool `json:"smtp_test,omitempty"`
|
SMTPTest bool `json:"smtp_test,omitempty"`
|
||||||
// SSOSettings is single sign on settings
|
// SSOSettings is single sign on settings
|
||||||
SSOSettings SSOSettings `json:"sso_settings"`
|
SSOSettings SSOSettings `json:"sso_settings"`
|
||||||
|
// FleetDesktop holds settings for Fleet Desktop that can be changed via the API.
|
||||||
|
FleetDesktop FleetDesktopSettings `json:"fleet_desktop"`
|
||||||
|
|
||||||
// VulnerabilitySettings defines how fleet will behave while scanning for vulnerabilities in the host software
|
// VulnerabilitySettings defines how fleet will behave while scanning for vulnerabilities in the host software
|
||||||
VulnerabilitySettings VulnerabilitySettings `json:"vulnerability_settings"`
|
VulnerabilitySettings VulnerabilitySettings `json:"vulnerability_settings"`
|
||||||
@ -261,6 +263,15 @@ type HostSettings struct {
|
|||||||
AdditionalQueries *json.RawMessage `json:"additional_queries,omitempty"`
|
AdditionalQueries *json.RawMessage `json:"additional_queries,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FleetDesktopSettings contains settings used to configure Fleet Desktop.
|
||||||
|
type FleetDesktopSettings struct {
|
||||||
|
// TransparencyURL is the URL used for the “Transparency” link in the Fleet Desktop menu.
|
||||||
|
TransparencyURL string `json:"transparency_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTransparencyURL is the default URL used for the “Transparency” link in the Fleet Desktop menu.
|
||||||
|
const DefaultTransparencyURL = "https://fleetdm.com/transparency"
|
||||||
|
|
||||||
type OrderDirection int
|
type OrderDirection int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -75,6 +75,14 @@ func getAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet.Se
|
|||||||
hostExpirySettings = config.HostExpirySettings
|
hostExpirySettings = config.HostExpirySettings
|
||||||
agentOptions = config.AgentOptions
|
agentOptions = config.AgentOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transparencyURL := fleet.DefaultTransparencyURL
|
||||||
|
// Fleet Premium license is required for custom transparency url
|
||||||
|
if license.Tier == "premium" && config.FleetDesktop.TransparencyURL != "" {
|
||||||
|
transparencyURL = config.FleetDesktop.TransparencyURL
|
||||||
|
}
|
||||||
|
fleetDesktop := fleet.FleetDesktopSettings{TransparencyURL: transparencyURL}
|
||||||
|
|
||||||
hostSettings := config.HostSettings
|
hostSettings := config.HostSettings
|
||||||
response := appConfigResponse{
|
response := appConfigResponse{
|
||||||
AppConfig: fleet.AppConfig{
|
AppConfig: fleet.AppConfig{
|
||||||
@ -88,6 +96,8 @@ func getAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet.Se
|
|||||||
HostExpirySettings: hostExpirySettings,
|
HostExpirySettings: hostExpirySettings,
|
||||||
AgentOptions: agentOptions,
|
AgentOptions: agentOptions,
|
||||||
|
|
||||||
|
FleetDesktop: fleetDesktop,
|
||||||
|
|
||||||
WebhookSettings: config.WebhookSettings,
|
WebhookSettings: config.WebhookSettings,
|
||||||
Integrations: config.Integrations,
|
Integrations: config.Integrations,
|
||||||
},
|
},
|
||||||
@ -157,6 +167,11 @@ func modifyAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet
|
|||||||
if response.SMTPSettings.SMTPPassword != "" {
|
if response.SMTPSettings.SMTPPassword != "" {
|
||||||
response.SMTPSettings.SMTPPassword = fleet.MaskedPassword
|
response.SMTPSettings.SMTPPassword = fleet.MaskedPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if license.Tier != "premium" || response.FleetDesktop.TransparencyURL == "" {
|
||||||
|
response.FleetDesktop.TransparencyURL = fleet.DefaultTransparencyURL
|
||||||
|
}
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +187,11 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte) (*fleet.AppCo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
license, err := svc.License(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
oldSmtpSettings := appConfig.SMTPSettings
|
oldSmtpSettings := appConfig.SMTPSettings
|
||||||
|
|
||||||
storedJiraByProjectKey, err := fleet.IndexJiraIntegrations(appConfig.Integrations.Jira)
|
storedJiraByProjectKey, err := fleet.IndexJiraIntegrations(appConfig.Integrations.Jira)
|
||||||
@ -243,6 +263,19 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte) (*fleet.AppCo
|
|||||||
}
|
}
|
||||||
appConfig.Integrations.Zendesk = newAppConfig.Integrations.Zendesk
|
appConfig.Integrations.Zendesk = newAppConfig.Integrations.Zendesk
|
||||||
|
|
||||||
|
transparencyURL := appConfig.FleetDesktop.TransparencyURL
|
||||||
|
if transparencyURL != "" && license.Tier != "premium" {
|
||||||
|
invalid.Append("transparency_url", ErrMissingLicense.Error())
|
||||||
|
return nil, ctxerr.Wrap(ctx, invalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := url.Parse(transparencyURL); err != nil {
|
||||||
|
invalid.Append("transparency_url", err.Error())
|
||||||
|
return nil, ctxerr.Wrap(ctx, invalid)
|
||||||
|
|
||||||
|
}
|
||||||
|
appConfig.FleetDesktop.TransparencyURL = transparencyURL
|
||||||
|
|
||||||
if err := svc.ds.SaveAppConfig(ctx, appConfig); err != nil {
|
if err := svc.ds.SaveAppConfig(ctx, appConfig); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -389,3 +389,95 @@ func TestModifyAppConfigSMTPConfigured(t *testing.T) {
|
|||||||
require.False(t, dsAppConfig.SMTPSettings.SMTPEnabled)
|
require.False(t, dsAppConfig.SMTPSettings.SMTPEnabled)
|
||||||
require.False(t, dsAppConfig.SMTPSettings.SMTPConfigured)
|
require.False(t, dsAppConfig.SMTPSettings.SMTPConfigured)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTransparencyURL tests that Fleet Premium licensees can use custom transparency urls and Fleet
|
||||||
|
// Free licensees are restricted to the default transparency url.
|
||||||
|
func TestTransparencyURL(t *testing.T) {
|
||||||
|
ds := new(mock.Store)
|
||||||
|
|
||||||
|
admin := &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}
|
||||||
|
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: admin})
|
||||||
|
|
||||||
|
checkLicenseErr := func(t *testing.T, shouldFail bool, err error) {
|
||||||
|
if shouldFail {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "missing or invalid license")
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
licenseTier string
|
||||||
|
initialURL string
|
||||||
|
newURL string
|
||||||
|
expectedURL string
|
||||||
|
shouldFailModify bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "customURL",
|
||||||
|
licenseTier: "free",
|
||||||
|
initialURL: "",
|
||||||
|
newURL: "customURL",
|
||||||
|
expectedURL: "",
|
||||||
|
shouldFailModify: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "customURL",
|
||||||
|
licenseTier: "premium",
|
||||||
|
initialURL: "",
|
||||||
|
newURL: "customURL",
|
||||||
|
expectedURL: "customURL",
|
||||||
|
shouldFailModify: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptyURL",
|
||||||
|
licenseTier: "free",
|
||||||
|
initialURL: "",
|
||||||
|
newURL: "",
|
||||||
|
expectedURL: "",
|
||||||
|
shouldFailModify: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptyURL",
|
||||||
|
licenseTier: "premium",
|
||||||
|
initialURL: "customURL",
|
||||||
|
newURL: "",
|
||||||
|
expectedURL: "",
|
||||||
|
shouldFailModify: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
svc := newTestService(t, ds, nil, nil, &TestServerOpts{License: &fleet.LicenseInfo{Tier: tt.licenseTier}})
|
||||||
|
|
||||||
|
dsAppConfig := &fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: tt.initialURL}}
|
||||||
|
|
||||||
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||||
|
return dsAppConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.SaveAppConfigFunc = func(ctx context.Context, conf *fleet.AppConfig) error {
|
||||||
|
*dsAppConfig = *conf
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ac, err := svc.AppConfig(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.initialURL, ac.FleetDesktop.TransparencyURL)
|
||||||
|
|
||||||
|
raw, err := json.Marshal(fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: tt.newURL}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
modified, err := svc.ModifyAppConfig(ctx, raw)
|
||||||
|
checkLicenseErr(t, tt.shouldFailModify, err)
|
||||||
|
|
||||||
|
if modified != nil {
|
||||||
|
require.Equal(t, tt.expectedURL, modified.FleetDesktop.TransparencyURL)
|
||||||
|
ac, err = svc.AppConfig(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.expectedURL, ac.FleetDesktop.TransparencyURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ func (r *getDeviceHostRequest) deviceAuthToken() string {
|
|||||||
type getDeviceHostResponse struct {
|
type getDeviceHostResponse struct {
|
||||||
Host *HostDetailResponse `json:"host"`
|
Host *HostDetailResponse `json:"host"`
|
||||||
OrgLogoURL string `json:"org_logo_url"`
|
OrgLogoURL string `json:"org_logo_url"`
|
||||||
|
TransparencyURL string `json:"transparency_url"`
|
||||||
Err error `json:"error,omitempty"`
|
Err error `json:"error,omitempty"`
|
||||||
License fleet.LicenseInfo `json:"license"`
|
License fleet.LicenseInfo `json:"license"`
|
||||||
}
|
}
|
||||||
@ -64,9 +65,15 @@ func getDeviceHostEndpoint(ctx context.Context, request interface{}, svc fleet.S
|
|||||||
return getDeviceHostResponse{Err: err}, nil
|
return getDeviceHostResponse{Err: err}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transparencyURL := fleet.DefaultTransparencyURL
|
||||||
|
if license.Tier == "premium" && ac.FleetDesktop.TransparencyURL != "" {
|
||||||
|
transparencyURL = ac.FleetDesktop.TransparencyURL
|
||||||
|
}
|
||||||
|
|
||||||
return getDeviceHostResponse{
|
return getDeviceHostResponse{
|
||||||
Host: resp,
|
Host: resp,
|
||||||
OrgLogoURL: ac.OrgInfo.OrgLogoURL,
|
OrgLogoURL: ac.OrgInfo.OrgLogoURL,
|
||||||
|
TransparencyURL: transparencyURL,
|
||||||
License: *license,
|
License: *license,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -4634,6 +4634,71 @@ func (s *integrationTestSuite) TestDeviceAuthenticatedEndpoints() {
|
|||||||
require.NotNil(t, apiFeaturesResp.Features)
|
require.NotNil(t, apiFeaturesResp.Features)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDefaultTransparencyURL tests that Fleet Free licensees are restricted to the default transparency url.
|
||||||
|
func (s *integrationTestSuite) TestDefaultTransparencyURL() {
|
||||||
|
t := s.T()
|
||||||
|
|
||||||
|
host, err := s.ds.NewHost(context.Background(), &fleet.Host{
|
||||||
|
DetailUpdatedAt: time.Now(),
|
||||||
|
LabelUpdatedAt: time.Now(),
|
||||||
|
PolicyUpdatedAt: time.Now(),
|
||||||
|
SeenTime: time.Now().Add(-1 * time.Minute),
|
||||||
|
OsqueryHostID: t.Name(),
|
||||||
|
NodeKey: t.Name(),
|
||||||
|
UUID: uuid.New().String(),
|
||||||
|
Hostname: fmt.Sprintf("%sfoo.local", t.Name()),
|
||||||
|
Platform: "darwin",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create device token for host
|
||||||
|
token := "token_test_default_transparency_url"
|
||||||
|
mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error {
|
||||||
|
_, err := db.ExecContext(context.Background(), `INSERT INTO host_device_auth (host_id, token) VALUES (?, ?)`, host.ID, token)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
// confirm initial default url
|
||||||
|
acResp := appConfigResponse{}
|
||||||
|
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
|
||||||
|
require.NotNil(t, acResp)
|
||||||
|
require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL)
|
||||||
|
|
||||||
|
// confirm device endpoint returns initial default url
|
||||||
|
deviceResp := &getDeviceHostResponse{}
|
||||||
|
rawResp := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
|
||||||
|
json.NewDecoder(rawResp.Body).Decode(deviceResp)
|
||||||
|
rawResp.Body.Close()
|
||||||
|
require.NoError(t, deviceResp.Err)
|
||||||
|
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
|
||||||
|
|
||||||
|
// empty string applies default url
|
||||||
|
acResp = appConfigResponse{}
|
||||||
|
s.DoJSON("PATCH", "/api/latest/fleet/config", fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: ""}}, http.StatusOK, &acResp)
|
||||||
|
require.NotNil(t, acResp)
|
||||||
|
require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL)
|
||||||
|
|
||||||
|
// device endpoint returns default url
|
||||||
|
deviceResp = &getDeviceHostResponse{}
|
||||||
|
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
|
||||||
|
json.NewDecoder(rawResp.Body).Decode(deviceResp)
|
||||||
|
rawResp.Body.Close()
|
||||||
|
require.NoError(t, deviceResp.Err)
|
||||||
|
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
|
||||||
|
|
||||||
|
// modify transparency url with custom url fails
|
||||||
|
acResp = appConfigResponse{}
|
||||||
|
s.DoJSON("PATCH", "/api/latest/fleet/config", fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: "customURL"}}, http.StatusUnprocessableEntity, &acResp)
|
||||||
|
|
||||||
|
// device endpoint still returns default url
|
||||||
|
deviceResp = &getDeviceHostResponse{}
|
||||||
|
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
|
||||||
|
json.NewDecoder(rawResp.Body).Decode(deviceResp)
|
||||||
|
rawResp.Body.Close()
|
||||||
|
require.NoError(t, deviceResp.Err)
|
||||||
|
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *integrationTestSuite) TestModifyUser() {
|
func (s *integrationTestSuite) TestModifyUser() {
|
||||||
t := s.T()
|
t := s.T()
|
||||||
|
|
||||||
|
@ -1064,3 +1064,70 @@ func (s *integrationEnterpriseTestSuite) TestListDevicePolicies() {
|
|||||||
require.Equal(t, "http://example.com/logo", getDeviceHostResp.OrgLogoURL)
|
require.Equal(t, "http://example.com/logo", getDeviceHostResp.OrgLogoURL)
|
||||||
require.Len(t, *getDeviceHostResp.Host.Policies, 2)
|
require.Len(t, *getDeviceHostResp.Host.Policies, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCustomTransparencyURL tests that Fleet Premium licensees can use custom transparency urls.
|
||||||
|
func (s *integrationEnterpriseTestSuite) TestCustomTransparencyURL() {
|
||||||
|
t := s.T()
|
||||||
|
|
||||||
|
host, err := s.ds.NewHost(context.Background(), &fleet.Host{
|
||||||
|
DetailUpdatedAt: time.Now(),
|
||||||
|
LabelUpdatedAt: time.Now(),
|
||||||
|
PolicyUpdatedAt: time.Now(),
|
||||||
|
SeenTime: time.Now().Add(-1 * time.Minute),
|
||||||
|
OsqueryHostID: t.Name(),
|
||||||
|
NodeKey: t.Name(),
|
||||||
|
UUID: uuid.New().String(),
|
||||||
|
Hostname: fmt.Sprintf("%sfoo.local", t.Name()),
|
||||||
|
Platform: "darwin",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create device token for host
|
||||||
|
token := "token_test_custom_transparency_url"
|
||||||
|
mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error {
|
||||||
|
_, err := db.ExecContext(context.Background(), `INSERT INTO host_device_auth (host_id, token) VALUES (?, ?)`, host.ID, token)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
// confirm intitial default url
|
||||||
|
acResp := appConfigResponse{}
|
||||||
|
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
|
||||||
|
require.NotNil(t, acResp)
|
||||||
|
require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL)
|
||||||
|
|
||||||
|
// confirm device endpoint returns initial default url
|
||||||
|
deviceResp := &getDeviceHostResponse{}
|
||||||
|
rawResp := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
|
||||||
|
json.NewDecoder(rawResp.Body).Decode(deviceResp)
|
||||||
|
rawResp.Body.Close()
|
||||||
|
require.NoError(t, deviceResp.Err)
|
||||||
|
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
|
||||||
|
|
||||||
|
// set custom url
|
||||||
|
acResp = appConfigResponse{}
|
||||||
|
s.DoJSON("PATCH", "/api/latest/fleet/config", fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: "customURL"}}, http.StatusOK, &acResp)
|
||||||
|
require.NotNil(t, acResp)
|
||||||
|
require.Equal(t, "customURL", acResp.FleetDesktop.TransparencyURL)
|
||||||
|
|
||||||
|
// device endpoint returns custom url
|
||||||
|
deviceResp = &getDeviceHostResponse{}
|
||||||
|
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
|
||||||
|
json.NewDecoder(rawResp.Body).Decode(deviceResp)
|
||||||
|
rawResp.Body.Close()
|
||||||
|
require.NoError(t, deviceResp.Err)
|
||||||
|
require.Equal(t, "customURL", deviceResp.TransparencyURL)
|
||||||
|
|
||||||
|
// empty string applies default url
|
||||||
|
acResp = appConfigResponse{}
|
||||||
|
s.DoJSON("PATCH", "/api/latest/fleet/config", fleet.AppConfig{FleetDesktop: fleet.FleetDesktopSettings{TransparencyURL: ""}}, http.StatusOK, &acResp)
|
||||||
|
require.NotNil(t, acResp)
|
||||||
|
require.Equal(t, fleet.DefaultTransparencyURL, acResp.FleetDesktop.TransparencyURL)
|
||||||
|
|
||||||
|
// device endpoint returns default url
|
||||||
|
deviceResp = &getDeviceHostResponse{}
|
||||||
|
rawResp = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token, nil, http.StatusOK)
|
||||||
|
json.NewDecoder(rawResp.Body).Decode(deviceResp)
|
||||||
|
rawResp.Body.Close()
|
||||||
|
require.NoError(t, deviceResp.Err)
|
||||||
|
require.Equal(t, fleet.DefaultTransparencyURL, deviceResp.TransparencyURL)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user