mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 17:05:18 +00:00
337d61c823
Related to #9459, this adds logic to the cron to add a `com.fleetdm.fleetd.config` configuration profile to the `apple_mdm_configuration_profiles` table. As noted in the comments, this makes some assumptions: - This profile will be applied to all hosts in the team (or "no team",) but it will only be used by hosts that have a fleetd installation without an enroll secret and fleet URL (mainly DEP enrolled hosts). - Once the profile is applied to a team (or "no team",) it's not removed if AppConfig.MDM.AppleBMDefaultTeam changes, this is to preserve existing agents using the configuration (mainly ServerURL as EnrollSecret is used only during enrollment)
434 lines
13 KiB
Go
434 lines
13 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestAppConfig(t *testing.T) {
|
|
ds := CreateMySQLDS(t)
|
|
|
|
cases := []struct {
|
|
name string
|
|
fn func(t *testing.T, ds *Datastore)
|
|
}{
|
|
{"OrgInfo", testAppConfigOrgInfo},
|
|
{"AdditionalQueries", testAppConfigAdditionalQueries},
|
|
{"EnrollSecrets", testAppConfigEnrollSecrets},
|
|
{"EnrollSecretsCaseSensitive", testAppConfigEnrollSecretsCaseSensitive},
|
|
{"EnrollSecretRoundtrip", testAppConfigEnrollSecretRoundtrip},
|
|
{"EnrollSecretUniqueness", testAppConfigEnrollSecretUniqueness},
|
|
{"AggregateEnrollSecretPerTeam", testAggregateEnrollSecretPerTeam},
|
|
{"Defaults", testAppConfigDefaults},
|
|
{"Backwards Compatibility", testAppConfigBackwardsCompatibility},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
c.fn(t, ds)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testAppConfigOrgInfo(t *testing.T, ds *Datastore) {
|
|
info := &fleet.AppConfig{
|
|
OrgInfo: fleet.OrgInfo{
|
|
OrgName: "Test",
|
|
OrgLogoURL: "localhost:8080/logo.png",
|
|
},
|
|
}
|
|
|
|
info, err := ds.NewAppConfig(context.Background(), info)
|
|
assert.Nil(t, err)
|
|
require.NotNil(t, info)
|
|
|
|
// Checking some defaults
|
|
require.Equal(t, 24*time.Hour, info.WebhookSettings.Interval.Duration)
|
|
require.False(t, info.WebhookSettings.HostStatusWebhook.Enable)
|
|
require.NotNil(t, info.AgentOptions)
|
|
|
|
info2, err := ds.AppConfig(context.Background())
|
|
require.Nil(t, err)
|
|
assert.Equal(t, info2.OrgInfo.OrgName, info.OrgInfo.OrgName)
|
|
smtpConfigured := info2.SMTPSettings.SMTPConfigured
|
|
assert.False(t, smtpConfigured)
|
|
|
|
info2.OrgInfo.OrgName = "testss"
|
|
info2.SMTPSettings.SMTPDomain = "foo"
|
|
info2.SMTPSettings.SMTPConfigured = true
|
|
info2.SMTPSettings.SMTPSenderAddress = "123"
|
|
info2.SMTPSettings.SMTPServer = "server"
|
|
info2.SMTPSettings.SMTPPort = 100
|
|
info2.SMTPSettings.SMTPAuthenticationType = fleet.AuthTypeNameUserNamePassword
|
|
info2.SMTPSettings.SMTPUserName = "username"
|
|
info2.SMTPSettings.SMTPPassword = "password"
|
|
info2.SMTPSettings.SMTPEnableTLS = false
|
|
info2.SMTPSettings.SMTPAuthenticationMethod = fleet.AuthMethodNameCramMD5
|
|
info2.SMTPSettings.SMTPVerifySSLCerts = true
|
|
info2.SMTPSettings.SMTPEnableStartTLS = true
|
|
info2.SSOSettings.EnableSSO = true
|
|
info2.SSOSettings.EntityID = "test"
|
|
info2.SSOSettings.MetadataURL = "https://idp.com/metadata.xml"
|
|
info2.SSOSettings.IssuerURI = "https://idp.issuer.com"
|
|
info2.SSOSettings.IDPName = "My IDP"
|
|
info2.Features.EnableSoftwareInventory = true
|
|
|
|
err = ds.SaveAppConfig(context.Background(), info2)
|
|
require.Nil(t, err)
|
|
|
|
info3, err := ds.AppConfig(context.Background())
|
|
require.Nil(t, err)
|
|
assert.Equal(t, info2, info3)
|
|
|
|
info4, err := ds.NewAppConfig(context.Background(), info3)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, info3, info4)
|
|
|
|
email := "e@mail.com"
|
|
u := &fleet.User{
|
|
Password: []byte("pass"),
|
|
Email: email,
|
|
SSOEnabled: true,
|
|
GlobalRole: ptr.String(fleet.RoleAdmin),
|
|
}
|
|
_, err = ds.NewUser(context.Background(), u)
|
|
assert.Nil(t, err)
|
|
|
|
verify, err := ds.UserByEmail(context.Background(), email)
|
|
assert.Nil(t, err)
|
|
assert.True(t, verify.SSOEnabled)
|
|
|
|
info4.SSOSettings.EnableSSO = false
|
|
err = ds.SaveAppConfig(context.Background(), info4)
|
|
assert.Nil(t, err)
|
|
|
|
verify, err = ds.UserByEmail(context.Background(), email)
|
|
assert.Nil(t, err)
|
|
assert.False(t, verify.SSOEnabled)
|
|
}
|
|
|
|
func testAppConfigAdditionalQueries(t *testing.T, ds *Datastore) {
|
|
additional := ptr.RawMessage(json.RawMessage("not valid json"))
|
|
info := &fleet.AppConfig{
|
|
OrgInfo: fleet.OrgInfo{
|
|
OrgName: "Test",
|
|
OrgLogoURL: "localhost:8080/logo.png",
|
|
},
|
|
Features: fleet.Features{
|
|
AdditionalQueries: additional,
|
|
},
|
|
}
|
|
|
|
_, err := ds.NewAppConfig(context.Background(), info)
|
|
require.Error(t, err)
|
|
|
|
info.Features.AdditionalQueries = ptr.RawMessage(json.RawMessage(`{}`))
|
|
info, err = ds.NewAppConfig(context.Background(), info)
|
|
require.NoError(t, err)
|
|
|
|
info.Features.AdditionalQueries = ptr.RawMessage(json.RawMessage(`{"foo": "bar"}`))
|
|
info, err = ds.NewAppConfig(context.Background(), info)
|
|
require.NoError(t, err)
|
|
rawJson := *info.Features.AdditionalQueries
|
|
assert.JSONEq(t, `{"foo":"bar"}`, string(rawJson))
|
|
}
|
|
|
|
func testAppConfigEnrollSecrets(t *testing.T, ds *Datastore) {
|
|
ctx := context.Background()
|
|
defer TruncateTables(t, ds)
|
|
|
|
team1, err := ds.NewTeam(ctx, &fleet.Team{Name: "team1"})
|
|
require.NoError(t, err)
|
|
|
|
secret, err := ds.VerifyEnrollSecret(ctx, "missing")
|
|
assert.Error(t, err)
|
|
assert.Nil(t, secret)
|
|
|
|
err = ds.ApplyEnrollSecrets(ctx, &team1.ID,
|
|
[]*fleet.EnrollSecret{
|
|
{Secret: "one_secret", TeamID: &team1.ID},
|
|
},
|
|
)
|
|
assert.NoError(t, err)
|
|
|
|
// keep the created-at timestamp of the team's secret
|
|
t1Secrets, err := ds.GetEnrollSecrets(ctx, &team1.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, t1Secrets, 1)
|
|
t1CreatedAt := t1Secrets[0].CreatedAt
|
|
|
|
secret, err = ds.VerifyEnrollSecret(ctx, "one")
|
|
assert.Error(t, err, "secret should not match")
|
|
assert.Nil(t, secret, "secret should be nil")
|
|
secret, err = ds.VerifyEnrollSecret(ctx, "one_secret")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &team1.ID, secret.TeamID)
|
|
secret, err = ds.VerifyEnrollSecret(ctx, "two_secret")
|
|
assert.Error(t, err)
|
|
assert.Nil(t, secret)
|
|
|
|
// Add global secret
|
|
err = ds.ApplyEnrollSecrets(ctx, nil,
|
|
[]*fleet.EnrollSecret{
|
|
{Secret: "two_secret"},
|
|
},
|
|
)
|
|
assert.NoError(t, err)
|
|
|
|
// keep the created-at timestamp of the global secret
|
|
globalSecrets, err := ds.GetEnrollSecrets(ctx, nil)
|
|
require.NoError(t, err)
|
|
require.Len(t, globalSecrets, 1)
|
|
globalCreatedAt := globalSecrets[0].CreatedAt
|
|
|
|
secret, err = ds.VerifyEnrollSecret(ctx, "one_secret")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &team1.ID, secret.TeamID)
|
|
secret, err = ds.VerifyEnrollSecret(ctx, "two_secret")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, (*uint)(nil), secret.TeamID)
|
|
|
|
// ensure mysql returns a distinct timestamp
|
|
time.Sleep(time.Second)
|
|
|
|
// apply a new team secret, keeping the old one
|
|
err = ds.ApplyEnrollSecrets(ctx, &team1.ID,
|
|
[]*fleet.EnrollSecret{
|
|
{Secret: "one_secret", TeamID: &team1.ID},
|
|
{Secret: "three_secret", TeamID: &team1.ID},
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// apply a new global secret, keeping the old one
|
|
err = ds.ApplyEnrollSecrets(ctx, nil,
|
|
[]*fleet.EnrollSecret{
|
|
{Secret: "two_secret"},
|
|
{Secret: "four_secret"},
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// check that old secrets kept their original created-at timestamp
|
|
t1Secrets, err = ds.GetEnrollSecrets(ctx, &team1.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, t1Secrets, 2)
|
|
sort.Slice(t1Secrets, func(i, j int) bool {
|
|
l, r := t1Secrets[i], t1Secrets[j]
|
|
return l.CreatedAt.Before(r.CreatedAt)
|
|
})
|
|
assert.Equal(t, t1CreatedAt, t1Secrets[0].CreatedAt)
|
|
assert.True(t, t1Secrets[1].CreatedAt.After(t1CreatedAt))
|
|
assert.Equal(t, "one_secret", t1Secrets[0].Secret)
|
|
assert.Equal(t, "three_secret", t1Secrets[1].Secret)
|
|
|
|
globalSecrets, err = ds.GetEnrollSecrets(ctx, nil)
|
|
require.NoError(t, err)
|
|
require.Len(t, globalSecrets, 2)
|
|
sort.Slice(globalSecrets, func(i, j int) bool {
|
|
l, r := globalSecrets[i], globalSecrets[j]
|
|
return l.CreatedAt.Before(r.CreatedAt)
|
|
})
|
|
assert.Equal(t, globalCreatedAt, globalSecrets[0].CreatedAt)
|
|
assert.True(t, globalSecrets[1].CreatedAt.After(globalCreatedAt))
|
|
assert.Equal(t, "two_secret", globalSecrets[0].Secret)
|
|
assert.Equal(t, "four_secret", globalSecrets[1].Secret)
|
|
|
|
// Remove team secrets
|
|
err = ds.ApplyEnrollSecrets(ctx, &team1.ID, []*fleet.EnrollSecret{})
|
|
assert.NoError(t, err)
|
|
secret, err = ds.VerifyEnrollSecret(ctx, "one_secret")
|
|
assert.Error(t, err)
|
|
assert.Nil(t, secret)
|
|
secret, err = ds.VerifyEnrollSecret(ctx, "two_secret")
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, (*uint)(nil), secret.TeamID)
|
|
}
|
|
|
|
func testAppConfigEnrollSecretsCaseSensitive(t *testing.T, ds *Datastore) {
|
|
defer TruncateTables(t, ds)
|
|
|
|
err := ds.ApplyEnrollSecrets(
|
|
context.Background(),
|
|
nil,
|
|
[]*fleet.EnrollSecret{
|
|
{Secret: "one_secret"},
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
_, err = ds.VerifyEnrollSecret(context.Background(), "one_secret")
|
|
assert.NoError(t, err, "enroll secret should match with matching case")
|
|
_, err = ds.VerifyEnrollSecret(context.Background(), "One_Secret")
|
|
assert.Error(t, err, "enroll secret with different case should not verify")
|
|
}
|
|
|
|
func testAppConfigEnrollSecretRoundtrip(t *testing.T, ds *Datastore) {
|
|
defer TruncateTables(t, ds)
|
|
|
|
team1, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
|
|
require.NoError(t, err)
|
|
|
|
secrets, err := ds.GetEnrollSecrets(context.Background(), nil)
|
|
require.NoError(t, err)
|
|
assert.Len(t, secrets, 0)
|
|
|
|
secrets, err = ds.GetEnrollSecrets(context.Background(), &team1.ID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, secrets, 0)
|
|
|
|
expectedSecrets := []*fleet.EnrollSecret{
|
|
{Secret: "one_secret"},
|
|
{Secret: "two_secret"},
|
|
}
|
|
err = ds.ApplyEnrollSecrets(context.Background(), &team1.ID, expectedSecrets)
|
|
require.NoError(t, err)
|
|
|
|
secrets, err = ds.GetEnrollSecrets(context.Background(), &team1.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, secrets, 2)
|
|
// sort secrets before equality checks to ensure proper order
|
|
sort.Slice(secrets, func(i, j int) bool { return secrets[i].Secret < secrets[j].Secret })
|
|
|
|
assert.Equal(t, "one_secret", secrets[0].Secret)
|
|
assert.Equal(t, "two_secret", secrets[1].Secret)
|
|
|
|
expectedSecrets[0].Secret += "_global"
|
|
expectedSecrets[1].Secret += "_global"
|
|
err = ds.ApplyEnrollSecrets(context.Background(), nil, expectedSecrets)
|
|
require.NoError(t, err)
|
|
|
|
secrets, err = ds.GetEnrollSecrets(context.Background(), nil)
|
|
require.NoError(t, err)
|
|
require.Len(t, secrets, 2)
|
|
|
|
}
|
|
|
|
func testAppConfigEnrollSecretUniqueness(t *testing.T, ds *Datastore) {
|
|
defer TruncateTables(t, ds)
|
|
|
|
team1, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
|
|
require.NoError(t, err)
|
|
|
|
expectedSecrets := []*fleet.EnrollSecret{
|
|
{Secret: "one_secret"},
|
|
}
|
|
err = ds.ApplyEnrollSecrets(context.Background(), &team1.ID, expectedSecrets)
|
|
require.NoError(t, err)
|
|
|
|
// Same secret at global level should not be allowed
|
|
err = ds.ApplyEnrollSecrets(context.Background(), nil, expectedSecrets)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func testAppConfigDefaults(t *testing.T, ds *Datastore) {
|
|
insertAppConfigQuery := `INSERT INTO app_config_json(json_value) VALUES(?) ON DUPLICATE KEY UPDATE json_value = VALUES(json_value)`
|
|
_, err := ds.writer.Exec(insertAppConfigQuery, `{}`)
|
|
require.NoError(t, err)
|
|
|
|
ac, err := ds.AppConfig(context.Background())
|
|
require.NoError(t, err)
|
|
require.Equal(t, 24*time.Hour, ac.WebhookSettings.Interval.Duration)
|
|
require.False(t, ac.WebhookSettings.HostStatusWebhook.Enable)
|
|
require.True(t, ac.Features.EnableHostUsers)
|
|
require.False(t, ac.Features.EnableSoftwareInventory)
|
|
|
|
_, err = ds.writer.Exec(
|
|
insertAppConfigQuery,
|
|
`{"webhook_settings": {"interval": "12h"}, "features": {"enable_host_users": false}}`,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
ac, err = ds.AppConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 12*time.Hour, ac.WebhookSettings.Interval.Duration)
|
|
require.False(t, ac.Features.EnableHostUsers)
|
|
require.False(t, ac.Features.EnableSoftwareInventory)
|
|
}
|
|
|
|
func testAppConfigBackwardsCompatibility(t *testing.T, ds *Datastore) {
|
|
insertAppConfigQuery := `INSERT INTO app_config_json(json_value) VALUES(?) ON DUPLICATE KEY UPDATE json_value = VALUES(json_value)`
|
|
_, err := ds.writer.Exec(insertAppConfigQuery, `
|
|
{
|
|
"host_settings": {
|
|
"enable_host_users": false,
|
|
"enable_software_inventory": true,
|
|
"additional_queries": { "foo": "bar" }
|
|
}
|
|
}`)
|
|
|
|
require.NoError(t, err)
|
|
|
|
ac, err := ds.AppConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, ac.Features.EnableHostUsers)
|
|
require.True(t, ac.Features.EnableSoftwareInventory)
|
|
require.NotNil(t, ac.Features.AdditionalQueries)
|
|
}
|
|
|
|
func testAggregateEnrollSecretPerTeam(t *testing.T, ds *Datastore) {
|
|
ctx := context.Background()
|
|
defer TruncateTables(t, ds)
|
|
|
|
// Add global secret
|
|
err := ds.ApplyEnrollSecrets(ctx, nil,
|
|
[]*fleet.EnrollSecret{
|
|
{Secret: "global_secret"},
|
|
},
|
|
)
|
|
assert.NoError(t, err)
|
|
|
|
// a team with two enroll secrets
|
|
team1, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
|
|
require.NoError(t, err)
|
|
err = ds.ApplyEnrollSecrets(context.Background(), &team1.ID, []*fleet.EnrollSecret{
|
|
{Secret: "team_1_secret_1"},
|
|
{Secret: "team_1_secret_2"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// a team with no enroll secrets
|
|
_, err = ds.NewTeam(context.Background(), &fleet.Team{Name: "team2"})
|
|
require.NoError(t, err)
|
|
|
|
// a team with a single enroll secret
|
|
team3, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team3"})
|
|
require.NoError(t, err)
|
|
err = ds.ApplyEnrollSecrets(context.Background(), &team3.ID, []*fleet.EnrollSecret{
|
|
{Secret: "team_3_secret_1"},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
agg, err := ds.AggregateEnrollSecretPerTeam(ctx)
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, agg, 4)
|
|
sort.Slice(agg, func(i, j int) bool {
|
|
if agg[i].TeamID == nil {
|
|
return true
|
|
}
|
|
|
|
if agg[j].TeamID == nil {
|
|
return false
|
|
}
|
|
return *agg[i].TeamID < *agg[j].TeamID
|
|
})
|
|
|
|
require.ElementsMatch(t, []*fleet.EnrollSecret{
|
|
{TeamID: nil, Secret: "global_secret"},
|
|
{TeamID: ptr.Uint(1), Secret: "team_1_secret_1"},
|
|
{TeamID: ptr.Uint(2), Secret: ""},
|
|
{TeamID: ptr.Uint(3), Secret: "team_3_secret_1"},
|
|
}, agg)
|
|
}
|