Run multiple independent Fleet dev servers in parallel (#41865)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #41848 

Docs updates: https://github.com/fleetdm/fleet/pull/41868/changes

# Checklist for submitter

- changes not needed since this is a dev environment and test issue

## Testing

- [x] QA'd all new/changed functionality manually

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Enhanced test infrastructure to support environment-variable-based
configuration for SAML, mail, database, and S3 services, enabling more
flexible and dynamic test setups.

* **Chores**
* Updated Docker Compose configuration to use environment variables for
service ports, allowing runtime customization while maintaining backward
compatibility with default values.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Victor Lyuboslavsky 2026-03-18 13:58:58 -05:00 committed by GitHub
parent 15ea1042bb
commit 8c2ad7f901
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 208 additions and 118 deletions

View file

@ -40,6 +40,15 @@ import (
"github.com/urfave/cli/v2"
)
var testSAMLIDPMetadataURL = getTestSAMLIDPMetadataURL()
func getTestSAMLIDPMetadataURL() string {
if port := os.Getenv("FLEET_SAML_IDP_HTTP_PORT"); port != "" {
return "http://localhost:" + port + "/simplesaml/saml2/idp/metadata.php"
}
return "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
}
var userRoleSpecList = []*fleet.User{
{
UpdateCreateTimestamps: fleet.UpdateCreateTimestamps{
@ -3619,7 +3628,7 @@ spec:
},
{
desc: "missing required sso entity_id",
spec: `
spec: fmt.Sprintf(`
apiVersion: v1
kind: config
spec:
@ -3628,13 +3637,13 @@ spec:
entity_id: ""
issuer_uri: "http://localhost:8080/simplesaml/saml2/idp/SSOService.php"
idp_name: "SimpleSAML"
metadata_url: "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
`,
metadata_url: "%s"
`, testSAMLIDPMetadataURL),
wantErr: `422 Validation Failed: required`,
},
{
desc: "missing required sso idp_name",
spec: `
spec: fmt.Sprintf(`
apiVersion: v1
kind: config
spec:
@ -3643,8 +3652,8 @@ spec:
entity_id: "https://localhost:8080"
issuer_uri: "http://localhost:8080/simplesaml/saml2/idp/SSOService.php"
idp_name: ""
metadata_url: "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
`,
metadata_url: "%s"
`, testSAMLIDPMetadataURL),
wantErr: `422 Validation Failed: required`,
},
{

View file

@ -26,7 +26,7 @@ services:
# This is required by Percona XtraDB server.
CLUSTER_NAME: fleet
ports:
- "3306:3306"
- "${FLEET_MYSQL_PORT:-3306}:3306"
mysql_test:
image: ${FLEET_MYSQL_IMAGE:-mysql:8.0.44}
@ -76,7 +76,7 @@ services:
environment: *mysql-default-environment
ports:
# ports 3308 and 3309 are used by the main and replica MySQL containers in tools/mysql-replica-testing/docker-compose.yml
- "3310:3306"
- "${FLEET_MYSQL_REPLICA_TEST_PORT:-3310}:3306"
tmpfs:
- /var/lib/mysql:rw,noexec,nosuid
- /tmpfs
@ -85,15 +85,15 @@ services:
mailhog:
image: mailhog/mailhog:latest
ports:
- "8025:8025"
- "1025:1025"
- "${FLEET_MAILHOG_WEB_PORT:-8025}:8025"
- "${FLEET_MAILHOG_SMTP_PORT:-1025}:1025"
# SMTP server with Basic Authentication.
mailpit:
image: axllent/mailpit:latest
ports:
- "8026:8025"
- "1026:1025"
- "${FLEET_MAILPIT_WEB_PORT:-8026}:8025"
- "${FLEET_MAILPIT_SMTP_PORT:-1026}:1025"
volumes:
- ./tools/mailpit/auth.txt:/auth.txt
command: ["--smtp-auth-file=/auth.txt", "--smtp-auth-allow-insecure=true"]
@ -102,8 +102,8 @@ services:
smtp4dev_test:
image: rnwood/smtp4dev:v3
ports:
- "8028:80"
- "1027:25"
- "${FLEET_SMTP4DEV_WEB_PORT:-8028}:80"
- "${FLEET_SMTP4DEV_SMTP_PORT:-1027}:25"
volumes:
- ./tools/smtp4dev:/certs
environment:
@ -114,7 +114,7 @@ services:
redis:
image: redis:6
ports:
- "6379:6379"
- "${FLEET_REDIS_PORT:-6379}:6379"
saml_idp:
image: fleetdm/docker-idp:latest
@ -122,15 +122,15 @@ services:
- ./tools/saml/users.php:/var/www/simplesamlphp/config/authsources.php
- ./tools/saml/config.php:/var/www/simplesamlphp/metadata/saml20-sp-remote.php
ports:
- "9080:8080"
- "9443:8443"
- "${FLEET_SAML_IDP_HTTP_PORT:-9080}:8080"
- "${FLEET_SAML_IDP_HTTPS_PORT:-9443}:8443"
# CAdvisor container allows monitoring other containers. Useful for
# development.
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
ports:
- "5678:8080"
- "${FLEET_CADVISOR_PORT:-5678}:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /sys:/sys:ro
@ -139,7 +139,7 @@ services:
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
- "${FLEET_PROMETHEUS_PORT:-9090}:9090"
volumes:
- ./tools/app/prometheus.yml:/etc/prometheus/prometheus.yml
@ -148,8 +148,8 @@ services:
localstack:
image: localstack/localstack
ports:
- "4566:4566"
- "4571:4571"
- "${FLEET_LOCALSTACK_PORT:-4566}:4566"
- "${FLEET_LOCALSTACK_LEGACY_PORT:-4571}:4571"
environment:
- SERVICES=firehose,kinesis,s3,iam,sts,secretsmanager
@ -157,8 +157,8 @@ services:
s3:
image: rustfs/rustfs:1.0.0-alpha.85
ports:
- "9000:9000"
- "9001:9001"
- "${FLEET_S3_PORT:-9000}:9000"
- "${FLEET_S3_CONSOLE_PORT:-9001}:9001"
environment:
- RUSTFS_ADDRESS=0.0.0.0:9000
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001

View file

@ -15,9 +15,17 @@ import (
const (
accessKeyID = "locals3"
secretAccessKey = "locals3"
testEndpoint = "http://localhost:9000"
)
var testEndpoint = getTestEndpoint()
func getTestEndpoint() string {
if port := os.Getenv("FLEET_S3_PORT"); port != "" {
return "http://localhost:" + port
}
return "http://localhost:9000"
}
func SetupTestSoftwareInstallerStore(tb testing.TB, bucket, prefix string) *SoftwareInstallerStore {
store := setupTestStore(tb, bucket, prefix, NewSoftwareInstallerStore)
tb.Cleanup(func() { cleanupStore(tb, store.s3store) })

View file

@ -7,6 +7,7 @@ import (
"io"
"net/http"
"os"
"strconv"
"testing"
"time"
@ -18,6 +19,35 @@ import (
"github.com/stretchr/testify/require"
)
var testMailpitSMTPPort = getTestMailpitSMTPPort()
var testMailpitWebURL = getTestMailpitWebURL()
var testSMTP4DevSMTPPort = getTestSMTP4DevSMTPPort()
func getTestMailpitSMTPPort() uint {
if port := os.Getenv("FLEET_MAILPIT_SMTP_PORT"); port != "" {
if p, err := strconv.ParseUint(port, 10, 32); err == nil && p > 0 {
return uint(p)
}
}
return 1026
}
func getTestMailpitWebURL() string {
if port := os.Getenv("FLEET_MAILPIT_WEB_PORT"); port != "" {
return "http://127.0.0.1:" + port
}
return "http://127.0.0.1:8026"
}
func getTestSMTP4DevSMTPPort() uint {
if port := os.Getenv("FLEET_SMTP4DEV_SMTP_PORT"); port != "" {
if p, err := strconv.ParseUint(port, 10, 32); err == nil && p > 0 {
return uint(p)
}
}
return 1027
}
var testFunctions = [...]func(*testing.T, fleet.MailService){
testSMTPPlainAuth,
testSMTPPlainAuthInvalidCreds,
@ -37,7 +67,7 @@ func TestCanSendMail(t *testing.T) {
SMTPEnableTLS: false,
SMTPVerifySSLCerts: false,
SMTPEnableStartTLS: false,
SMTPPort: 1026,
SMTPPort: testMailpitSMTPPort,
SMTPServer: "localhost",
SMTPSenderAddress: "test@example.com",
}
@ -49,8 +79,8 @@ func TestCanSendMail(t *testing.T) {
}
func TestMail(t *testing.T) {
// This mail test requires mailhog unauthenticated running on localhost:1025
// and mailpit running on localhost:1026.
// This mail test requires mailhog and mailpit (ports read from env vars
// FLEET_MAILPIT_SMTP_PORT, FLEET_SMTP4DEV_SMTP_PORT, FLEET_MAILPIT_WEB_PORT).
if _, ok := os.LookupEnv("MAIL_TEST"); !ok {
t.Skip("Mail tests are disabled")
}
@ -78,7 +108,7 @@ func testSMTPPlainAuth(t *testing.T, mailer fleet.MailService) {
SMTPEnableTLS: false,
SMTPVerifySSLCerts: false,
SMTPEnableStartTLS: false,
SMTPPort: 1026,
SMTPPort: testMailpitSMTPPort,
SMTPServer: "localhost",
SMTPSenderAddress: "test@example.com",
},
@ -104,7 +134,7 @@ func testSMTPPlainAuthInvalidCreds(t *testing.T, mailer fleet.MailService) {
SMTPEnableTLS: false,
SMTPVerifySSLCerts: false,
SMTPEnableStartTLS: false,
SMTPPort: 1026,
SMTPPort: testMailpitSMTPPort,
SMTPServer: "localhost",
SMTPSenderAddress: "test@example.com",
},
@ -130,7 +160,7 @@ func testSMTPSkipVerify(t *testing.T, mailer fleet.MailService) {
SMTPEnableTLS: true,
SMTPVerifySSLCerts: false,
SMTPEnableStartTLS: true,
SMTPPort: 1027,
SMTPPort: testSMTP4DevSMTPPort,
SMTPServer: "localhost",
SMTPSenderAddress: "test@example.com",
},
@ -153,7 +183,7 @@ func testSMTPNoAuthWithTLS(t *testing.T, mailer fleet.MailService) {
SMTPEnableTLS: true,
SMTPVerifySSLCerts: true,
SMTPEnableStartTLS: true,
SMTPPort: 1027,
SMTPPort: testSMTP4DevSMTPPort,
SMTPServer: "localhost",
SMTPSenderAddress: "test@example.com",
},
@ -181,7 +211,7 @@ func testSMTPDomain(t *testing.T, mailer fleet.MailService) {
SMTPEnableTLS: false,
SMTPVerifySSLCerts: false,
SMTPEnableStartTLS: false,
SMTPPort: 1026,
SMTPPort: testMailpitSMTPPort,
SMTPServer: "localhost",
SMTPDomain: "custom.domain.example.com",
SMTPSenderAddress: randomAddress,
@ -221,7 +251,7 @@ type MailpitMessages struct {
}
func getLastRawMailpitMessageFrom(t *testing.T, address string) string {
res, err := http.Get("http://127.0.0.1:8026/api/v1/messages")
res, err := http.Get(testMailpitWebURL + "/api/v1/messages")
require.NoError(t, err)
var messages MailpitMessages
@ -236,7 +266,7 @@ func getLastRawMailpitMessageFrom(t *testing.T, address string) string {
}
require.NotNilf(t, messageID, "could not find message from %s in mailpit", address)
res, err = http.Get(fmt.Sprintf("http://127.0.0.1:8026/api/v1/message/%s/raw", messageID))
res, err = http.Get(fmt.Sprintf("%s/api/v1/message/%s/raw", testMailpitWebURL, messageID))
require.NoError(t, err)
rawMail, err := io.ReadAll(res.Body)
@ -258,7 +288,7 @@ func testMailTest(t *testing.T, mailer fleet.MailService) {
SMTPEnableTLS: true,
SMTPVerifySSLCerts: true,
SMTPEnableStartTLS: true,
SMTPPort: 1027,
SMTPPort: testSMTP4DevSMTPPort,
SMTPServer: "localhost",
SMTPSenderAddress: "test@example.com",
},

View file

@ -20,9 +20,19 @@ const (
TestUsername = "root"
TestPassword = "toor"
TestReplicaDatabaseSuffix = "_replica"
TestReplicaAddress = "localhost:3310"
)
var TestReplicaAddress = getTestReplicaAddress()
// getTestReplicaAddress returns the MySQL replica test server address from environment variable
// FLEET_MYSQL_REPLICA_TEST_PORT or defaults to localhost:3310
func getTestReplicaAddress() string {
if port := os.Getenv("FLEET_MYSQL_REPLICA_TEST_PORT"); port != "" {
return "localhost:" + port
}
return "localhost:3310"
}
var TestAddress = getTestAddress()
// getTestAddress returns the MySQL test server address from environment variable

View file

@ -4868,7 +4868,7 @@ func (s *integrationEnterpriseTestSuite) TestSSOJITProvisioning() {
t := s.T()
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"server_settings": {
"server_url": "https://localhost:8080"
},
@ -4876,10 +4876,10 @@ func (s *integrationEnterpriseTestSuite) TestSSOJITProvisioning() {
"enable_sso": true,
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php",
"metadata_url": "%s",
"enable_jit_provisioning": false
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotNil(t, acResp)
require.False(t, acResp.SSOSettings.EnableJITProvisioning)
@ -4895,15 +4895,15 @@ func (s *integrationEnterpriseTestSuite) TestSSOJITProvisioning() {
// enable JIT provisioning
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"sso_settings": {
"enable_sso": true,
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php",
"metadata_url": "%s",
"enable_jit_provisioning": true
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotNil(t, acResp)
require.True(t, acResp.SSOSettings.EnableJITProvisioning)
@ -21075,7 +21075,7 @@ func (s *integrationEnterpriseTestSuite) TestSSOIdPInitiatedLogin() {
t := s.T()
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"server_settings": {
"server_url": "https://localhost:8080"
},
@ -21085,9 +21085,9 @@ func (s *integrationEnterpriseTestSuite) TestSSOIdPInitiatedLogin() {
"enable_sso_idp_login": true,
"entity_id": "sso.test.com",
"idp_name": "SimpleSAML",
"metadata_url": "http://127.0.0.1:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotNil(t, acResp)
body := s.LoginSSOUserIDPInitiated("sso_user2", "user123#", "sso.test.com")

View file

@ -95,18 +95,18 @@ func (s *integrationMDMTestSuite) TestDEPEnrollReleaseDeviceGlobal() {
// setup IdP so that AccountConfiguration profile is sent after DEP enrollment
var acResp appConfigResponse
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"mdm": {
"end_user_authentication": {
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
},
"macos_setup": {
"enable_end_user_authentication": true
}
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotEmpty(t, acResp.MDM.EndUserAuthentication)
t.Cleanup(func() {
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
@ -264,13 +264,13 @@ func (s *integrationMDMTestSuite) TestDEPEnrollReleaseDeviceTeam() {
"end_user_authentication": {
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
},
"macos_setup": {
"enable_end_user_authentication": true
}
}
}`, "fleet_ade_test", tm.Name, tm.Name, tm.Name)), http.StatusOK, &acResp)
}`, "fleet_ade_test", tm.Name, tm.Name, tm.Name, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotEmpty(t, acResp.MDM.EndUserAuthentication)
t.Cleanup(func() {
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{

View file

@ -4819,15 +4819,15 @@ func (s *integrationMDMTestSuite) TestMDMMacOSSetup() {
// setup test data
var acResp appConfigResponse
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"mdm": {
"end_user_authentication": {
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
}
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotEmpty(t, acResp.MDM.EndUserAuthentication)
tm, err := s.ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
@ -5205,18 +5205,18 @@ func (s *integrationMDMTestSuite) TestMDMMacOSSetup() {
var acResp appConfigResponse
var errResp validationErrResp
var teamResp teamResponse
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"mdm": {
"end_user_authentication": {
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
},
"macos_setup": {
"enable_end_user_authentication": true
}
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotEmpty(t, acResp.MDM.EndUserAuthentication)
// can't clear IdP settings while end user authentication is enabled (global)
@ -5258,15 +5258,15 @@ func (s *integrationMDMTestSuite) TestMDMMacOSSetup() {
// can't clear IdP settings while end user authentication is enabled on a team
// 1. configure IdP globally
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"mdm": {
"end_user_authentication": {
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
}
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotEmpty(t, acResp.MDM.EndUserAuthentication)
require.False(t, acResp.MDM.MacOSSetup.EnableEndUserAuthentication)
@ -6275,7 +6275,7 @@ func (s *integrationMDMTestSuite) TestSSO() {
// "mdm.test.com" entity ID is defined in `tools/saml/config.php`.
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"server_settings": {
"server_url": "https://localhost:8080"
},
@ -6283,14 +6283,14 @@ func (s *integrationMDMTestSuite) TestSSO() {
"end_user_authentication": {
"entity_id": "mdm.test.com",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
},
"macos_setup": {
"enable_end_user_authentication": true,
"lock_end_user_info": true
}
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
s.runWorker()
require.Contains(t, lastSubmittedProfile.URL, acResp.ServerSettings.ServerURL+"/mdm/sso")
@ -7074,7 +7074,7 @@ func (s *integrationMDMTestSuite) setUpMDMSSO(t *testing.T, lockEndUserInfo bool
// set the SSO fields
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"server_settings": {
"server_url": "https://localhost:8080"
},
@ -7082,18 +7082,18 @@ func (s *integrationMDMTestSuite) setUpMDMSSO(t *testing.T, lockEndUserInfo bool
"end_user_authentication": {
"entity_id": "mdm.test.com",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
},
"macos_setup": {
"enable_end_user_authentication": true,
"lock_end_user_info": `+fmt.Sprintf("%t", lockEndUserInfo)+`
"lock_end_user_info": %t
}
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL, lockEndUserInfo)), http.StatusOK, &acResp)
wantSettings := fleet.SSOProviderSettings{
EntityID: "mdm.test.com",
IDPName: "SimpleSAML",
MetadataURL: "http://localhost:9080/simplesaml/saml2/idp/metadata.php",
MetadataURL: testSAMLIDPMetadataURL,
}
assert.Equal(t, wantSettings, acResp.MDM.EndUserAuthentication.SSOProviderSettings)
@ -11102,18 +11102,18 @@ func (s *integrationMDMTestSuite) TestCustomConfigurationWebURL() {
// configure end-user authentication globally
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"mdm": {
"end_user_authentication": {
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
},
"macos_setup": {
"enable_end_user_authentication": true
}
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
// assign the DEP profile and assert that contains the right values for the URL
configurationWebURLShouldBeEmpty = false
@ -11167,18 +11167,18 @@ func (s *integrationMDMTestSuite) TestCustomConfigurationWebURL() {
// try to enable end user auth again, it fails because configuration_web_url is set
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"mdm": {
"end_user_authentication": {
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
},
"macos_setup": {
"enable_end_user_authentication": true
}
}
}`), http.StatusUnprocessableEntity, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusUnprocessableEntity, &acResp)
// create a team via spec
teamSpecs := map[string]any{
@ -11207,18 +11207,18 @@ func (s *integrationMDMTestSuite) TestCustomConfigurationWebURL() {
err = s.ds.DeleteMDMAppleSetupAssistant(context.Background(), nil)
require.NoError(t, err)
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"mdm": {
"end_user_authentication": {
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
},
"macos_setup": {
"enable_end_user_authentication": true
}
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
// enable end user auth
teamSpecs = map[string]any{
@ -19943,7 +19943,7 @@ func (s *integrationMDMTestSuite) TestBYODEnrollmentWithIdPEnabled() {
res = s.DoRawNoAuth("GET", "/enroll", nil, http.StatusSeeOther, "enroll_secret", "idp")
location := res.Header.Get("Location")
require.NotEmpty(t, location)
require.True(t, strings.HasPrefix(location, "http://localhost:9080/simplesaml/"))
require.True(t, strings.HasPrefix(location, testSAMLIDPBaseURL+"/simplesaml/"))
res = s.LoginMDMSSOUser("sso_user", "user123#")
require.Equal(t, http.StatusSeeOther, res.StatusCode)
@ -19958,7 +19958,7 @@ func (s *integrationMDMTestSuite) TestBYODEnrollmentWithIdPEnabled() {
map[string]string{"Cookie": shared_mdm.BYODIdpCookieName + "=abc"}, "enroll_secret", "idp", "enrollment_reference", "not_matching!")
location = res.Header.Get("Location")
require.NotEmpty(t, location)
require.True(t, strings.HasPrefix(location, "http://localhost:9080/simplesaml/"))
require.True(t, strings.HasPrefix(location, testSAMLIDPBaseURL+"/simplesaml/"))
// requesting the /enroll page again and simulating the BYOD IdP cookie being
// set renders the download profile page when there is a matching enrollment
@ -19974,7 +19974,7 @@ func (s *integrationMDMTestSuite) TestBYODEnrollmentWithIdPEnabled() {
res = s.DoRawNoAuth("GET", "/enroll", nil, http.StatusSeeOther, "enroll_secret", "no-such-secret")
location = res.Header.Get("Location")
require.NotEmpty(t, location)
require.True(t, strings.HasPrefix(location, "http://localhost:9080/simplesaml/"))
require.True(t, strings.HasPrefix(location, testSAMLIDPBaseURL+"/simplesaml/"))
}
func (s *integrationMDMTestSuite) TestIOSiPadOSRefetch() {
@ -21425,7 +21425,7 @@ func (s *integrationMDMTestSuite) TestTechnicianPermissions() {
// Set SMTP, agent options, and SSO settings, and check they are not available for Technicians.
acSetup := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"smtp_settings": {
"enable_smtp": true,
"sender_address": "test@example.com",
@ -21439,7 +21439,7 @@ func (s *integrationMDMTestSuite) TestTechnicianPermissions() {
"enable_sso": true,
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php",
"metadata_url": "%s",
"enable_jit_provisioning": false
},
"agent_options": {
@ -21455,7 +21455,7 @@ func (s *integrationMDMTestSuite) TestTechnicianPermissions() {
}
}
}
}`), http.StatusOK, &acSetup)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acSetup)
t.Cleanup(func() {
acSetup := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{

View file

@ -61,15 +61,15 @@ func (s *integrationSSOTestSuite) TestGetSSOSettings() {
t := s.T()
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"sso_settings": {
"enable_sso": true,
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php",
"metadata_url": "%s",
"enable_jit_provisioning": false
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotNil(t, acResp)
// double-check the settings
@ -167,7 +167,7 @@ func (s *integrationSSOTestSuite) TestSSOLogin() {
t := s.T()
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"server_settings": {
"server_url": "https://localhost:8080"
},
@ -175,9 +175,9 @@ func (s *integrationSSOTestSuite) TestSSOLogin() {
"enable_sso": true,
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotNil(t, acResp)
// Register current number of activities.
@ -270,7 +270,7 @@ func (s *integrationSSOTestSuite) TestSSOLoginDisallowedWithPremiumRoles() {
t := s.T()
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"server_settings": {
"server_url": "https://localhost:8080"
},
@ -278,9 +278,9 @@ func (s *integrationSSOTestSuite) TestSSOLoginDisallowedWithPremiumRoles() {
"enable_sso": true,
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotNil(t, acResp)
user, err := s.ds.UserByEmail(t.Context(), "sso_user2@example.com")
@ -344,14 +344,14 @@ func (s *integrationSSOTestSuite) TestPerformRequiredPasswordResetWithSSO() {
// enable SSO
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"sso_settings": {
"enable_sso": true,
"entity_id": "https://localhost:8080",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotNil(t, acResp)
// perform a required password change using the non-SSO user, works
@ -403,8 +403,8 @@ func (s *integrationSSOTestSuite) TestSSOLoginWithMetadata() {
t := s.T()
acResp := appConfigResponse{}
metadata, err := json.Marshal([]byte(`<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="http://localhost:9080/simplesaml/saml2/idp/metadata.php">
metadata, err := json.Marshal(fmt.Appendf(nil, `<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="%s">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
@ -420,11 +420,11 @@ func (s *integrationSSOTestSuite) TestSSOLoginWithMetadata() {
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:9080/simplesaml/saml2/idp/SingleLogoutService.php"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="%s"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:9080/simplesaml/saml2/idp/SSOService.php"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="%s"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>`))
</md:EntityDescriptor>`, testSAMLIDPMetadataURL, testSAMLIDPSLOURL, testSAMLIDPSSOURL))
require.NoError(t, err)
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"server_settings": {
@ -461,7 +461,7 @@ func (s *integrationSSOTestSuite) TestSSOLoginNoEntityID() {
t := s.T()
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"server_settings": {
"server_url": "https://localhost:8080"
},
@ -469,9 +469,9 @@ func (s *integrationSSOTestSuite) TestSSOLoginNoEntityID() {
"enable_sso": true,
"entity_id": "localhost",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotNil(t, acResp)
ac, err := s.ds.AppConfig(context.Background())
@ -506,7 +506,7 @@ func (s *integrationSSOTestSuite) TestSSOLoginSAMLResponseTampered() {
}
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
"server_settings": {
"server_url": "https://localhost:8080"
},
@ -514,9 +514,9 @@ func (s *integrationSSOTestSuite) TestSSOLoginSAMLResponseTampered() {
"enable_sso": true,
"entity_id": "sso.test.com",
"idp_name": "SimpleSAML",
"metadata_url": "http://localhost:9080/simplesaml/saml2/idp/metadata.php"
"metadata_url": "%s"
}
}`), http.StatusOK, &acResp)
}`, testSAMLIDPMetadataURL)), http.StatusOK, &acResp)
require.NotNil(t, acResp)
// Create sso_user2@example.com if it doesn't exist (because this is
@ -588,9 +588,9 @@ func (s *integrationSSOTestSuite) TestSSOLoginSAMLResponseTampered() {
func (s *integrationSSOTestSuite) TestSSOServerURL() {
t := s.T()
// Use the test metadata instead of trying to fetch from localhost:9080
testMetadata := `
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="http://localhost:9080/simplesaml/saml2/idp/metadata.php">
// Use the test metadata instead of trying to fetch from the SAML IDP
testMetadata := fmt.Sprintf(`
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="%s">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
@ -599,11 +599,11 @@ func (s *integrationSSOTestSuite) TestSSOServerURL() {
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:9080/simplesaml/saml2/idp/SingleLogoutService.php"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="%s"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:9080/simplesaml/saml2/idp/SSOService.php"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="%s"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>`
</md:EntityDescriptor>`, testSAMLIDPMetadataURL, testSAMLIDPSLOURL, testSAMLIDPSSOURL)
// Configure SSO with a specific SSO server URL and inline metadata
acResp := appConfigResponse{}

View file

@ -8,6 +8,7 @@ import (
"io"
"net/http"
"os"
"strconv"
"testing"
"github.com/fleetdm/fleet/v4/server/fleet"
@ -18,6 +19,25 @@ import (
"gopkg.in/guregu/null.v3"
)
var testMailpitSMTPPort = getTestMailpitSMTPPort()
var testMailpitWebURL = getTestMailpitWebURL()
func getTestMailpitSMTPPort() uint {
if port := os.Getenv("FLEET_MAILPIT_SMTP_PORT"); port != "" {
if p, err := strconv.ParseUint(port, 10, 32); err == nil && p > 0 {
return uint(p)
}
}
return 1026
}
func getTestMailpitWebURL() string {
if port := os.Getenv("FLEET_MAILPIT_WEB_PORT"); port != "" {
return "http://127.0.0.1:" + port
}
return "http://127.0.0.1:8026"
}
type notTestFoundError struct{}
func (e *notTestFoundError) Error() string {
@ -40,7 +60,7 @@ func (e *notTestFoundError) Is(other error) bool {
}
func TestMailService(t *testing.T) {
// This mail test requires mailpit running on localhost:1026.
// This mail test requires mailpit (ports read from env vars FLEET_MAILPIT_SMTP_PORT, FLEET_MAILPIT_WEB_PORT).
if _, ok := os.LookupEnv("MAIL_TEST"); !ok {
t.Skip("Mail tests are disabled")
}
@ -61,7 +81,7 @@ func TestMailService(t *testing.T) {
SMTPPassword: "mailpit-password",
SMTPEnableTLS: false,
SMTPVerifySSLCerts: false,
SMTPPort: 1026,
SMTPPort: testMailpitSMTPPort,
SMTPServer: "localhost",
SMTPSenderAddress: "foobar@example.com",
},
@ -101,7 +121,7 @@ func TestMailService(t *testing.T) {
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(`{
_, err := svc.ModifyAppConfig(ctx, fmt.Appendf(nil, `{
"org_info": {
"org_name": "Acme"
},
@ -117,15 +137,15 @@ func TestMailService(t *testing.T) {
"password": "mailpit-password",
"enable_ssl_tls": false,
"verify_ssl_certs": false,
"port": 1026,
"port": %d,
"server": "127.0.0.1",
"sender_address": "foobar_updated@example.com"
}
}`), fleet.ApplySpecOptions{})
}`, testMailpitSMTPPort), fleet.ApplySpecOptions{})
require.NoError(t, err)
getLastMailPitMessage := func() map[string]interface{} {
resp, err := http.Get("http://localhost:8026/api/v1/messages?limit=1")
resp, err := http.Get(testMailpitWebURL + "/api/v1/messages?limit=1")
require.NoError(t, err)
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)

View file

@ -38,6 +38,19 @@ import (
"github.com/stretchr/testify/suite"
)
// testSAMLIDPBaseURL is the SAML IDP base URL, read from FLEET_SAML_IDP_HTTP_PORT (defaults to http://localhost:9080).
var testSAMLIDPBaseURL = getTestSAMLIDPBaseURL()
var testSAMLIDPMetadataURL = testSAMLIDPBaseURL + "/simplesaml/saml2/idp/metadata.php"
var testSAMLIDPSSOURL = testSAMLIDPBaseURL + "/simplesaml/saml2/idp/SSOService.php"
var testSAMLIDPSLOURL = testSAMLIDPBaseURL + "/simplesaml/saml2/idp/SingleLogoutService.php"
func getTestSAMLIDPBaseURL() string {
if port := os.Getenv("FLEET_SAML_IDP_HTTP_PORT"); port != "" {
return "http://localhost:" + port
}
return "http://localhost:9080"
}
type withDS struct {
s *suite.Suite
ds *mysql.Datastore
@ -455,7 +468,7 @@ func (ts *withServer) LoginSSOUserIDPInitiated(username, password, entityID stri
res := ts.loginSSOUserIDPInitiated(
username, password,
"/api/v1/fleet/sso",
fmt.Sprintf("http://127.0.0.1:9080/simplesaml/saml2/idp/SSOService.php?spentityid=%s", entityID),
fmt.Sprintf("%s?spentityid=%s", testSAMLIDPSSOURL, entityID),
http.StatusOK,
)
defer res.Body.Close()