From 2a532ede945f32bdb49354de253fb4093cccbe63 Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Wed, 7 Jun 2023 16:06:36 -0300 Subject: [PATCH] Do not return empty SSO and SMTP settings for non-global-admins (#12180) #11266 PS: I first attempted a serialization trick by introducing a new `appConfigResponse` and implementing `json.Marshal` to exclude these fields but it was too hacky and hard to maintain moving forward, so I'm bitting the bullet now. Happy to hear other ideas. - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - ~[ ] Documented any permissions changes~ - ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements)~ - ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features.~ - [X] Added/updated tests - [X] Manual QA for all new/changed functionality - ~For Orbit and Fleet Desktop changes:~ - ~[ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux.~ - ~[ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).~ --- changes/11266-omit-sso-and-smtp-if-not-set | 1 + cmd/fleet/serve.go | 2 +- cmd/fleetctl/apply_test.go | 2 + cmd/fleetctl/get_test.go | 102 ++++++++++++++ docs/Using-Fleet/Permissions.md | 6 +- ee/server/service/mdm.go | 7 +- ee/server/service/users.go | 4 +- server/datastore/mysql/app_configs.go | 2 +- server/fleet/app.go | 71 +++++++--- server/fleet/emails.go | 9 +- server/mail/mail.go | 28 ++-- server/mail/mail_test.go | 130 +++++++++--------- server/mail/ses.go | 8 +- server/mail/ses_test.go | 31 +++-- server/service/appconfig.go | 72 ++++++---- server/service/appconfig_test.go | 148 +++++++++++++++++++-- server/service/invites.go | 11 +- server/service/mail_test.go | 2 +- server/service/service_appconfig.go | 8 +- server/service/sessions.go | 15 ++- server/service/sessions_test.go | 6 +- server/service/users.go | 27 +++- server/service/users_test.go | 8 +- 23 files changed, 506 insertions(+), 194 deletions(-) create mode 100644 changes/11266-omit-sso-and-smtp-if-not-set diff --git a/changes/11266-omit-sso-and-smtp-if-not-set b/changes/11266-omit-sso-and-smtp-if-not-set new file mode 100644 index 0000000000..bf81559e0a --- /dev/null +++ b/changes/11266-omit-sso-and-smtp-if-not-set @@ -0,0 +1 @@ +* `GET /api/_version_/fleet/config` to omit fields `smtp_settings` and `sso_settings` if not set. diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index 147e0f4f63..03d413456a 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -554,7 +554,7 @@ the way that the Fleet server works. } // setup mail service - if appCfg.SMTPSettings.SMTPEnabled { + if appCfg.SMTPSettings != nil && appCfg.SMTPSettings.SMTPEnabled { // if SMTP is already enabled then default the backend to empty string, which fill force load the SMTP implementation if config.Email.EmailBackend != "" { config.Email.EmailBackend = "" diff --git a/cmd/fleetctl/apply_test.go b/cmd/fleetctl/apply_test.go index 2fff5481f2..91ecfa9495 100644 --- a/cmd/fleetctl/apply_test.go +++ b/cmd/fleetctl/apply_test.go @@ -1386,6 +1386,8 @@ func TestApplyMacosSetup(t *testing.T) { OrgInfo: fleet.OrgInfo{OrgName: "Fleet"}, ServerSettings: fleet.ServerSettings{ServerURL: "https://example.org"}, MDM: fleet.MDM{EnabledAndConfigured: true}, + SMTPSettings: &fleet.SMTPSettings{}, + SSOSettings: &fleet.SSOSettings{}, } mockStore.Unlock() ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { diff --git a/cmd/fleetctl/get_test.go b/cmd/fleetctl/get_test.go index 9aae4cbaed..7af9ee1a13 100644 --- a/cmd/fleetctl/get_test.go +++ b/cmd/fleetctl/get_test.go @@ -554,6 +554,8 @@ func TestGetConfig(t *testing.T) { return &fleet.AppConfig{ Features: fleet.Features{EnableHostUsers: true}, VulnerabilitySettings: fleet.VulnerabilitySettings{DatabasesPath: "/some/path"}, + SMTPSettings: &fleet.SMTPSettings{}, + SSOSettings: &fleet.SSOSettings{}, }, nil } @@ -1933,3 +1935,103 @@ func TestUserIsObserver(t *testing.T) { }) } } + +func TestGetConfigAgentOptionsSSOAndSMTP(t *testing.T) { + _, ds := runServerWithMockedDS(t) + + agentOpts := json.RawMessage(` +{ + "config": { + "options": { + "distributed_interval": 10 + } + }, + "overrides": { + "platforms": { + "darwin": { + "options": { + "distributed_interval": 5 + } + } + } + } +}`) + ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { + return &fleet.AppConfig{ + AgentOptions: &agentOpts, + SSOSettings: &fleet.SSOSettings{}, + SMTPSettings: &fleet.SMTPSettings{}, + }, 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 + checkOutput func(output string) bool + }{ + { + name: "global admin", + user: &fleet.User{ + ID: 1, + Name: "Global admin", + Password: []byte("p4ssw0rd.123"), + Email: "ga@example.com", + GlobalRole: ptr.String(fleet.RoleAdmin), + }, + checkOutput: func(output string) bool { + return strings.Contains(output, "sso_settings") && strings.Contains(output, "smtp_settings") + }, + }, + { + name: "global observer", + user: &fleet.User{ + ID: 2, + Name: "Global observer", + Password: []byte("p4ssw0rd.123"), + Email: "go@example.com", + GlobalRole: ptr.String(fleet.RoleObserverPlus), + }, + checkOutput: func(output string) bool { + return !strings.Contains(output, "sso_settings") && !strings.Contains(output, "smtp_settings") + }, + }, + { + name: "team observer", + user: &fleet.User{ + ID: 3, + Name: "Team observer", + Password: []byte("p4ssw0rd.123"), + Email: "tm@example.com", + GlobalRole: nil, + Teams: []fleet.UserTeam{{Role: fleet.RoleObserver}}, + }, + checkOutput: func(output string) bool { + return !strings.Contains(output, "sso_settings") && !strings.Contains(output, "smtp_settings") + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + setCurrentUserSession(tc.user) + + ok := tc.checkOutput(runAppForTest(t, []string{"get", "config"})) + require.True(t, ok) + }) + } +} diff --git a/docs/Using-Fleet/Permissions.md b/docs/Using-Fleet/Permissions.md index 4494d9811f..8a6d2d723c 100644 --- a/docs/Using-Fleet/Permissions.md +++ b/docs/Using-Fleet/Permissions.md @@ -63,7 +63,10 @@ GitOps is an API-only and write-only role that can be used on CI/CD pipelines. | Create, edit, and delete teams\* | | | | ✅ | ✅ | | Create, edit, and delete [enroll secrets](https://fleetdm.com/docs/deploying/faq#when-do-i-need-to-deploy-a-new-enroll-secret-to-my-hosts) | | | ✅ | ✅ | ✅ | | Create, edit, and delete [enroll secrets for teams](https://fleetdm.com/docs/using-fleet/rest-api#get-enroll-secrets-for-a-team)\* | | | ✅ | ✅ | | -| Read organization settings and agent options\*** | ✅ | ✅ | ✅ | ✅ | | +| Read organization settings\*** | ✅ | ✅ | ✅ | ✅ | | +| Read Single Sign-On settings\*** | | | | ✅ | | +| Read SMTP settings\*** | | | | ✅ | | +| Read osquery agent options\*** | | | | ✅ | | | Edit [organization settings](https://fleetdm.com/docs/using-fleet/configuration-files#organization-settings) | | | | ✅ | ✅ | | Edit [agent options](https://fleetdm.com/docs/using-fleet/configuration-files#agent-options) | | | | ✅ | ✅ | | Edit [agent options for hosts assigned to teams](https://fleetdm.com/docs/using-fleet/configuration-files#team-agent-options)\* | | | | ✅ | ✅ | @@ -131,6 +134,7 @@ Users that are members of multiple teams can be assigned different roles for eac | Add and remove team members | | | | ✅ | ✅ | | Edit team name | | | | ✅ | ✅ | | Create, edit, and delete [team enroll secrets](https://fleetdm.com/docs/using-fleet/rest-api#get-enroll-secrets-for-a-team) | | | ✅ | ✅ | | +| Read organization settings\* | ✅ | ✅ | ✅ | ✅ | | | Read agent options\* | ✅ | ✅ | ✅ | ✅ | | | Edit [agent options](https://fleetdm.com/docs/using-fleet/configuration-files#agent-options) | | | | ✅ | ✅ | | Initiate [file carving](https://fleetdm.com/docs/using-fleet/rest-api#file-carving) | | | ✅ | ✅ | | diff --git a/ee/server/service/mdm.go b/ee/server/service/mdm.go index 15d87122f2..109f4d4b73 100644 --- a/ee/server/service/mdm.go +++ b/ee/server/service/mdm.go @@ -646,10 +646,15 @@ func (svc *Service) InitiateMDMAppleSSOCallback(ctx context.Context, auth fleet. return "", ctxerr.Wrap(ctx, err, "validate request in session") } + var ssoSettings fleet.SSOSettings + if appConfig.SSOSettings != nil { + ssoSettings = *appConfig.SSOSettings + } + err = sso.ValidateAudiences( *metadata, auth, - appConfig.SSOSettings.EntityID, + ssoSettings.EntityID, appConfig.ServerSettings.ServerURL, appConfig.ServerSettings.ServerURL+svc.config.Server.URLPrefix+"/api/v1/fleet/mdm/sso/callback", ) diff --git a/ee/server/service/users.go b/ee/server/service/users.go index ecaf0ee9e7..a5da9d0ae6 100644 --- a/ee/server/service/users.go +++ b/ee/server/service/users.go @@ -34,7 +34,7 @@ func (svc *Service) GetSSOUser(ctx context.Context, auth fleet.Auth) (*fleet.Use // If JIT provisioning is disabled, then Fleet does not attempt to change // the role of the existing user. - if !config.SSOSettings.EnableJITProvisioning { + if config.SSOSettings == nil || !config.SSOSettings.EnableJITProvisioning { return user, nil } @@ -75,7 +75,7 @@ func (svc *Service) GetSSOUser(ctx context.Context, auth fleet.Auth) (*fleet.Use } return user, nil case errors.As(err, &nfe): - if !config.SSOSettings.EnableJITProvisioning { + if config.SSOSettings == nil || !config.SSOSettings.EnableJITProvisioning { return nil, err } default: diff --git a/server/datastore/mysql/app_configs.go b/server/datastore/mysql/app_configs.go index 83cb185584..fb12563369 100644 --- a/server/datastore/mysql/app_configs.go +++ b/server/datastore/mysql/app_configs.go @@ -63,7 +63,7 @@ func (ds *Datastore) SaveAppConfig(ctx context.Context, info *fleet.AppConfig) e return ctxerr.Wrap(ctx, err, "insert app_config_json") } - if !info.SSOSettings.EnableSSO { + if info.SSOSettings != nil && !info.SSOSettings.EnableSSO { _, err = tx.ExecContext(ctx, `UPDATE users SET sso_enabled=false`) if err != nil { return ctxerr.Wrap(ctx, err, "update users sso") diff --git a/server/fleet/app.go b/server/fleet/app.go index 221292111e..6b30a462c8 100644 --- a/server/fleet/app.go +++ b/server/fleet/app.go @@ -308,22 +308,35 @@ type MDMEndUserAuthentication struct { SSOProviderSettings } -// AppConfig holds server configuration that can be changed via the API. +// AppConfig holds global server configuration that can be changed via the API. // // Note: management of deprecated fields is done on JSON-marshalling and uses // the legacyConfig struct to list them. +// +// /////////////////////////////////////////////////////////////// +// WARNING: If you add or change fields of this struct make sure +// it's taken into account in the AppConfig Clone implementation! +// /////////////////////////////////////////////////////////////// type AppConfig struct { - OrgInfo OrgInfo `json:"org_info"` - ServerSettings ServerSettings `json:"server_settings"` - SMTPSettings SMTPSettings `json:"smtp_settings"` + OrgInfo OrgInfo `json:"org_info"` + ServerSettings ServerSettings `json:"server_settings"` + // SMTPSettings holds the SMTP integration settings. + // + // This field is a pointer to avoid returning this information to non-global-admins. + SMTPSettings *SMTPSettings `json:"smtp_settings,omitempty"` HostExpirySettings HostExpirySettings `json:"host_expiry_settings"` // Features allows to globally enable or disable features - Features Features `json:"features"` + Features Features `json:"features"` + // AgentOptions holds osquery configuration. + // + // This field is a pointer to avoid returning this information to non-global-admins. AgentOptions *json.RawMessage `json:"agent_options,omitempty"` // SMTPTest is a flag that if set will cause the server to test email configuration SMTPTest bool `json:"smtp_test,omitempty"` - // SSOSettings is single sign on settings - SSOSettings SSOSettings `json:"sso_settings"` + // SSOSettings is single sign on integration settings. + // + // This field is a pointer to avoid returning this information to non-global-admins. + SSOSettings *SSOSettings `json:"sso_settings,omitempty"` // FleetDesktop holds settings for Fleet Desktop that can be changed via the API. FleetDesktop FleetDesktopSettings `json:"fleet_desktop"` @@ -342,15 +355,15 @@ type AppConfig struct { // if any legacy settings were set in the raw JSON. didUnmarshalLegacySettings []string - ///////////////////////////////////////////////////////////////// - // WARNING: If you add to this struct make sure it's taken into - // account in the AppConfig Clone implementation! - ///////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////// + // WARNING: If you add or change fields of this struct make sure + // it's taken into account in the AppConfig Clone implementation! + // /////////////////////////////////////////////////////////////// } // Obfuscate overrides credentials with obfuscated characters. func (c *AppConfig) Obfuscate() { - if c.SMTPSettings.SMTPPassword != "" { + if c.SMTPSettings != nil && c.SMTPSettings.SMTPPassword != "" { c.SMTPSettings.SMTPPassword = MaskedPassword } for _, jiraIntegration := range c.Integrations.Jira { @@ -389,7 +402,12 @@ func (c *AppConfig) Copy() *AppConfig { copy(clone.ServerSettings.DebugHostIDs, c.ServerSettings.DebugHostIDs) } - // SMTPSettings: nothing needs cloning + if c.SMTPSettings != nil { + var smtpSettings SMTPSettings + smtpSettings = *c.SMTPSettings + clone.SMTPSettings = &smtpSettings + } + // HostExpirySettings: nothing needs cloning if c.Features.AdditionalQueries != nil { @@ -403,7 +421,12 @@ func (c *AppConfig) Copy() *AppConfig { clone.AgentOptions = &ao } - // SSOSettings: nothing needs cloning + if c.SSOSettings != nil { + var ssoSettings SSOSettings + ssoSettings = *c.SSOSettings + clone.SSOSettings = &ssoSettings + } + // FleetDesktop: nothing needs cloning // VulnerabilitySettings: nothing needs cloning @@ -548,16 +571,24 @@ type VulnerabilitiesWebhookSettings struct { func (c *AppConfig) ApplyDefaultsForNewInstalls() { c.ServerSettings.EnableAnalytics = true - c.SMTPSettings.SMTPPort = 587 - c.SMTPSettings.SMTPEnableStartTLS = true - c.SMTPSettings.SMTPAuthenticationType = AuthTypeNameUserNamePassword - c.SMTPSettings.SMTPAuthenticationMethod = AuthMethodNamePlain - c.SMTPSettings.SMTPVerifySSLCerts = true - c.SMTPSettings.SMTPEnableTLS = true + // Add default values for SMTPSettings. + var smtpSettings SMTPSettings + smtpSettings.SMTPEnabled = false + smtpSettings.SMTPPort = 587 + smtpSettings.SMTPEnableStartTLS = true + smtpSettings.SMTPAuthenticationType = AuthTypeNameUserNamePassword + smtpSettings.SMTPAuthenticationMethod = AuthMethodNamePlain + smtpSettings.SMTPVerifySSLCerts = true + smtpSettings.SMTPEnableTLS = true + c.SMTPSettings = &smtpSettings agentOptions := json.RawMessage(`{"config": {"options": {"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": {}}`) c.AgentOptions = &agentOptions + // Make sure an empty SSOSettings is set. + var ssoSettings SSOSettings + c.SSOSettings = &ssoSettings + c.Features.ApplyDefaultsForNewInstalls() c.ApplyDefaults() diff --git a/server/fleet/emails.go b/server/fleet/emails.go index 8ca3ef1088..4f94f46e75 100644 --- a/server/fleet/emails.go +++ b/server/fleet/emails.go @@ -12,10 +12,11 @@ type Mailer interface { } type Email struct { - Subject string - To []string - Config *AppConfig - Mailer Mailer + Subject string + To []string + ServerURL string + SMTPSettings SMTPSettings + Mailer Mailer } type MailService interface { diff --git a/server/mail/mail.go b/server/mail/mail.go index 71b6732b77..91a0fce715 100644 --- a/server/mail/mail.go +++ b/server/mail/mail.go @@ -75,11 +75,11 @@ func getMessageBody(e fleet.Email, f fromFunc) ([]byte, error) { } func getFrom(e fleet.Email) (string, error) { - return "From: " + e.Config.SMTPSettings.SMTPSenderAddress + "\r\n", nil + return "From: " + e.SMTPSettings.SMTPSenderAddress + "\r\n", nil } func (m mailService) SendEmail(e fleet.Email) error { - if !e.Config.SMTPSettings.SMTPConfigured { + if !e.SMTPSettings.SMTPConfigured { return errors.New("email not configured") } msg, err := getMessageBody(e, getFrom) @@ -132,14 +132,14 @@ func (l *loginauth) Next(fromServer []byte, more bool) (toServer []byte, err err } func smtpAuth(e fleet.Email) (smtp.Auth, error) { - if e.Config.SMTPSettings.SMTPAuthenticationType != fleet.AuthTypeNameUserNamePassword { + if e.SMTPSettings.SMTPAuthenticationType != fleet.AuthTypeNameUserNamePassword { return nil, nil } - username := e.Config.SMTPSettings.SMTPUserName - password := e.Config.SMTPSettings.SMTPPassword - server := e.Config.SMTPSettings.SMTPServer - authMethod := e.Config.SMTPSettings.SMTPAuthenticationMethod + username := e.SMTPSettings.SMTPUserName + password := e.SMTPSettings.SMTPPassword + server := e.SMTPSettings.SMTPServer + authMethod := e.SMTPSettings.SMTPAuthenticationMethod var auth smtp.Auth switch authMethod { @@ -157,14 +157,14 @@ func smtpAuth(e fleet.Email) (smtp.Auth, error) { func (m mailService) sendMail(e fleet.Email, msg []byte) error { smtpHost := fmt.Sprintf( - "%s:%d", e.Config.SMTPSettings.SMTPServer, e.Config.SMTPSettings.SMTPPort) + "%s:%d", e.SMTPSettings.SMTPServer, e.SMTPSettings.SMTPPort) auth, err := smtpAuth(e) if err != nil { return fmt.Errorf("failed to get smtp auth: %w", err) } - if e.Config.SMTPSettings.SMTPAuthenticationMethod == fleet.AuthMethodNameCramMD5 { - err = smtp.SendMail(smtpHost, auth, e.Config.SMTPSettings.SMTPSenderAddress, e.To, msg) + if e.SMTPSettings.SMTPAuthenticationMethod == fleet.AuthMethodNameCramMD5 { + err = smtp.SendMail(smtpHost, auth, e.SMTPSettings.SMTPSenderAddress, e.To, msg) if err != nil { return fmt.Errorf("failed to send mail. crammd5 auth method: %w", err) } @@ -177,11 +177,11 @@ func (m mailService) sendMail(e fleet.Email, msg []byte) error { } defer client.Close() - if e.Config.SMTPSettings.SMTPEnableStartTLS { + if e.SMTPSettings.SMTPEnableStartTLS { if ok, _ := client.Extension("STARTTLS"); ok { config := &tls.Config{ - ServerName: e.Config.SMTPSettings.SMTPServer, - InsecureSkipVerify: !e.Config.SMTPSettings.SMTPVerifySSLCerts, + ServerName: e.SMTPSettings.SMTPServer, + InsecureSkipVerify: !e.SMTPSettings.SMTPVerifySSLCerts, } if err = client.StartTLS(config); err != nil { return fmt.Errorf("startTLS error: %w", err) @@ -193,7 +193,7 @@ func (m mailService) sendMail(e fleet.Email, msg []byte) error { return fmt.Errorf("client auth error: %w", err) } } - if err = client.Mail(e.Config.SMTPSettings.SMTPSenderAddress); err != nil { + if err = client.Mail(e.SMTPSettings.SMTPSenderAddress); err != nil { return fmt.Errorf("could not issue mail to provided address: %w", err) } for _, recip := range e.To { diff --git a/server/mail/mail_test.go b/server/mail/mail_test.go index fef8fbc276..2215174f2a 100644 --- a/server/mail/mail_test.go +++ b/server/mail/mail_test.go @@ -41,20 +41,18 @@ func testSMTPPlainAuth(t *testing.T, mailer fleet.MailService) { mail := fleet.Email{ Subject: "smtp plain auth", To: []string{"john@fleet.co"}, - Config: &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ - SMTPConfigured: true, - SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword, - SMTPAuthenticationMethod: fleet.AuthMethodNamePlain, - SMTPUserName: "mailpit-username", - SMTPPassword: "mailpit-password", - SMTPEnableTLS: true, - SMTPVerifySSLCerts: true, - SMTPEnableStartTLS: true, - SMTPPort: 1026, - SMTPServer: "localhost", - SMTPSenderAddress: "test@example.com", - }, + SMTPSettings: fleet.SMTPSettings{ + SMTPConfigured: true, + SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword, + SMTPAuthenticationMethod: fleet.AuthMethodNamePlain, + SMTPUserName: "mailpit-username", + SMTPPassword: "mailpit-password", + SMTPEnableTLS: true, + SMTPVerifySSLCerts: true, + SMTPEnableStartTLS: true, + SMTPPort: 1026, + SMTPServer: "localhost", + SMTPSenderAddress: "test@example.com", }, Mailer: &SMTPTestMailer{ BaseURL: "https://localhost:8080", @@ -69,20 +67,18 @@ func testSMTPPlainAuthInvalidCreds(t *testing.T, mailer fleet.MailService) { mail := fleet.Email{ Subject: "smtp plain auth with invalid credentials", To: []string{"john@fleet.co"}, - Config: &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ - SMTPConfigured: true, - SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword, - SMTPAuthenticationMethod: fleet.AuthMethodNamePlain, - SMTPUserName: "mailpit-username", - SMTPPassword: "wrong", - SMTPEnableTLS: true, - SMTPVerifySSLCerts: true, - SMTPEnableStartTLS: true, - SMTPPort: 1026, - SMTPServer: "localhost", - SMTPSenderAddress: "test@example.com", - }, + SMTPSettings: fleet.SMTPSettings{ + SMTPConfigured: true, + SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword, + SMTPAuthenticationMethod: fleet.AuthMethodNamePlain, + SMTPUserName: "mailpit-username", + SMTPPassword: "wrong", + SMTPEnableTLS: true, + SMTPVerifySSLCerts: true, + SMTPEnableStartTLS: true, + SMTPPort: 1026, + SMTPServer: "localhost", + SMTPSenderAddress: "test@example.com", }, Mailer: &SMTPTestMailer{ BaseURL: "https://localhost:8080", @@ -97,20 +93,18 @@ func testSMTPSkipVerify(t *testing.T, mailer fleet.MailService) { mail := fleet.Email{ Subject: "skip verify", To: []string{"john@fleet.co"}, - Config: &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ - SMTPConfigured: true, - SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword, - SMTPAuthenticationMethod: fleet.AuthMethodNamePlain, - SMTPUserName: "mailpit-username", - SMTPPassword: "mailpit-password", - SMTPEnableTLS: true, - SMTPVerifySSLCerts: false, - SMTPEnableStartTLS: true, - SMTPPort: 1025, - SMTPServer: "localhost", - SMTPSenderAddress: "test@example.com", - }, + SMTPSettings: fleet.SMTPSettings{ + SMTPConfigured: true, + SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword, + SMTPAuthenticationMethod: fleet.AuthMethodNamePlain, + SMTPUserName: "mailpit-username", + SMTPPassword: "mailpit-password", + SMTPEnableTLS: true, + SMTPVerifySSLCerts: false, + SMTPEnableStartTLS: true, + SMTPPort: 1025, + SMTPServer: "localhost", + SMTPSenderAddress: "test@example.com", }, Mailer: &SMTPTestMailer{ BaseURL: "https://localhost:8080", @@ -125,16 +119,14 @@ func testSMTPNoAuth(t *testing.T, mailer fleet.MailService) { mail := fleet.Email{ Subject: "no auth", To: []string{"bob@foo.com"}, - Config: &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ - SMTPConfigured: true, - SMTPAuthenticationType: fleet.AuthTypeNameNone, - SMTPEnableTLS: true, - SMTPVerifySSLCerts: true, - SMTPPort: 1025, - SMTPServer: "localhost", - SMTPSenderAddress: "test@example.com", - }, + SMTPSettings: fleet.SMTPSettings{ + SMTPConfigured: true, + SMTPAuthenticationType: fleet.AuthTypeNameNone, + SMTPEnableTLS: true, + SMTPVerifySSLCerts: true, + SMTPPort: 1025, + SMTPServer: "localhost", + SMTPSenderAddress: "test@example.com", }, Mailer: &SMTPTestMailer{ BaseURL: "https://localhost:8080", @@ -149,19 +141,17 @@ func testMailTest(t *testing.T, mailer fleet.MailService) { mail := fleet.Email{ Subject: "test tester", To: []string{"bob@foo.com"}, - Config: &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ - SMTPConfigured: true, - SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword, - SMTPAuthenticationMethod: fleet.AuthMethodNamePlain, - SMTPUserName: "mailpit-username", - SMTPPassword: "mailpit-password", - SMTPEnableTLS: true, - SMTPVerifySSLCerts: true, - SMTPPort: 1026, - SMTPServer: "localhost", - SMTPSenderAddress: "test@example.com", - }, + SMTPSettings: fleet.SMTPSettings{ + SMTPConfigured: true, + SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword, + SMTPAuthenticationMethod: fleet.AuthMethodNamePlain, + SMTPUserName: "mailpit-username", + SMTPPassword: "mailpit-password", + SMTPEnableTLS: true, + SMTPVerifySSLCerts: true, + SMTPPort: 1026, + SMTPServer: "localhost", + SMTPSenderAddress: "test@example.com", }, Mailer: &SMTPTestMailer{ BaseURL: "https://localhost:8080", @@ -193,8 +183,14 @@ func Test_getFrom(t *testing.T) { wantErr assert.ErrorAssertionFunc }{ { - name: "should return SMTP formatted From string", - args: args{e: fleet.Email{Config: &fleet.AppConfig{SMTPSettings: fleet.SMTPSettings{SMTPSenderAddress: "foo@bar.com"}}}}, + name: "should return SMTP formatted From string", + args: args{ + e: fleet.Email{ + SMTPSettings: fleet.SMTPSettings{ + SMTPSenderAddress: "foo@bar.com", + }, + }, + }, want: "From: foo@bar.com\r\n", wantErr: assert.NoError, }, diff --git a/server/mail/ses.go b/server/mail/ses.go index fd4b0bf5c7..9ba22c3274 100644 --- a/server/mail/ses.go +++ b/server/mail/ses.go @@ -23,12 +23,9 @@ type sesSender struct { } func getFromSES(e fleet.Email) (string, error) { - if e.Config == nil { - return "", errors.New("app config is nil") - } - serverURL, err := url.Parse(e.Config.ServerSettings.ServerURL) + serverURL, err := url.Parse(e.ServerURL) if err != nil || len(serverURL.Host) == 0 { - return "", fmt.Errorf("failed to parse server url %s err: %w", e.Config.ServerSettings.ServerURL, err) + return "", fmt.Errorf("failed to parse server url %s err: %w", e.ServerURL, err) } return fmt.Sprintf("From: %s\r\n", fmt.Sprintf("do-not-reply@%s", serverURL.Host)), nil } @@ -87,7 +84,6 @@ func (s *sesSender) sendMail(e fleet.Email, msg []byte) error { RawMessage: &ses.RawMessage{Data: msg}, SourceArn: &s.sourceArn, }) - if err != nil { return err } diff --git a/server/mail/ses_test.go b/server/mail/ses_test.go index 696b6b2386..052c1705d5 100644 --- a/server/mail/ses_test.go +++ b/server/mail/ses_test.go @@ -21,14 +21,18 @@ func Test_getFromSES(t *testing.T) { wantErr assert.ErrorAssertionFunc }{ { - name: "should return properly formatted SMTP from for use in SES", - args: args{e: fleet.Email{Config: &fleet.AppConfig{ServerSettings: fleet.ServerSettings{ServerURL: "https://foobar.fleetdm.com"}}}}, + name: "should return properly formatted SMTP from for use in SES", + args: args{e: fleet.Email{ + ServerURL: "https://foobar.fleetdm.com", + }}, want: "From: do-not-reply@foobar.fleetdm.com\r\n", wantErr: assert.NoError, }, { - name: "should error when we fail to parse fleet server url", - args: args{e: fleet.Email{Config: &fleet.AppConfig{ServerSettings: fleet.ServerSettings{ServerURL: "not-a-url"}}}}, + name: "should error when we fail to parse fleet server url", + args: args{e: fleet.Email{ + ServerURL: "not-a-url", + }}, want: "", wantErr: assert.Error, }, @@ -76,9 +80,9 @@ func Test_sesSender_SendEmail(t *testing.T) { sourceArn: "foo", }, args: args{e: fleet.Email{ - Subject: "Hello from Fleet!", - To: []string{"foouser@fleetdm.com"}, - Config: &fleet.AppConfig{ServerSettings: fleet.ServerSettings{ServerURL: "https://foobar.fleetdm.com"}}, + Subject: "Hello from Fleet!", + To: []string{"foouser@fleetdm.com"}, + ServerURL: "https://foobar.fleetdm.com", Mailer: &SMTPTestMailer{ BaseURL: "https://localhost:8080", }, @@ -94,7 +98,6 @@ func Test_sesSender_SendEmail(t *testing.T) { args: args{e: fleet.Email{ Subject: "Hello from Fleet!", To: []string{"foouser@fleetdm.com"}, - Config: nil, Mailer: &SMTPTestMailer{ BaseURL: "https://localhost:8080", }, @@ -108,9 +111,9 @@ func Test_sesSender_SendEmail(t *testing.T) { sourceArn: "foo", }, args: args{e: fleet.Email{ - Subject: "Hello from Fleet!", - To: []string{"foouser@fleetdm.com"}, - Config: &fleet.AppConfig{ServerSettings: fleet.ServerSettings{ServerURL: "https://foobar.fleetdm.com"}}, + Subject: "Hello from Fleet!", + To: []string{"foouser@fleetdm.com"}, + ServerURL: "https://foobar.fleetdm.com", Mailer: &SMTPTestMailer{ BaseURL: "https://localhost:8080", }, @@ -124,9 +127,9 @@ func Test_sesSender_SendEmail(t *testing.T) { sourceArn: "foo", }, args: args{e: fleet.Email{ - Subject: "Hello from Fleet!", - To: []string{"foouser@fleetdm.com"}, - Config: &fleet.AppConfig{ServerSettings: fleet.ServerSettings{ServerURL: "https://foobar.fleetdm.com"}}, + Subject: "Hello from Fleet!", + To: []string{"foouser@fleetdm.com"}, + ServerURL: "https://foobar.fleetdm.com", Mailer: &SMTPTestMailer{ BaseURL: "https://localhost:8080", }, diff --git a/server/service/appconfig.go b/server/service/appconfig.go index f75973f6b2..83dc1eef1c 100644 --- a/server/service/appconfig.go +++ b/server/service/appconfig.go @@ -98,15 +98,13 @@ func getAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet.Se return nil, err } - var smtpSettings fleet.SMTPSettings - var ssoSettings fleet.SSOSettings - var hostExpirySettings fleet.HostExpirySettings + // Only the Global Admin should be able to see see SMTP, SSO and osquery agent settings. + var smtpSettings *fleet.SMTPSettings + var ssoSettings *fleet.SSOSettings var agentOptions *json.RawMessage - // only admin can see smtp, sso, and host expiry settings if vc.User.GlobalRole != nil && *vc.User.GlobalRole == fleet.RoleAdmin { smtpSettings = config.SMTPSettings ssoSettings = config.SSOSettings - hostExpirySettings = config.HostExpirySettings agentOptions = config.AgentOptions } @@ -128,11 +126,11 @@ func getAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet.Se ServerSettings: config.ServerSettings, Features: features, VulnerabilitySettings: config.VulnerabilitySettings, + HostExpirySettings: config.HostExpirySettings, - SMTPSettings: smtpSettings, - SSOSettings: ssoSettings, - HostExpirySettings: hostExpirySettings, - AgentOptions: agentOptions, + SMTPSettings: smtpSettings, + SSOSettings: ssoSettings, + AgentOptions: agentOptions, FleetDesktop: fleetDesktop, @@ -208,9 +206,7 @@ func modifyAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet }, } - if response.SMTPSettings.SMTPPassword != "" { - response.SMTPSettings.SMTPPassword = fleet.MaskedPassword - } + response.Obfuscate() if (!license.IsPremium()) || response.FleetDesktop.TransparencyURL == "" { response.FleetDesktop.TransparencyURL = fleet.DefaultTransparencyURL @@ -235,7 +231,16 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle // We do not use svc.License(ctx) to allow roles (like GitOps) write but not read access to AppConfig. license, _ := license.FromContext(ctx) - oldSmtpSettings := appConfig.SMTPSettings + var oldSMTPSettings fleet.SMTPSettings + if appConfig.SMTPSettings != nil { + oldSMTPSettings = *appConfig.SMTPSettings + } else { + // SMTPSettings used to be a non-pointer on previous iterations, + // so if current SMTPSettings are not present (with empty values), + // then this is a bug, let's log an error. + level.Error(svc.logger).Log("smtp_settings are not present") + } + oldAgentOptions := "" if appConfig.AgentOptions != nil { oldAgentOptions = string(*appConfig.AgentOptions) @@ -272,9 +277,11 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle } } - validateSSOSettings(newAppConfig, appConfig, invalid, license) - if invalid.HasErrors() { - return nil, ctxerr.Wrap(ctx, invalid) + if newAppConfig.SSOSettings != nil { + validateSSOSettings(newAppConfig, appConfig, invalid, license) + if invalid.HasErrors() { + return nil, ctxerr.Wrap(ctx, invalid) + } } // We apply the config that is incoming to the old one @@ -346,20 +353,23 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle return svc.AppConfigObfuscated(ctx) } - // ignore the values for SMTPEnabled and SMTPConfigured - oldSmtpSettings.SMTPEnabled = appConfig.SMTPSettings.SMTPEnabled - oldSmtpSettings.SMTPConfigured = appConfig.SMTPSettings.SMTPConfigured + // Perform validation of the applied SMTP settings. + if newAppConfig.SMTPSettings != nil { + // Ignore the values for SMTPEnabled and SMTPConfigured. + oldSMTPSettings.SMTPEnabled = appConfig.SMTPSettings.SMTPEnabled + oldSMTPSettings.SMTPConfigured = appConfig.SMTPSettings.SMTPConfigured - // if we enable SMTP and the settings have changed, then we send a test email - if appConfig.SMTPSettings.SMTPEnabled { - if oldSmtpSettings != appConfig.SMTPSettings || !appConfig.SMTPSettings.SMTPConfigured { - if err = svc.sendTestEmail(ctx, appConfig); err != nil { - return nil, ctxerr.Wrap(ctx, err) + // If we enable SMTP and the settings have changed, then we send a test email. + if appConfig.SMTPSettings.SMTPEnabled { + if oldSMTPSettings != *appConfig.SMTPSettings || !appConfig.SMTPSettings.SMTPConfigured { + if err = svc.sendTestEmail(ctx, appConfig); err != nil { + return nil, ctxerr.Wrap(ctx, err) + } } + appConfig.SMTPSettings.SMTPConfigured = true + } else { + appConfig.SMTPSettings.SMTPConfigured = false } - appConfig.SMTPSettings.SMTPConfigured = true - } else { - appConfig.SMTPSettings.SMTPConfigured = false } delJira, err := fleet.ValidateJiraIntegrations(ctx, storedJiraByProjectKey, newAppConfig.Integrations.Jira) @@ -648,9 +658,13 @@ func validateSSOProviderSettings(incoming, existing fleet.SSOProviderSettings, i } func validateSSOSettings(p fleet.AppConfig, existing *fleet.AppConfig, invalid *fleet.InvalidArgumentError, license *fleet.LicenseInfo) { - if p.SSOSettings.EnableSSO { + if p.SSOSettings != nil && p.SSOSettings.EnableSSO { - validateSSOProviderSettings(p.SSOSettings.SSOProviderSettings, existing.SSOSettings.SSOProviderSettings, invalid) + var existingSSOProviderSettings fleet.SSOProviderSettings + if existing.SSOSettings != nil { + existingSSOProviderSettings = existing.SSOSettings.SSOProviderSettings + } + validateSSOProviderSettings(p.SSOSettings.SSOProviderSettings, existingSSOProviderSettings, invalid) if !license.IsPremium() { if p.SSOSettings.EnableJITProvisioning { diff --git a/server/service/appconfig_test.go b/server/service/appconfig_test.go index 739fdfd325..6aeacd6828 100644 --- a/server/service/appconfig_test.go +++ b/server/service/appconfig_test.go @@ -349,7 +349,7 @@ func TestSSONotPresent(t *testing.T) { func TestNeedFieldsPresent(t *testing.T) { invalid := &fleet.InvalidArgumentError{} config := fleet.AppConfig{ - SSOSettings: fleet.SSOSettings{ + SSOSettings: &fleet.SSOSettings{ EnableSSO: true, SSOProviderSettings: fleet.SSOProviderSettings{ EntityID: "fleet", @@ -366,7 +366,7 @@ func TestNeedFieldsPresent(t *testing.T) { func TestShortIDPName(t *testing.T) { invalid := &fleet.InvalidArgumentError{} config := fleet.AppConfig{ - SSOSettings: fleet.SSOSettings{ + SSOSettings: &fleet.SSOSettings{ EnableSSO: true, SSOProviderSettings: fleet.SSOProviderSettings{ EntityID: "fleet", @@ -384,7 +384,7 @@ func TestShortIDPName(t *testing.T) { func TestMissingMetadata(t *testing.T) { invalid := &fleet.InvalidArgumentError{} config := fleet.AppConfig{ - SSOSettings: fleet.SSOSettings{ + SSOSettings: &fleet.SSOSettings{ EnableSSO: true, SSOProviderSettings: fleet.SSOProviderSettings{ EntityID: "fleet", @@ -401,7 +401,7 @@ func TestMissingMetadata(t *testing.T) { func TestJITProvisioning(t *testing.T) { config := fleet.AppConfig{ - SSOSettings: fleet.SSOSettings{ + SSOSettings: &fleet.SSOSettings{ EnableSSO: true, EnableJITProvisioning: true, SSOProviderSettings: fleet.SSOProviderSettings{ @@ -429,9 +429,11 @@ func TestJITProvisioning(t *testing.T) { t.Run("doesn't care if JIT provisioning is set to false on free licenses", func(t *testing.T) { invalid := &fleet.InvalidArgumentError{} - oldConfig := &fleet.AppConfig{} - - oldConfig.SSOSettings.EnableJITProvisioning = true + oldConfig := &fleet.AppConfig{ + SSOSettings: &fleet.SSOSettings{ + EnableJITProvisioning: false, + }, + } config.SSOSettings.EnableJITProvisioning = false validateSSOSettings(config, oldConfig, invalid, &fleet.LicenseInfo{}) require.False(t, invalid.HasErrors()) @@ -449,7 +451,9 @@ func TestAppConfigSecretsObfuscated(t *testing.T) { ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { return &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{SMTPPassword: "smtppassword"}, + SMTPSettings: &fleet.SMTPSettings{ + SMTPPassword: "smtppassword", + }, Integrations: fleet.Integrations{ Jira: []*fleet.JiraIntegration{ {APIToken: "jiratoken"}, @@ -553,7 +557,7 @@ func TestModifyAppConfigSMTPConfigured(t *testing.T) { ServerSettings: fleet.ServerSettings{ ServerURL: "https://example.org", }, - SMTPSettings: fleet.SMTPSettings{ + SMTPSettings: &fleet.SMTPSettings{ SMTPEnabled: true, SMTPConfigured: true, }, @@ -569,7 +573,7 @@ func TestModifyAppConfigSMTPConfigured(t *testing.T) { // Disable SMTP. newAppConfig := fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ + SMTPSettings: &fleet.SMTPSettings{ SMTPEnabled: false, SMTPConfigured: true, }, @@ -967,3 +971,127 @@ func TestMDMAppleConfig(t *testing.T) { }) } } + +func TestModifyAppConfigSMTPSSOAgentOptions(t *testing.T) { + ds := new(mock.Store) + svc, ctx := newTestService(t, ds, nil, nil) + + // SMTP and SSO are initially set. + agentOptions := json.RawMessage(` +{ + "config": { + "options": { + "distributed_interval": 10 + } + }, + "overrides": { + "platforms": { + "darwin": { + "options": { + "distributed_interval": 5 + } + } + } + } +}`) + dsAppConfig := &fleet.AppConfig{ + OrgInfo: fleet.OrgInfo{ + OrgName: "Test", + }, + ServerSettings: fleet.ServerSettings{ + ServerURL: "https://example.org", + }, + SMTPSettings: &fleet.SMTPSettings{ + SMTPEnabled: true, + SMTPConfigured: true, + SMTPSenderAddress: "foobar@example.com", + }, + SSOSettings: &fleet.SSOSettings{ + EnableSSO: true, + SSOProviderSettings: fleet.SSOProviderSettings{ + MetadataURL: "foobar.example.com/metadata", + }, + }, + AgentOptions: &agentOptions, + } + + 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 + } + ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error { + return nil + } + + // Not sending smtp_settings, sso_settings or agent_settings will do nothing. + b := []byte(`{}`) + admin := &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)} + ctx = viewer.NewContext(ctx, viewer.Viewer{User: admin}) + updatedAppConfig, err := svc.ModifyAppConfig(ctx, b, fleet.ApplySpecOptions{}) + require.NoError(t, err) + + require.True(t, updatedAppConfig.SMTPSettings.SMTPEnabled) + require.True(t, dsAppConfig.SMTPSettings.SMTPEnabled) + require.True(t, updatedAppConfig.SSOSettings.EnableSSO) + require.True(t, dsAppConfig.SSOSettings.EnableSSO) + require.Equal(t, agentOptions, *updatedAppConfig.AgentOptions) + require.Equal(t, agentOptions, *dsAppConfig.AgentOptions) + + // Not sending sso_settings or agent settings will not change them, and + // sending SMTP settings will change them. + b = []byte(`{"smtp_settings": {"enable_smtp": false}}`) + updatedAppConfig, err = svc.ModifyAppConfig(ctx, b, fleet.ApplySpecOptions{}) + require.NoError(t, err) + + require.False(t, updatedAppConfig.SMTPSettings.SMTPEnabled) + require.False(t, dsAppConfig.SMTPSettings.SMTPEnabled) + require.True(t, updatedAppConfig.SSOSettings.EnableSSO) + require.True(t, dsAppConfig.SSOSettings.EnableSSO) + require.Equal(t, agentOptions, *updatedAppConfig.AgentOptions) + require.Equal(t, agentOptions, *dsAppConfig.AgentOptions) + + // Not sending smtp_settings or agent settings will not change them, and + // sending SSO settings will change them. + b = []byte(`{"sso_settings": {"enable_sso": false}}`) + updatedAppConfig, err = svc.ModifyAppConfig(ctx, b, fleet.ApplySpecOptions{}) + require.NoError(t, err) + + require.False(t, updatedAppConfig.SMTPSettings.SMTPEnabled) + require.False(t, dsAppConfig.SMTPSettings.SMTPEnabled) + require.False(t, updatedAppConfig.SSOSettings.EnableSSO) + require.False(t, dsAppConfig.SSOSettings.EnableSSO) + require.Equal(t, agentOptions, *updatedAppConfig.AgentOptions) + require.Equal(t, agentOptions, *dsAppConfig.AgentOptions) + + // Not sending smtp_settings or sso_settings will not change them, and + // sending agent options will change them. + newAgentOptions := json.RawMessage(`{ + "config": { + "options": { + "distributed_interval": 100 + } + }, + "overrides": { + "platforms": { + "darwin": { + "options": { + "distributed_interval": 2 + } + } + } + } +}`) + b = []byte(`{"agent_options": ` + string(newAgentOptions) + `}`) + updatedAppConfig, err = svc.ModifyAppConfig(ctx, b, fleet.ApplySpecOptions{}) + require.NoError(t, err) + + require.False(t, updatedAppConfig.SMTPSettings.SMTPEnabled) + require.False(t, dsAppConfig.SMTPSettings.SMTPEnabled) + require.False(t, updatedAppConfig.SSOSettings.EnableSSO) + require.False(t, dsAppConfig.SSOSettings.EnableSSO) + require.Equal(t, newAgentOptions, *dsAppConfig.AgentOptions) + require.Equal(t, newAgentOptions, *dsAppConfig.AgentOptions) +} diff --git a/server/service/invites.go b/server/service/invites.go index d42497023f..8b3cf72419 100644 --- a/server/service/invites.go +++ b/server/service/invites.go @@ -104,10 +104,15 @@ func (svc *Service) InviteNewUser(ctx context.Context, payload fleet.InvitePaylo if invitedBy == "" { invitedBy = inviter.Email } + var smtpSettings fleet.SMTPSettings + if config.SMTPSettings != nil { + smtpSettings = *config.SMTPSettings + } inviteEmail := fleet.Email{ - Subject: "You are Invited to Fleet", - To: []string{invite.Email}, - Config: config, + Subject: "You are Invited to Fleet", + To: []string{invite.Email}, + ServerURL: config.ServerSettings.ServerURL, + SMTPSettings: smtpSettings, Mailer: &mail.InviteMailer{ Invite: invite, BaseURL: template.URL(config.ServerSettings.ServerURL + svc.config.Server.URLPrefix), diff --git a/server/service/mail_test.go b/server/service/mail_test.go index d330034220..9c0222c878 100644 --- a/server/service/mail_test.go +++ b/server/service/mail_test.go @@ -52,7 +52,7 @@ func TestMailService(t *testing.T) { ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { return &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ + SMTPSettings: &fleet.SMTPSettings{ SMTPEnabled: true, SMTPConfigured: true, SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword, diff --git a/server/service/service_appconfig.go b/server/service/service_appconfig.go index 7ac16f5023..956d6a9d69 100644 --- a/server/service/service_appconfig.go +++ b/server/service/service_appconfig.go @@ -69,6 +69,11 @@ func (svc *Service) sendTestEmail(ctx context.Context, config *fleet.AppConfig) return fleet.ErrNoContext } + var smtpSettings fleet.SMTPSettings + if config.SMTPSettings != nil { + smtpSettings = *config.SMTPSettings + } + testMail := fleet.Email{ Subject: "Hello from Fleet", To: []string{vc.User.Email}, @@ -76,7 +81,8 @@ func (svc *Service) sendTestEmail(ctx context.Context, config *fleet.AppConfig) BaseURL: template.URL(config.ServerSettings.ServerURL + svc.config.Server.URLPrefix), AssetURL: getAssetURL(), }, - Config: config, + SMTPSettings: smtpSettings, + ServerURL: config.ServerSettings.ServerURL, } if err := mail.Test(svc.mailService, testMail); err != nil { diff --git a/server/service/sessions.go b/server/service/sessions.go index 062adead04..a37ad83915 100644 --- a/server/service/sessions.go +++ b/server/service/sessions.go @@ -294,7 +294,7 @@ func (svc *Service) InitiateSSO(ctx context.Context, redirectURL string) (string return "", ctxerr.Wrap(ctx, err, "InitiateSSO getting app config") } - if !appConfig.SSOSettings.EnableSSO { + if appConfig.SSOSettings == nil || !appConfig.SSOSettings.EnableSSO { err := &fleet.BadRequestError{Message: "organization not configured to use sso"} return "", ctxerr.Wrap(ctx, newSSOError(err, ssoOrgDisabled), "initiate sso") } @@ -448,7 +448,7 @@ func (svc *Service) InitSSOCallback(ctx context.Context, auth fleet.Auth) (strin return "", ctxerr.Wrap(ctx, err, "get config for sso") } - if !appConfig.SSOSettings.EnableSSO { + if appConfig.SSOSettings == nil || !appConfig.SSOSettings.EnableSSO { err := ctxerr.New(ctx, "organization not configured to use sso") return "", ctxerr.Wrap(ctx, newSSOError(err, ssoOrgDisabled), "callback sso") } @@ -563,10 +563,15 @@ func (svc *Service) SSOSettings(ctx context.Context) (*fleet.SessionSSOSettings, return nil, ctxerr.Wrap(ctx, err, "SessionSSOSettings getting app config") } + var ssoSettings fleet.SSOSettings + if appConfig.SSOSettings != nil { + ssoSettings = *appConfig.SSOSettings + } + settings := &fleet.SessionSSOSettings{ - IDPName: appConfig.SSOSettings.IDPName, - IDPImageURL: appConfig.SSOSettings.IDPImageURL, - SSOEnabled: appConfig.SSOSettings.EnableSSO, + IDPName: ssoSettings.IDPName, + IDPImageURL: ssoSettings.IDPImageURL, + SSOEnabled: ssoSettings.EnableSSO, } return settings, nil } diff --git a/server/service/sessions_test.go b/server/service/sessions_test.go index f330231922..139cacfebe 100644 --- a/server/service/sessions_test.go +++ b/server/service/sessions_test.go @@ -225,7 +225,7 @@ func TestGetSSOUser(t *testing.T) { ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { return &fleet.AppConfig{ - SSOSettings: fleet.SSOSettings{ + SSOSettings: &fleet.SSOSettings{ EnableSSO: true, EnableSSOIdPLogin: true, EnableJITProvisioning: true, @@ -321,7 +321,7 @@ func TestGetSSOUser(t *testing.T) { ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { return &fleet.AppConfig{ - SSOSettings: fleet.SSOSettings{ + SSOSettings: &fleet.SSOSettings{ EnableSSO: true, EnableSSOIdPLogin: true, EnableJITProvisioning: false, @@ -347,7 +347,7 @@ func TestGetSSOUser(t *testing.T) { ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { return &fleet.AppConfig{ - SSOSettings: fleet.SSOSettings{ + SSOSettings: &fleet.SSOSettings{ EnableSSO: true, EnableSSOIdPLogin: true, EnableJITProvisioning: true, diff --git a/server/service/users.go b/server/service/users.go index d544ee7af2..9da533673a 100644 --- a/server/service/users.go +++ b/server/service/users.go @@ -5,11 +5,12 @@ import ( "database/sql" "encoding/base64" "errors" - "github.com/go-kit/kit/log/level" "html/template" "net/http" "time" + "github.com/go-kit/kit/log/level" + "github.com/fleetdm/fleet/v4/server" "github.com/fleetdm/fleet/v4/server/authz" authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz" @@ -777,10 +778,16 @@ func (svc *Service) modifyEmailAddress(ctx context.Context, user *fleet.User, em return err } + var smtpSettings fleet.SMTPSettings + if config.SMTPSettings != nil { + smtpSettings = *config.SMTPSettings + } + changeEmail := fleet.Email{ - Subject: "Confirm Fleet Email Change", - To: []string{email}, - Config: config, + Subject: "Confirm Fleet Email Change", + To: []string{email}, + SMTPSettings: smtpSettings, + ServerURL: config.ServerSettings.ServerURL, Mailer: &mail.ChangeEmailMailer{ Token: token, BaseURL: template.URL(config.ServerSettings.ServerURL + svc.config.Server.URLPrefix), @@ -1021,10 +1028,16 @@ func (svc *Service) RequestPasswordReset(ctx context.Context, email string) erro return err } + var smtpSettings fleet.SMTPSettings + if config.SMTPSettings != nil { + smtpSettings = *config.SMTPSettings + } + resetEmail := fleet.Email{ - Subject: "Reset Your Fleet Password", - To: []string{user.Email}, - Config: config, + Subject: "Reset Your Fleet Password", + To: []string{user.Email}, + SMTPSettings: smtpSettings, + ServerURL: config.ServerSettings.ServerURL, Mailer: &mail.PasswordResetMailer{ BaseURL: template.URL(config.ServerSettings.ServerURL + svc.config.Server.URLPrefix), AssetURL: getAssetURL(), diff --git a/server/service/users_test.go b/server/service/users_test.go index 6f975665eb..71a6971074 100644 --- a/server/service/users_test.go +++ b/server/service/users_test.go @@ -443,7 +443,7 @@ func TestModifyUserEmail(t *testing.T) { } ms.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { config := &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ + SMTPSettings: &fleet.SMTPSettings{ SMTPConfigured: true, SMTPAuthenticationType: fleet.AuthTypeNameNone, SMTPPort: 1025, @@ -492,7 +492,7 @@ func TestModifyUserEmailNoPassword(t *testing.T) { } ms.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { config := &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ + SMTPSettings: &fleet.SMTPSettings{ SMTPConfigured: true, SMTPAuthenticationType: fleet.AuthTypeNameNone, SMTPPort: 1025, @@ -540,7 +540,7 @@ func TestModifyAdminUserEmailNoPassword(t *testing.T) { } ms.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { config := &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ + SMTPSettings: &fleet.SMTPSettings{ SMTPConfigured: true, SMTPAuthenticationType: fleet.AuthTypeNameNone, SMTPPort: 1025, @@ -592,7 +592,7 @@ func TestModifyAdminUserEmailPassword(t *testing.T) { } ms.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { config := &fleet.AppConfig{ - SMTPSettings: fleet.SMTPSettings{ + SMTPSettings: &fleet.SMTPSettings{ SMTPConfigured: true, SMTPAuthenticationType: fleet.AuthTypeNameNone, SMTPPort: 1025,