diff --git a/cmd/fleetctl/get_test.go b/cmd/fleetctl/get_test.go index bba40091748b..ea9e9d8bcfbe 100644 --- a/cmd/fleetctl/get_test.go +++ b/cmd/fleetctl/get_test.go @@ -23,6 +23,7 @@ import ( "github.com/fleetdm/fleet/v4/server/config" "github.com/fleetdm/fleet/v4/server/fleet" nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client" + "github.com/fleetdm/fleet/v4/server/mock" nanodep_mock "github.com/fleetdm/fleet/v4/server/mock/nanodep" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/service" @@ -65,6 +66,23 @@ var userRoleList = []*fleet.User{ }, } +var setCurrentUserSession = func(t *testing.T, ds *mock.Store, user *fleet.User) { + user, err := ds.NewUser(context.Background(), user) + require.NoError(t, err) + ds.SessionByKeyFunc = func(ctx context.Context, key string) (*fleet.Session, error) { + return &fleet.Session{ + CreateTimestamp: fleet.CreateTimestamp{CreatedAt: time.Now()}, + ID: 1, + AccessedAt: time.Now(), + UserID: user.ID, + Key: key, + }, nil + } + ds.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) { + return user, nil + } +} + func TestGetUserRoles(t *testing.T) { _, ds := runServerWithMockedDS(t) @@ -627,6 +645,70 @@ func TestGetConfig(t *testing.T) { assert.YAMLEq(t, expectedYAML, runAppForTest(t, []string{"get", "config", "--include-server-config", "--yaml"})) require.JSONEq(t, expectedJSON, runAppForTest(t, []string{"get", "config", "--include-server-config", "--json"})) }) + + t.Run("AppConfigAsTeamUsers", func(t *testing.T) { + // test as team admin + setCurrentUserSession(t, ds, &fleet.User{ + ID: 5, + Name: "Admin of team 1", + Password: []byte("p4ssw0rd.123"), + Email: "omt2@example.com", + GlobalRole: nil, + Teams: []fleet.UserTeam{ + { + Team: fleet.Team{ID: 1}, + Role: fleet.RoleAdmin, + }, + { + Team: fleet.Team{ID: 2}, + Role: fleet.RoleMaintainer, + }, + }, + }) + + b, err := os.ReadFile(filepath.Join("testdata", "expectedGetConfigAppConfigYaml.yml")) + require.NoError(t, err) + expectedYaml := string(b) + + b, err = os.ReadFile(filepath.Join("testdata", "expectedGetConfigAppConfigJson.json")) + require.NoError(t, err) + expectedJson := string(b) + + assert.YAMLEq(t, expectedYaml, runAppForTest(t, []string{"get", "config"})) + assert.YAMLEq(t, expectedYaml, runAppForTest(t, []string{"get", "config", "--yaml"})) + assert.JSONEq(t, expectedJson, runAppForTest(t, []string{"get", "config", "--json"})) + + // test as team maintainer + setCurrentUserSession(t, ds, &fleet.User{ + ID: 6, + Name: "Maintainer of team 1", + Password: []byte("p4ssw0rd.123"), + Email: "omt3@example.com", + GlobalRole: nil, + Teams: []fleet.UserTeam{ + { + Team: fleet.Team{ID: 1}, + Role: fleet.RoleMaintainer, + }, + { + Team: fleet.Team{ID: 2}, + Role: fleet.RoleMaintainer, + }, + }, + }) + + b, err = os.ReadFile(filepath.Join("testdata", "expectedGetConfigAppConfigTeamMaintainerYaml.yml")) + require.NoError(t, err) + expectedYaml = string(b) + + b, err = os.ReadFile(filepath.Join("testdata", "expectedGetConfigAppConfigTeamMaintainerJson.json")) + require.NoError(t, err) + expectedJson = string(b) + + assert.YAMLEq(t, expectedYaml, runAppForTest(t, []string{"get", "config"})) + assert.YAMLEq(t, expectedYaml, runAppForTest(t, []string{"get", "config", "--yaml"})) + assert.JSONEq(t, expectedJson, runAppForTest(t, []string{"get", "config", "--json"})) + }) } func TestGetSoftwareTitles(t *testing.T) { @@ -1546,23 +1628,6 @@ spec: func TestGetQueriesAsObserver(t *testing.T) { _, ds := runServerWithMockedDS(t) - setCurrentUserSession := func(user *fleet.User) { - user, err := ds.NewUser(context.Background(), user) - require.NoError(t, err) - ds.SessionByKeyFunc = func(ctx context.Context, key string) (*fleet.Session, error) { - return &fleet.Session{ - CreateTimestamp: fleet.CreateTimestamp{CreatedAt: time.Now()}, - ID: 1, - AccessedAt: time.Now(), - UserID: user.ID, - Key: key, - }, nil - } - ds.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) { - return user, nil - } - } - ds.ListQueriesFunc = func(ctx context.Context, opt fleet.ListQueryOptions) ([]*fleet.Query, int, *fleet.PaginationMetadata, error) { return []*fleet.Query{ { @@ -1636,7 +1701,7 @@ func TestGetQueriesAsObserver(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - setCurrentUserSession(tc.user) + setCurrentUserSession(t, ds, tc.user) expected := `+--------+-------------+-----------+-----------+----------------------------+ | NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE | @@ -1678,7 +1743,7 @@ spec: } // Test with a user that is observer of a team, but maintainer of another team (should not filter the queries). - setCurrentUserSession(&fleet.User{ + setCurrentUserSession(t, ds, &fleet.User{ ID: 4, Name: "Not observer of all teams", Password: []byte("p4ssw0rd.123"), @@ -1786,7 +1851,7 @@ spec: assert.Equal(t, expectedJson, runAppForTest(t, []string{"get", "queries", "--json"})) // No queries are returned if none is observer_can_run. - setCurrentUserSession(&fleet.User{ + setCurrentUserSession(t, ds, &fleet.User{ ID: 2, Name: "Team observer", Password: []byte("p4ssw0rd.123"), @@ -2948,23 +3013,6 @@ func TestGetConfigAgentOptionsSSOAndSMTP(t *testing.T) { }, nil } - setCurrentUserSession := func(user *fleet.User) { - user, err := ds.NewUser(context.Background(), user) - require.NoError(t, err) - ds.SessionByKeyFunc = func(ctx context.Context, key string) (*fleet.Session, error) { - return &fleet.Session{ - CreateTimestamp: fleet.CreateTimestamp{CreatedAt: time.Now()}, - ID: 1, - AccessedAt: time.Now(), - UserID: user.ID, - Key: key, - }, nil - } - ds.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) { - return user, nil - } - } - for _, tc := range []struct { name string user *fleet.User @@ -3012,7 +3060,7 @@ func TestGetConfigAgentOptionsSSOAndSMTP(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - setCurrentUserSession(tc.user) + setCurrentUserSession(t, ds, tc.user) ok := tc.checkOutput(runAppForTest(t, []string{"get", "config"})) require.True(t, ok) diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerJson.json b/cmd/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerJson.json new file mode 100644 index 000000000000..77fcb980aa1b --- /dev/null +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerJson.json @@ -0,0 +1,124 @@ +{ + "kind": "config", + "apiVersion": "v1", + "spec": { + "org_info": { + "org_name": "", + "org_logo_url": "", + "org_logo_url_light_background": "", + "contact_url": "https://fleetdm.com/company/contact" + }, + "server_settings": { + "server_url": "", + "live_query_disabled": false, + "query_report_cap": 0, + "query_reports_disabled": false, + "enable_analytics": false, + "deferred_save_host": false, + "scripts_disabled": false, + "ai_features_disabled": false + }, + "host_expiry_settings": { + "host_expiry_enabled": false, + "host_expiry_window": 0 + }, + "activity_expiry_settings": { + "activity_expiry_enabled": false, + "activity_expiry_window": 0 + }, + "features": { + "enable_host_users": true, + "enable_software_inventory": false + }, + "fleet_desktop": { + "transparency_url": "https://fleetdm.com/transparency" + }, + "vulnerability_settings": { + "databases_path": "/some/path" + }, + "webhook_settings": { + "activities_webhook": { + "enable_activities_webhook": false, + "destination_url": "" + }, + "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, + "google_calendar": null, + "ndes_scep_proxy": null + }, + "mdm": { + "apple_bm_terms_expired": false, + "apple_server_url": "", + "apple_bm_enabled_and_configured": false, + "enabled_and_configured": false, + "apple_business_manager": null, + "volume_purchasing_program": null, + "windows_enabled_and_configured": false, + "enable_disk_encryption": false, + "macos_updates": { + "minimum_version": null, + "deadline": null + }, + "ios_updates": { + "minimum_version": null, + "deadline": null + }, + "ipados_updates": { + "minimum_version": null, + "deadline": null + }, + "windows_updates": { + "deadline_days": 7, + "grace_period_days": 3 + }, + "windows_migration_enabled": false, + "macos_migration": { + "enable": false, + "mode": "", + "webhook_url": "" + }, + "macos_settings": { + "custom_settings": null + }, + "macos_setup": { + "bootstrap_package": null, + "enable_end_user_authentication": false, + "macos_setup_assistant": null, + "enable_release_device_manually": false, + "script": null, + "software": null + }, + "windows_settings": { + "custom_settings": null + }, + "end_user_authentication": { + "entity_id": "", + "issuer_uri": "", + "metadata": "", + "metadata_url": "", + "idp_name": "" + } + }, + "scripts": null + } +} diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerYaml.yml new file mode 100644 index 000000000000..ad20026e99f2 --- /dev/null +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerYaml.yml @@ -0,0 +1,99 @@ +--- +apiVersion: v1 +kind: config +spec: + fleet_desktop: + transparency_url: https://fleetdm.com/transparency + host_expiry_settings: + host_expiry_enabled: false + host_expiry_window: 0 + activity_expiry_settings: + activity_expiry_enabled: false + activity_expiry_window: 0 + features: + enable_host_users: true + enable_software_inventory: false + integrations: + google_calendar: null + jira: null + ndes_scep_proxy: null + zendesk: null + mdm: + apple_bm_terms_expired: false + apple_server_url: "" + apple_bm_enabled_and_configured: false + enabled_and_configured: false + apple_business_manager: null + volume_purchasing_program: null + windows_enabled_and_configured: false + enable_disk_encryption: false + windows_migration_enabled: false + macos_migration: + enable: false + mode: "" + webhook_url: "" + macos_updates: + minimum_version: null + deadline: null + ios_updates: + minimum_version: null + deadline: null + ipados_updates: + minimum_version: null + deadline: null + windows_updates: + deadline_days: 7 + grace_period_days: 3 + macos_settings: + custom_settings: + macos_setup: + bootstrap_package: + enable_end_user_authentication: false + enable_release_device_manually: false + macos_setup_assistant: + script: + software: + windows_settings: + custom_settings: null + end_user_authentication: + idp_name: "" + issuer_uri: "" + metadata: "" + metadata_url: "" + entity_id: "" + scripts: null + org_info: + org_logo_url: "" + org_logo_url_light_background: "" + org_name: "" + contact_url: https://fleetdm.com/company/contact + server_settings: + deferred_save_host: false + enable_analytics: false + live_query_disabled: false + query_report_cap: 0 + query_reports_disabled: false + server_url: "" + scripts_disabled: false + ai_features_disabled: false + vulnerability_settings: + databases_path: /some/path + webhook_settings: + activities_webhook: + enable_activities_webhook: false + destination_url: "" + failing_policies_webhook: + destination_url: "" + enable_failing_policies_webhook: false + host_batch_size: 0 + policy_ids: null + host_status_webhook: + days_count: 0 + destination_url: "" + enable_host_status_webhook: false + host_percentage: 0 + interval: 0s + vulnerabilities_webhook: + destination_url: "" + enable_vulnerabilities_webhook: false + host_batch_size: 0 diff --git a/server/service/appconfig.go b/server/service/appconfig.go index e0ee0317e67f..4d48426bf86f 100644 --- a/server/service/appconfig.go +++ b/server/service/appconfig.go @@ -134,13 +134,29 @@ func getAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet.Se return nil, err } - // Only the Global Admin should be able to see see SMTP, SSO and osquery agent settings. + isGlobalAdmin := vc.User.GlobalRole != nil && *vc.User.GlobalRole == fleet.RoleAdmin + isAnyTeamAdmin := false + if vc.User.Teams != nil { + // check if the user is an admin for any team + for _, team := range vc.User.Teams { + if team.Role == fleet.RoleAdmin { + isAnyTeamAdmin = true + break + } + } + } + + // Only admins should see SMTP and SSO settings var smtpSettings *fleet.SMTPSettings var ssoSettings *fleet.SSOSettings - var agentOptions *json.RawMessage - if vc.User.GlobalRole != nil && *vc.User.GlobalRole == fleet.RoleAdmin { + if isGlobalAdmin || isAnyTeamAdmin { smtpSettings = appConfig.SMTPSettings ssoSettings = appConfig.SSOSettings + } + + // Only global admins should see osquery agent settings. + var agentOptions *json.RawMessage + if isGlobalAdmin { agentOptions = appConfig.AgentOptions }