mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Fix SMTP e-mail send when SMTP server has credentials (#10758)
#9609 This PR also fixes #10777. The issue is: We were using `svc.AppConfig` instead of `svc.ds.AppConfig` to retrieve the SMTP credentials. `svc.AppConfig` obfuscates credentials, whereas `svc.ds.AppConfig` does not. To help prevent this from happening again I've renamed `svc.AppConfig` to `svc.AppConfigObfuscated`. I've also added a new test SMTP server (https://github.com/axllent/mailpit) that supports Basic Authentication and tests that make use of it to catch these kind of bugs (the tests are executed when running `go test` with `MAIL_TEST=1`). - [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)).~
This commit is contained in:
parent
477bb53f90
commit
40265d0e6f
3
.github/workflows/test-go.yaml
vendored
3
.github/workflows/test-go.yaml
vendored
@ -60,7 +60,7 @@ jobs:
|
|||||||
# Pre-starting dependencies here means they are ready to go when we need them.
|
# Pre-starting dependencies here means they are ready to go when we need them.
|
||||||
- name: Start Infra Dependencies
|
- name: Start Infra Dependencies
|
||||||
# Use & to background this
|
# Use & to background this
|
||||||
run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker-compose up -d mysql_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup minio saml_idp &
|
run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker-compose up -d mysql_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup minio saml_idp mailhog mailpit &
|
||||||
|
|
||||||
# It seems faster not to cache Go dependencies
|
# It seems faster not to cache Go dependencies
|
||||||
- name: Install Go Dependencies
|
- name: Install Go Dependencies
|
||||||
@ -95,6 +95,7 @@ jobs:
|
|||||||
MYSQL_TEST=1 \
|
MYSQL_TEST=1 \
|
||||||
MINIO_STORAGE_TEST=1 \
|
MINIO_STORAGE_TEST=1 \
|
||||||
SAML_IDP_TEST=1 \
|
SAML_IDP_TEST=1 \
|
||||||
|
MAIL_TEST=1 \
|
||||||
NETWORK_TEST_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
|
NETWORK_TEST_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
|
||||||
make test-go 2>&1 | tee /tmp/gotest.log
|
make test-go 2>&1 | tee /tmp/gotest.log
|
||||||
|
|
||||||
|
1
changes/9609-fix-smtp-email-send
Normal file
1
changes/9609-fix-smtp-email-send
Normal file
@ -0,0 +1 @@
|
|||||||
|
* Fix e-mail sending on user invites and user e-mail change when SMTP server has credentials.
|
@ -55,12 +55,27 @@ services:
|
|||||||
- /var/lib/mysql:rw,noexec,nosuid
|
- /var/lib/mysql:rw,noexec,nosuid
|
||||||
- /tmpfs
|
- /tmpfs
|
||||||
|
|
||||||
|
# Unauthenticated SMTP server.
|
||||||
mailhog:
|
mailhog:
|
||||||
image: mailhog/mailhog:latest
|
image: mailhog/mailhog:latest
|
||||||
ports:
|
ports:
|
||||||
- "8025:8025"
|
- "8025:8025"
|
||||||
- "1025:1025"
|
- "1025:1025"
|
||||||
|
|
||||||
|
# SMTP server with Basic Authentication.
|
||||||
|
mailpit:
|
||||||
|
image: axllent/mailpit:latest
|
||||||
|
ports:
|
||||||
|
- "8026:8025"
|
||||||
|
- "1026:1025"
|
||||||
|
volumes:
|
||||||
|
- ./tools/mailpit/auth.txt:/auth.txt
|
||||||
|
command:
|
||||||
|
[
|
||||||
|
"--smtp-auth-file=/auth.txt",
|
||||||
|
"--smtp-auth-allow-insecure=true"
|
||||||
|
]
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:5
|
image: redis:5
|
||||||
ports:
|
ports:
|
||||||
|
@ -19,7 +19,9 @@
|
|||||||
- [Command line](#command-line)
|
- [Command line](#command-line)
|
||||||
- [Test hosts](#test-hosts)
|
- [Test hosts](#test-hosts)
|
||||||
- [Email](#email)
|
- [Email](#email)
|
||||||
- [Manually testing email with MailHog](#manually-testing-email-with-mailhog)
|
- [Manually testing email with MailHog and Mailpit](#manually-testing-email-with-mailhog-and-mailpit)
|
||||||
|
- [MailHog SMTP server without authentication](#mailhog-smtp-server-without-authentication)
|
||||||
|
- [Mailpit SMTP server with plain authentication](#mailpit-smtp-server-with-plain-authentication)
|
||||||
- [Development database management](#development-database-management)
|
- [Development database management](#development-database-management)
|
||||||
- [MySQL shell](#mysql-shell)
|
- [MySQL shell](#mysql-shell)
|
||||||
- [Redis REPL](#redis-repl)
|
- [Redis REPL](#redis-repl)
|
||||||
@ -30,8 +32,7 @@
|
|||||||
- [Telemetry](#telemetry)
|
- [Telemetry](#telemetry)
|
||||||
- [MDM setup and testing](#mdm-setup-and-testing)
|
- [MDM setup and testing](#mdm-setup-and-testing)
|
||||||
- [ABM setup](#abm-setup)
|
- [ABM setup](#abm-setup)
|
||||||
- [Private key + certificate](#private-key--certificate)
|
- [Private key, certificate, and encrypted token](#private-key-certificate-and-encrypted-token)
|
||||||
- [Encrypted token](#encrypted-token)
|
|
||||||
- [APNs and SCEP setup](#apns-and-scep-setup)
|
- [APNs and SCEP setup](#apns-and-scep-setup)
|
||||||
- [Running the server](#running-the-server)
|
- [Running the server](#running-the-server)
|
||||||
- [Testing MDM](#testing-mdm)
|
- [Testing MDM](#testing-mdm)
|
||||||
@ -232,7 +233,9 @@ The Fleet repo includes tools to start testing osquery hosts. Please see the doc
|
|||||||
|
|
||||||
## Email
|
## Email
|
||||||
|
|
||||||
### Manually testing email with MailHog
|
### Manually testing email with MailHog and Mailpit
|
||||||
|
|
||||||
|
#### MailHog SMTP server without authentication
|
||||||
|
|
||||||
To intercept sent emails while running a Fleet development environment, first, as an Admin in the Fleet UI, navigate to the Organization settings.
|
To intercept sent emails while running a Fleet development environment, first, as an Admin in the Fleet UI, navigate to the Organization settings.
|
||||||
|
|
||||||
@ -240,6 +243,17 @@ Then, in the "SMTP options" section, enter any email address in the "Sender addr
|
|||||||
|
|
||||||
Visit [localhost:8025](http://localhost:8025) to view MailHog's admin interface displaying all emails sent using the simulated mail server.
|
Visit [localhost:8025](http://localhost:8025) to view MailHog's admin interface displaying all emails sent using the simulated mail server.
|
||||||
|
|
||||||
|
#### Mailpit SMTP server with plain authentication
|
||||||
|
|
||||||
|
Alternatively, if you need to test a SMTP server with plain basic authentication enabled, set:
|
||||||
|
- "SMTP server" to `localhost` on port `1026`
|
||||||
|
- "Authentication type" to `Plain`.
|
||||||
|
- "SMTP username" to `mailpit-username`.
|
||||||
|
- "SMTP password" to `mailpit-password`.
|
||||||
|
- Note that you may use any active or inactive sender address.
|
||||||
|
|
||||||
|
Visit [localhost:8026](http://localhost:8026) to view Mailpit's admin interface displaying all emails sent using the simulated mail server.
|
||||||
|
|
||||||
## Development database management
|
## Development database management
|
||||||
|
|
||||||
In the course of development (particularly when crafting database migrations), it may be useful to
|
In the course of development (particularly when crafting database migrations), it may be useful to
|
||||||
@ -574,3 +588,5 @@ Reference the [Apple DEP Profile documentation](https://developer.apple.com/docu
|
|||||||
2. In ABM, look for the computer with the serial number that matches the one your VM has, click on it and click on "Edit MDM Server" to assign that computer to your MDM server.
|
2. In ABM, look for the computer with the serial number that matches the one your VM has, click on it and click on "Edit MDM Server" to assign that computer to your MDM server.
|
||||||
|
|
||||||
3. Boot the machine, it should automatically enroll into MDM.
|
3. Boot the machine, it should automatically enroll into MDM.
|
||||||
|
|
||||||
|
<meta name="pageOrderInSection" value="1500">
|
@ -27,7 +27,7 @@ func (svc *Service) GetAppleBM(ctx context.Context) (*fleet.AppleBM, error) {
|
|||||||
return nil, notFoundError{}
|
return nil, notFoundError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
appCfg, err := svc.AppConfig(ctx)
|
appCfg, err := svc.AppConfigObfuscated(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -507,7 +507,7 @@ func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appConfig, err := svc.AppConfig(ctx)
|
appConfig, err := svc.AppConfigObfuscated(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -351,7 +351,8 @@ type Service interface {
|
|||||||
// AppConfigService provides methods for configuring the Fleet application
|
// AppConfigService provides methods for configuring the Fleet application
|
||||||
|
|
||||||
NewAppConfig(ctx context.Context, p AppConfig) (info *AppConfig, err error)
|
NewAppConfig(ctx context.Context, p AppConfig) (info *AppConfig, err error)
|
||||||
AppConfig(ctx context.Context) (info *AppConfig, err error)
|
// AppConfigObfuscated returns the global application config with obfuscated credentials.
|
||||||
|
AppConfigObfuscated(ctx context.Context) (info *AppConfig, err error)
|
||||||
ModifyAppConfig(ctx context.Context, p []byte, applyOpts ApplySpecOptions) (info *AppConfig, err error)
|
ModifyAppConfig(ctx context.Context, p []byte, applyOpts ApplySpecOptions) (info *AppConfig, err error)
|
||||||
SandboxEnabled() bool
|
SandboxEnabled() bool
|
||||||
|
|
||||||
|
@ -10,30 +10,23 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockMailer struct{}
|
|
||||||
|
|
||||||
func (m *mockMailer) SendEmail(e fleet.Email) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMailer() fleet.MailService {
|
|
||||||
|
|
||||||
if os.Getenv("MAIL_TEST") == "" {
|
|
||||||
return &mockMailer{}
|
|
||||||
}
|
|
||||||
return NewService()
|
|
||||||
}
|
|
||||||
|
|
||||||
var testFunctions = [...]func(*testing.T, fleet.MailService){
|
var testFunctions = [...]func(*testing.T, fleet.MailService){
|
||||||
testSMTPPlainAuth,
|
testSMTPPlainAuth,
|
||||||
|
testSMTPPlainAuthInvalidCreds,
|
||||||
testSMTPSkipVerify,
|
testSMTPSkipVerify,
|
||||||
testSMTPNoAuth,
|
testSMTPNoAuth,
|
||||||
testMailTest,
|
testMailTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMail(t *testing.T) {
|
func TestMail(t *testing.T) {
|
||||||
|
// This mail test requires mailhog unauthenticated running on localhost:1025
|
||||||
|
// and mailpit running on localhost:1026.
|
||||||
|
if _, ok := os.LookupEnv("MAIL_TEST"); !ok {
|
||||||
|
t.Skip("Mail tests are disabled")
|
||||||
|
}
|
||||||
|
|
||||||
for _, f := range testFunctions {
|
for _, f := range testFunctions {
|
||||||
r := getMailer()
|
r := NewService()
|
||||||
|
|
||||||
t.Run(test.FunctionName(f), func(t *testing.T) {
|
t.Run(test.FunctionName(f), func(t *testing.T) {
|
||||||
f(t, r)
|
f(t, r)
|
||||||
@ -50,12 +43,12 @@ func testSMTPPlainAuth(t *testing.T, mailer fleet.MailService) {
|
|||||||
SMTPConfigured: true,
|
SMTPConfigured: true,
|
||||||
SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword,
|
SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword,
|
||||||
SMTPAuthenticationMethod: fleet.AuthMethodNamePlain,
|
SMTPAuthenticationMethod: fleet.AuthMethodNamePlain,
|
||||||
SMTPUserName: "bob",
|
SMTPUserName: "mailpit-username",
|
||||||
SMTPPassword: "secret",
|
SMTPPassword: "mailpit-password",
|
||||||
SMTPEnableTLS: true,
|
SMTPEnableTLS: true,
|
||||||
SMTPVerifySSLCerts: true,
|
SMTPVerifySSLCerts: true,
|
||||||
SMTPEnableStartTLS: true,
|
SMTPEnableStartTLS: true,
|
||||||
SMTPPort: 1025,
|
SMTPPort: 1026,
|
||||||
SMTPServer: "localhost",
|
SMTPServer: "localhost",
|
||||||
SMTPSenderAddress: "test@example.com",
|
SMTPSenderAddress: "test@example.com",
|
||||||
},
|
},
|
||||||
@ -69,6 +62,34 @@ func testSMTPPlainAuth(t *testing.T, mailer fleet.MailService) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Mailer: &SMTPTestMailer{
|
||||||
|
BaseURL: "https://localhost:8080",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mailer.SendEmail(mail)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func testSMTPSkipVerify(t *testing.T, mailer fleet.MailService) {
|
func testSMTPSkipVerify(t *testing.T, mailer fleet.MailService) {
|
||||||
mail := fleet.Email{
|
mail := fleet.Email{
|
||||||
Subject: "skip verify",
|
Subject: "skip verify",
|
||||||
@ -78,8 +99,8 @@ func testSMTPSkipVerify(t *testing.T, mailer fleet.MailService) {
|
|||||||
SMTPConfigured: true,
|
SMTPConfigured: true,
|
||||||
SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword,
|
SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword,
|
||||||
SMTPAuthenticationMethod: fleet.AuthMethodNamePlain,
|
SMTPAuthenticationMethod: fleet.AuthMethodNamePlain,
|
||||||
SMTPUserName: "bob",
|
SMTPUserName: "mailpit-username",
|
||||||
SMTPPassword: "secret",
|
SMTPPassword: "mailpit-password",
|
||||||
SMTPEnableTLS: true,
|
SMTPEnableTLS: true,
|
||||||
SMTPVerifySSLCerts: false,
|
SMTPVerifySSLCerts: false,
|
||||||
SMTPEnableStartTLS: true,
|
SMTPEnableStartTLS: true,
|
||||||
@ -127,13 +148,16 @@ func testMailTest(t *testing.T, mailer fleet.MailService) {
|
|||||||
To: []string{"bob@foo.com"},
|
To: []string{"bob@foo.com"},
|
||||||
Config: &fleet.AppConfig{
|
Config: &fleet.AppConfig{
|
||||||
SMTPSettings: fleet.SMTPSettings{
|
SMTPSettings: fleet.SMTPSettings{
|
||||||
SMTPConfigured: true,
|
SMTPConfigured: true,
|
||||||
SMTPAuthenticationType: fleet.AuthTypeNameNone,
|
SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword,
|
||||||
SMTPEnableTLS: true,
|
SMTPAuthenticationMethod: fleet.AuthMethodNamePlain,
|
||||||
SMTPVerifySSLCerts: true,
|
SMTPUserName: "mailpit-username",
|
||||||
SMTPPort: 1025,
|
SMTPPassword: "mailpit-password",
|
||||||
SMTPServer: "localhost",
|
SMTPEnableTLS: true,
|
||||||
SMTPSenderAddress: "test@example.com",
|
SMTPVerifySSLCerts: true,
|
||||||
|
SMTPPort: 1026,
|
||||||
|
SMTPServer: "localhost",
|
||||||
|
SMTPSenderAddress: "test@example.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Mailer: &SMTPTestMailer{
|
Mailer: &SMTPTestMailer{
|
||||||
@ -142,7 +166,6 @@ func testMailTest(t *testing.T, mailer fleet.MailService) {
|
|||||||
}
|
}
|
||||||
err := Test(mailer, mail)
|
err := Test(mailer, mail)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplateProcessor(t *testing.T) {
|
func TestTemplateProcessor(t *testing.T) {
|
||||||
|
@ -70,7 +70,7 @@ func getAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet.Se
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("could not fetch user")
|
return nil, errors.New("could not fetch user")
|
||||||
}
|
}
|
||||||
config, err := svc.AppConfig(ctx)
|
config, err := svc.AppConfigObfuscated(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ func (svc *Service) SandboxEnabled() bool {
|
|||||||
return svc.config.Server.SandboxEnabled
|
return svc.config.Server.SandboxEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) AppConfig(ctx context.Context) (*fleet.AppConfig, error) {
|
func (svc *Service) AppConfigObfuscated(ctx context.Context) (*fleet.AppConfig, error) {
|
||||||
if !svc.authz.IsAuthenticatedWith(ctx, authz_ctx.AuthnDeviceToken) {
|
if !svc.authz.IsAuthenticatedWith(ctx, authz_ctx.AuthnDeviceToken) {
|
||||||
if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil {
|
if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -340,7 +340,7 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
|
|||||||
return nil, legacyUsedWarning
|
return nil, legacyUsedWarning
|
||||||
}
|
}
|
||||||
// must reload to get the unchanged app config
|
// must reload to get the unchanged app config
|
||||||
return svc.AppConfig(ctx)
|
return svc.AppConfigObfuscated(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore the values for SMTPEnabled and SMTPConfigured
|
// ignore the values for SMTPEnabled and SMTPConfigured
|
||||||
@ -398,7 +398,7 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
|
|||||||
}
|
}
|
||||||
|
|
||||||
// retrieve new app config with obfuscated secrets
|
// retrieve new app config with obfuscated secrets
|
||||||
obfuscatedConfig, err := svc.AppConfig(ctx)
|
obfuscatedConfig, err := svc.AppConfigObfuscated(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -667,7 +667,7 @@ func getCertificateEndpoint(ctx context.Context, request interface{}, svc fleet.
|
|||||||
|
|
||||||
// Certificate returns the PEM encoded certificate chain for osqueryd TLS termination.
|
// Certificate returns the PEM encoded certificate chain for osqueryd TLS termination.
|
||||||
func (svc *Service) CertificateChain(ctx context.Context) ([]byte, error) {
|
func (svc *Service) CertificateChain(ctx context.Context) ([]byte, error) {
|
||||||
config, err := svc.AppConfig(ctx)
|
config, err := svc.AppConfigObfuscated(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ func TestAppConfigAuth(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
|
ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
|
||||||
|
|
||||||
_, err := svc.AppConfig(ctx)
|
_, err := svc.AppConfigObfuscated(ctx)
|
||||||
checkAuthErr(t, tt.shouldFailRead, err)
|
checkAuthErr(t, tt.shouldFailRead, err)
|
||||||
|
|
||||||
_, err = svc.ModifyAppConfig(ctx, []byte(`{}`), fleet.ApplySpecOptions{})
|
_, err = svc.ModifyAppConfig(ctx, []byte(`{}`), fleet.ApplySpecOptions{})
|
||||||
@ -423,7 +423,7 @@ func TestAppConfigSecretsObfuscated(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
|
ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
|
||||||
|
|
||||||
ac, err := svc.AppConfig(ctx)
|
ac, err := svc.AppConfigObfuscated(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, ac.SMTPSettings.SMTPPassword, fleet.MaskedPassword)
|
require.Equal(t, ac.SMTPSettings.SMTPPassword, fleet.MaskedPassword)
|
||||||
require.Equal(t, ac.Integrations.Jira[0].APIToken, fleet.MaskedPassword)
|
require.Equal(t, ac.Integrations.Jira[0].APIToken, fleet.MaskedPassword)
|
||||||
@ -564,7 +564,7 @@ func TestTransparencyURL(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ac, err := svc.AppConfig(ctx)
|
ac, err := svc.AppConfigObfuscated(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.initialURL, ac.FleetDesktop.TransparencyURL)
|
require.Equal(t, tt.initialURL, ac.FleetDesktop.TransparencyURL)
|
||||||
|
|
||||||
@ -576,7 +576,7 @@ func TestTransparencyURL(t *testing.T) {
|
|||||||
|
|
||||||
if modified != nil {
|
if modified != nil {
|
||||||
require.Equal(t, tt.expectedURL, modified.FleetDesktop.TransparencyURL)
|
require.Equal(t, tt.expectedURL, modified.FleetDesktop.TransparencyURL)
|
||||||
ac, err = svc.AppConfig(ctx)
|
ac, err = svc.AppConfigObfuscated(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.expectedURL, ac.FleetDesktop.TransparencyURL)
|
require.Equal(t, tt.expectedURL, ac.FleetDesktop.TransparencyURL)
|
||||||
}
|
}
|
||||||
@ -613,7 +613,7 @@ func TestTransparencyURLDowngradeLicense(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ac, err := svc.AppConfig(ctx)
|
ac, err := svc.AppConfigObfuscated(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "https://example.com/transparency", ac.FleetDesktop.TransparencyURL)
|
require.Equal(t, "https://example.com/transparency", ac.FleetDesktop.TransparencyURL)
|
||||||
|
|
||||||
@ -633,7 +633,7 @@ func TestTransparencyURLDowngradeLicense(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, modified)
|
require.NotNil(t, modified)
|
||||||
require.Equal(t, "", modified.FleetDesktop.TransparencyURL)
|
require.Equal(t, "", modified.FleetDesktop.TransparencyURL)
|
||||||
ac, err = svc.AppConfig(ctx)
|
ac, err = svc.AppConfigObfuscated(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "f1337", ac.OrgInfo.OrgName)
|
require.Equal(t, "f1337", ac.OrgInfo.OrgName)
|
||||||
require.Equal(t, "", ac.FleetDesktop.TransparencyURL)
|
require.Equal(t, "", ac.FleetDesktop.TransparencyURL)
|
||||||
@ -716,7 +716,7 @@ func TestService_ModifyAppConfig_MDM(t *testing.T) {
|
|||||||
return nil, errors.New(notFoundErr)
|
return nil, errors.New(notFoundErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
ac, err := svc.AppConfig(ctx)
|
ac, err := svc.AppConfigObfuscated(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.oldMDM, ac.MDM)
|
require.Equal(t, tt.oldMDM, ac.MDM)
|
||||||
|
|
||||||
@ -731,7 +731,7 @@ func TestService_ModifyAppConfig_MDM(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.expectedMDM, modified.MDM)
|
require.Equal(t, tt.expectedMDM, modified.MDM)
|
||||||
ac, err = svc.AppConfig(ctx)
|
ac, err = svc.AppConfigObfuscated(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.expectedMDM, ac.MDM)
|
require.Equal(t, tt.expectedMDM, ac.MDM)
|
||||||
})
|
})
|
||||||
|
@ -1470,7 +1470,7 @@ func (svc *Service) BatchSetMDMAppleProfiles(ctx context.Context, tmID *uint, tm
|
|||||||
return ctxerr.Wrap(ctx, err)
|
return ctxerr.Wrap(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
appCfg, err := svc.AppConfig(ctx)
|
appCfg, err := svc.AppConfigObfuscated(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctxerr.Wrap(ctx, err)
|
return ctxerr.Wrap(ctx, err)
|
||||||
}
|
}
|
||||||
@ -1600,7 +1600,7 @@ func (svc *Service) UpdateMDMAppleSettings(ctx context.Context, payload fleet.MD
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) updateAppConfigMDMAppleSettings(ctx context.Context, payload fleet.MDMAppleSettingsPayload) error {
|
func (svc *Service) updateAppConfigMDMAppleSettings(ctx context.Context, payload fleet.MDMAppleSettingsPayload) error {
|
||||||
ac, err := svc.AppConfig(ctx)
|
ac, err := svc.AppConfigObfuscated(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ func getDeviceHostEndpoint(ctx context.Context, request interface{}, svc fleet.S
|
|||||||
// the org logo URL config is required by the frontend to render the page;
|
// the org logo URL config is required by the frontend to render the page;
|
||||||
// we need to be careful with what we return from AppConfig in the response
|
// we need to be careful with what we return from AppConfig in the response
|
||||||
// as this is a weakly authenticated endpoint (with the device auth token).
|
// as this is a weakly authenticated endpoint (with the device auth token).
|
||||||
ac, err := svc.AppConfig(ctx)
|
ac, err := svc.AppConfigObfuscated(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return getDeviceHostResponse{Err: err}, nil
|
return getDeviceHostResponse{Err: err}, nil
|
||||||
}
|
}
|
||||||
@ -329,7 +329,7 @@ func (r transparencyURLResponse) hijackRender(ctx context.Context, w http.Respon
|
|||||||
func (r transparencyURLResponse) error() error { return r.Err }
|
func (r transparencyURLResponse) error() error { return r.Err }
|
||||||
|
|
||||||
func transparencyURL(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
func transparencyURL(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||||
config, err := svc.AppConfig(ctx)
|
config, err := svc.AppConfigObfuscated(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return transparencyURLResponse{Err: err}, nil
|
return transparencyURLResponse{Err: err}, nil
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ func (svc *Service) InviteNewUser(ctx context.Context, payload fleet.InvitePaylo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := svc.AppConfig(ctx)
|
config, err := svc.ds.AppConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
171
server/service/mail_test.go
Normal file
171
server/service/mail_test.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||||
|
"github.com/fleetdm/fleet/v4/server/mock"
|
||||||
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||||
|
"github.com/fleetdm/fleet/v4/server/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/guregu/null.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type notTestFoundError struct{}
|
||||||
|
|
||||||
|
func (e *notTestFoundError) Error() string {
|
||||||
|
return "not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *notTestFoundError) IsNotFound() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestNotFoundError() *notTestFoundError {
|
||||||
|
return ¬TestFoundError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is is implemented so that errors.Is(err, sql.ErrNoRows) returns true for an
|
||||||
|
// error of type *notFoundError, without having to wrap sql.ErrNoRows
|
||||||
|
// explicitly.
|
||||||
|
func (e *notTestFoundError) Is(other error) bool {
|
||||||
|
return other == sql.ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMailService(t *testing.T) {
|
||||||
|
// This mail test requires mailpit running on localhost:1026.
|
||||||
|
if _, ok := os.LookupEnv("MAIL_TEST"); !ok {
|
||||||
|
t.Skip("Mail tests are disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
ds := new(mock.Store)
|
||||||
|
svc, ctx := newTestService(t, ds, nil, nil, &TestServerOpts{
|
||||||
|
UseMailService: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||||
|
return &fleet.AppConfig{
|
||||||
|
SMTPSettings: fleet.SMTPSettings{
|
||||||
|
SMTPEnabled: true,
|
||||||
|
SMTPConfigured: true,
|
||||||
|
SMTPAuthenticationType: fleet.AuthTypeNameUserNamePassword,
|
||||||
|
SMTPAuthenticationMethod: fleet.AuthMethodNamePlain,
|
||||||
|
SMTPUserName: "mailpit-username",
|
||||||
|
SMTPPassword: "mailpit-password",
|
||||||
|
SMTPEnableTLS: true,
|
||||||
|
SMTPVerifySSLCerts: true,
|
||||||
|
SMTPPort: 1026,
|
||||||
|
SMTPServer: "localhost",
|
||||||
|
SMTPSenderAddress: "foobar@example.com",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.UserByEmailFunc = func(ctx context.Context, email string) (*fleet.User, error) {
|
||||||
|
return nil, newTestNotFoundError()
|
||||||
|
}
|
||||||
|
|
||||||
|
var invite *fleet.Invite
|
||||||
|
ds.NewInviteFunc = func(ctx context.Context, i *fleet.Invite) (*fleet.Invite, error) {
|
||||||
|
invite = i
|
||||||
|
return invite, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.SaveAppConfigFunc = func(ctx context.Context, info *fleet.AppConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.InviteFunc = func(ctx context.Context, id uint) (*fleet.Invite, error) {
|
||||||
|
return invite, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = test.UserContext(ctx, test.UserAdmin)
|
||||||
|
|
||||||
|
// (1) Modifying the app config `sender_address` field to trigger a test e-mail send.
|
||||||
|
_, err := svc.ModifyAppConfig(ctx, []byte(`{
|
||||||
|
"org_info": {
|
||||||
|
"org_name": "Acme"
|
||||||
|
},
|
||||||
|
"server_settings": {
|
||||||
|
"server_url": "http://someurl"
|
||||||
|
},
|
||||||
|
"smtp_settings": {
|
||||||
|
"enable_smtp": true,
|
||||||
|
"configured": true,
|
||||||
|
"authentication_type": "authtype_username_password",
|
||||||
|
"authentication_method": "authmethod_plain",
|
||||||
|
"user_name": "mailpit-username",
|
||||||
|
"password": "mailpit-password",
|
||||||
|
"enable_ssl_tls": true,
|
||||||
|
"verify_ssl_certs": true,
|
||||||
|
"port": 1026,
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"sender_address": "foobar_updated@example.com"
|
||||||
|
}
|
||||||
|
}`), fleet.ApplySpecOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
getLastMailPitMessage := func() map[string]interface{} {
|
||||||
|
resp, err := http.Get("http://localhost:8026/api/v1/messages?limit=1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
b, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
var m map[string]interface{}
|
||||||
|
err = json.Unmarshal(b, &m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, m["messages"])
|
||||||
|
require.Len(t, m["messages"], 1)
|
||||||
|
lm := (m["messages"]).([]interface{})[0]
|
||||||
|
require.NotNil(t, lm)
|
||||||
|
lastMessage := lm.(map[string]interface{})
|
||||||
|
fmt.Printf("%+v\n", lastMessage)
|
||||||
|
return lastMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMessage := getLastMailPitMessage()
|
||||||
|
require.Equal(t, "Hello from Fleet", lastMessage["Subject"])
|
||||||
|
|
||||||
|
// (2) Inviting a user should send an e-mail to join.
|
||||||
|
_, err = svc.InviteNewUser(ctx, fleet.InvitePayload{
|
||||||
|
Email: ptr.String("foobar_recipient@example.com"),
|
||||||
|
Name: ptr.String("Foobar"),
|
||||||
|
GlobalRole: null.NewString("observer", true),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
lastMessage = getLastMailPitMessage()
|
||||||
|
require.Equal(t, "You are Invited to Fleet", lastMessage["Subject"])
|
||||||
|
|
||||||
|
ds.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) {
|
||||||
|
if id == 1 {
|
||||||
|
return test.UserAdmin, nil
|
||||||
|
}
|
||||||
|
return nil, newNotFoundError()
|
||||||
|
}
|
||||||
|
ds.InviteByEmailFunc = func(ctx context.Context, email string) (*fleet.Invite, error) {
|
||||||
|
return nil, newTestNotFoundError()
|
||||||
|
}
|
||||||
|
ds.PendingEmailChangeFunc = func(ctx context.Context, userID uint, newEmail, token string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ds.SaveUserFunc = func(ctx context.Context, user *fleet.User) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// (3) Changing e-mail address should send an e-mail for confirmation.
|
||||||
|
_, err = svc.ModifyUser(ctx, 1, fleet.UserPayload{
|
||||||
|
Email: ptr.String("useradmin_2@example.com"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
lastMessage = getLastMailPitMessage()
|
||||||
|
require.Equal(t, "Confirm Fleet Email Change", lastMessage["Subject"])
|
||||||
|
}
|
@ -22,7 +22,7 @@ func (mw metricsMiddleware) NewAppConfig(ctx context.Context, p fleet.AppConfig)
|
|||||||
return info, err
|
return info, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw metricsMiddleware) AppConfig(ctx context.Context) (*fleet.AppConfig, error) {
|
func (mw metricsMiddleware) AppConfigObfuscated(ctx context.Context) (*fleet.AppConfig, error) {
|
||||||
var (
|
var (
|
||||||
info *fleet.AppConfig
|
info *fleet.AppConfig
|
||||||
err error
|
err error
|
||||||
@ -32,7 +32,7 @@ func (mw metricsMiddleware) AppConfig(ctx context.Context) (*fleet.AppConfig, er
|
|||||||
mw.requestCount.With(lvs...).Add(1)
|
mw.requestCount.With(lvs...).Add(1)
|
||||||
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
|
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
info, err = mw.Service.AppConfig(ctx)
|
info, err = mw.Service.AppConfigObfuscated(ctx)
|
||||||
return info, err
|
return info, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
||||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||||
"github.com/fleetdm/fleet/v4/server/logging"
|
"github.com/fleetdm/fleet/v4/server/logging"
|
||||||
|
"github.com/fleetdm/fleet/v4/server/mail"
|
||||||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||||
"github.com/fleetdm/fleet/v4/server/service/async"
|
"github.com/fleetdm/fleet/v4/server/service/async"
|
||||||
@ -44,7 +45,6 @@ func newTestService(t *testing.T, ds fleet.Datastore, rs fleet.QueryResultStore,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTestServiceWithConfig(t *testing.T, ds fleet.Datastore, fleetConfig config.FleetConfig, rs fleet.QueryResultStore, lq fleet.LiveQueryStore, opts ...*TestServerOpts) (fleet.Service, context.Context) {
|
func newTestServiceWithConfig(t *testing.T, ds fleet.Datastore, fleetConfig config.FleetConfig, rs fleet.QueryResultStore, lq fleet.LiveQueryStore, opts ...*TestServerOpts) (fleet.Service, context.Context) {
|
||||||
mailer := &mockMailService{SendEmailFn: func(e fleet.Email) error { return nil }}
|
|
||||||
lic := &fleet.LicenseInfo{Tier: fleet.TierFree}
|
lic := &fleet.LicenseInfo{Tier: fleet.TierFree}
|
||||||
writer, err := logging.NewFilesystemLogWriter(fleetConfig.Filesystem.StatusLogFile, kitlog.NewNopLogger(), fleetConfig.Filesystem.EnableLogRotation, fleetConfig.Filesystem.EnableLogCompression, 500, 28, 3)
|
writer, err := logging.NewFilesystemLogWriter(fleetConfig.Filesystem.StatusLogFile, kitlog.NewNopLogger(), fleetConfig.Filesystem.EnableLogRotation, fleetConfig.Filesystem.EnableLogCompression, 500, 28, 3)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -61,6 +61,7 @@ func newTestServiceWithConfig(t *testing.T, ds fleet.Datastore, fleetConfig conf
|
|||||||
mdmStorage nanomdm_storage.AllStorage
|
mdmStorage nanomdm_storage.AllStorage
|
||||||
depStorage nanodep_storage.AllStorage
|
depStorage nanodep_storage.AllStorage
|
||||||
mdmPusher nanomdm_push.Pusher
|
mdmPusher nanomdm_push.Pusher
|
||||||
|
mailer fleet.MailService = &mockMailService{SendEmailFn: func(e fleet.Email) error { return nil }}
|
||||||
)
|
)
|
||||||
var c clock.Clock = clock.C
|
var c clock.Clock = clock.C
|
||||||
if len(opts) > 0 {
|
if len(opts) > 0 {
|
||||||
@ -94,6 +95,9 @@ func newTestServiceWithConfig(t *testing.T, ds fleet.Datastore, fleetConfig conf
|
|||||||
if opts[0].EnrollHostLimiter != nil {
|
if opts[0].EnrollHostLimiter != nil {
|
||||||
enrollHostLimiter = opts[0].EnrollHostLimiter
|
enrollHostLimiter = opts[0].EnrollHostLimiter
|
||||||
}
|
}
|
||||||
|
if opts[0].UseMailService {
|
||||||
|
mailer = mail.NewService()
|
||||||
|
}
|
||||||
|
|
||||||
// allow to explicitly set installer store to nil
|
// allow to explicitly set installer store to nil
|
||||||
is = opts[0].Is
|
is = opts[0].Is
|
||||||
@ -253,6 +257,7 @@ type TestServerOpts struct {
|
|||||||
MDMPusher nanomdm_push.Pusher
|
MDMPusher nanomdm_push.Pusher
|
||||||
HTTPServerConfig *http.Server
|
HTTPServerConfig *http.Server
|
||||||
StartCronSchedules []TestNewScheduleFunc
|
StartCronSchedules []TestNewScheduleFunc
|
||||||
|
UseMailService bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunServerForTestsWithDS(t *testing.T, ds fleet.Datastore, opts ...*TestServerOpts) (map[string]fleet.User, *httptest.Server) {
|
func RunServerForTestsWithDS(t *testing.T, ds fleet.Datastore, opts ...*TestServerOpts) (map[string]fleet.User, *httptest.Server) {
|
||||||
|
@ -763,7 +763,7 @@ func (svc *Service) modifyEmailAddress(ctx context.Context, user *fleet.User, em
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
config, err := svc.AppConfig(ctx)
|
config, err := svc.ds.AppConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ var (
|
|||||||
UserAdmin = &fleet.User{
|
UserAdmin = &fleet.User{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
GlobalRole: ptr.String(fleet.RoleAdmin),
|
GlobalRole: ptr.String(fleet.RoleAdmin),
|
||||||
|
Email: "useradmin@example.com",
|
||||||
}
|
}
|
||||||
UserMaintainer = &fleet.User{
|
UserMaintainer = &fleet.User{
|
||||||
ID: 3,
|
ID: 3,
|
||||||
|
1
tools/mailpit/auth.txt
Normal file
1
tools/mailpit/auth.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
mailpit-username:mailpit-password
|
Loading…
Reference in New Issue
Block a user