mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #38889 PLEASE READ BELOW before looking at file changes Before converting individual files/packages to slog, we generally need to make these 2 changes to make the conversion easier: - Replace uses of `kitlog.With` since they are not fully compatible with our kitlog adapter - Directly use the kitlog adapter logger type instead of the kitlog interface, which will let us have direct access to the underlying slog logger: `*logging.Logger` Note: that I did not replace absolutely all uses of `kitlog.Logger`, but I did remove all uses of `kitlog.With` except for these due to complexity: - server/logging/filesystem.go and the other log writers (webhook, firehose, kinesis, lambda, pubsub, nats) - server/datastore/mysql/nanomdm_storage.go (adapter pattern) - server/vulnerabilities/nvd/* (cascades to CLI tools) - server/service/osquery_utils/queries.go (callback type signatures cascade broadly) - cmd/maintained-apps/ (standalone, so can be transitioned later all at once) Most of the changes in this PR follow these patterns: - `kitlog.Logger` type → `*logging.Logger` - `kitlog.With(logger, ...)` → `logger.With(...)` - `kitlog.NewNopLogger() → logging.NewNopLogger()`, including similar variations such as `logging.NewLogfmtLogger(w)` and `logging.NewJSONLogger(w)` - removed many now-unused kitlog imports Unique changes that the PR review should focus on: - server/platform/logging/kitlog_adapter.go: Core adapter changes - server/platform/logging/logging.go: New convenience functions - server/service/integration_logger_test.go: Test changes for slog # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. - Was added in previous PR ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Migrated the codebase to a unified internal structured logging system for more consistent, reliable logs and observability. * No user-facing functionality changed; runtime behavior and APIs remain compatible. * **Tests** * Updated tests to use the new logging helpers to ensure consistent test logging and validation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
275 lines
8.2 KiB
Go
275 lines
8.2 KiB
Go
package apple_mdm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep"
|
|
"github.com/fleetdm/fleet/v4/server/mock"
|
|
nanodep_mock "github.com/fleetdm/fleet/v4/server/mock/nanodep"
|
|
"github.com/fleetdm/fleet/v4/server/platform/logging"
|
|
"github.com/micromdm/plist"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestDEPService(t *testing.T) {
|
|
t.Run("EnsureDefaultSetupAssistant", func(t *testing.T) {
|
|
ds := new(mock.Store)
|
|
ctx := context.Background()
|
|
logger := logging.NewNopLogger()
|
|
depStorage := new(nanodep_mock.Storage)
|
|
depSvc := NewDEPService(ds, depStorage, logger)
|
|
defaultProfile := depSvc.getDefaultProfile()
|
|
serverURL := "https://example.com/"
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
switch r.URL.Path {
|
|
case "/session":
|
|
_, _ = w.Write([]byte(`{"auth_session_token": "xyz"}`))
|
|
case "/profile":
|
|
_, _ = w.Write([]byte(`{"profile_uuid": "abcd"}`))
|
|
body, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
var got godep.Profile
|
|
err = json.Unmarshal(body, &got)
|
|
require.NoError(t, err)
|
|
require.Contains(t, got.URL, serverURL+"api/mdm/apple/enroll?token=")
|
|
assert.Empty(t, got.ConfigurationWebURL)
|
|
got.URL = ""
|
|
got.ConfigurationWebURL = ""
|
|
defaultProfile.AwaitDeviceConfigured = true // this is now always set to true
|
|
require.Equal(t, defaultProfile, &got)
|
|
default:
|
|
require.Fail(t, "unexpected path: %s", r.URL.Path)
|
|
}
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
appCfg := &fleet.AppConfig{}
|
|
appCfg.ServerSettings.ServerURL = serverURL
|
|
return appCfg, nil
|
|
}
|
|
|
|
var savedProfile *fleet.MDMAppleEnrollmentProfile
|
|
ds.NewMDMAppleEnrollmentProfileFunc = func(ctx context.Context, p fleet.MDMAppleEnrollmentProfilePayload) (*fleet.MDMAppleEnrollmentProfile, error) {
|
|
require.Equal(t, fleet.MDMAppleEnrollmentTypeAutomatic, p.Type)
|
|
require.NotEmpty(t, p.Token)
|
|
res := &fleet.MDMAppleEnrollmentProfile{
|
|
Token: p.Token,
|
|
Type: p.Type,
|
|
DEPProfile: p.DEPProfile,
|
|
UpdateCreateTimestamps: fleet.UpdateCreateTimestamps{
|
|
UpdateTimestamp: fleet.UpdateTimestamp{UpdatedAt: time.Now()},
|
|
},
|
|
}
|
|
savedProfile = res
|
|
return res, nil
|
|
}
|
|
|
|
ds.GetMDMAppleEnrollmentProfileByTypeFunc = func(ctx context.Context, typ fleet.MDMAppleEnrollmentType) (*fleet.MDMAppleEnrollmentProfile, error) {
|
|
require.Equal(t, fleet.MDMAppleEnrollmentTypeAutomatic, typ)
|
|
if savedProfile == nil {
|
|
return nil, notFoundError{}
|
|
}
|
|
return savedProfile, nil
|
|
}
|
|
|
|
var defaultProfileUUID string
|
|
ds.GetMDMAppleDefaultSetupAssistantFunc = func(ctx context.Context, teamID *uint, orgName string) (profileUUID string, updatedAt time.Time, err error) {
|
|
if defaultProfileUUID == "" {
|
|
return "", time.Time{}, nil
|
|
}
|
|
return defaultProfileUUID, time.Now(), nil
|
|
}
|
|
|
|
ds.SetMDMAppleDefaultSetupAssistantProfileUUIDFunc = func(ctx context.Context, teamID *uint, profileUUID, orgName string) error {
|
|
require.Nil(t, teamID)
|
|
defaultProfileUUID = profileUUID
|
|
return nil
|
|
}
|
|
|
|
ds.SaveAppConfigFunc = func(ctx context.Context, info *fleet.AppConfig) error {
|
|
return nil
|
|
}
|
|
|
|
depStorage.RetrieveConfigFunc = func(ctx context.Context, name string) (*client.Config, error) {
|
|
return &client.Config{BaseURL: srv.URL}, nil
|
|
}
|
|
|
|
depStorage.RetrieveAuthTokensFunc = func(ctx context.Context, name string) (*client.OAuth1Tokens, error) {
|
|
return &client.OAuth1Tokens{}, nil
|
|
}
|
|
|
|
depStorage.StoreAssignerProfileFunc = func(ctx context.Context, name string, profileUUID string) error {
|
|
require.NotEmpty(t, profileUUID)
|
|
return nil
|
|
}
|
|
|
|
ds.GetABMTokenOrgNamesAssociatedWithTeamFunc = func(ctx context.Context, teamID *uint) ([]string, error) {
|
|
return []string{"org1"}, nil
|
|
}
|
|
|
|
ds.CountABMTokensWithTermsExpiredFunc = func(ctx context.Context) (int, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
profUUID, modTime, err := depSvc.EnsureDefaultSetupAssistant(ctx, nil, "org1")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "abcd", profUUID)
|
|
require.NotZero(t, modTime)
|
|
require.True(t, ds.NewMDMAppleEnrollmentProfileFuncInvoked)
|
|
require.True(t, ds.GetMDMAppleEnrollmentProfileByTypeFuncInvoked)
|
|
require.True(t, ds.GetMDMAppleDefaultSetupAssistantFuncInvoked)
|
|
require.True(t, ds.SetMDMAppleDefaultSetupAssistantProfileUUIDFuncInvoked)
|
|
require.True(t, depStorage.RetrieveConfigFuncInvoked)
|
|
require.False(t, depStorage.StoreAssignerProfileFuncInvoked) // not used anymore
|
|
})
|
|
|
|
t.Run("EnrollURL", func(t *testing.T) {
|
|
const serverURL = "https://example.com/"
|
|
|
|
appCfg := &fleet.AppConfig{}
|
|
appCfg.ServerSettings.ServerURL = serverURL
|
|
url, err := EnrollURL("token", appCfg)
|
|
require.NoError(t, err)
|
|
require.Equal(t, url, serverURL+"api/mdm/apple/enroll?token=token")
|
|
})
|
|
}
|
|
|
|
func TestAddEnrollmentRefToFleetURL(t *testing.T) {
|
|
const (
|
|
baseFleetURL = "https://example.com"
|
|
reference = "enroll-ref"
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
fleetURL string
|
|
reference string
|
|
expectedOutput string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "empty Reference",
|
|
fleetURL: baseFleetURL,
|
|
reference: "",
|
|
expectedOutput: baseFleetURL,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid URL and Reference",
|
|
fleetURL: baseFleetURL,
|
|
reference: reference,
|
|
expectedOutput: baseFleetURL + "?" + mobileconfig.FleetEnrollReferenceKey + "=" + reference,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "invalid URL",
|
|
fleetURL: "://invalid-url",
|
|
reference: reference,
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
output, err := AddEnrollmentRefToFleetURL(tc.fleetURL, tc.reference)
|
|
if tc.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedOutput, output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateEnrollmentProfileMobileconfig(t *testing.T) {
|
|
type scepPayload struct {
|
|
Challenge string
|
|
URL string
|
|
}
|
|
|
|
type enrollmentPayload struct {
|
|
PayloadType string
|
|
ServerURL string // used by the enrollment payload
|
|
PayloadContent scepPayload // scep contains a nested payload content dict
|
|
}
|
|
|
|
type enrollmentProfile struct {
|
|
PayloadIdentifier string
|
|
PayloadContent []enrollmentPayload
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
orgName string
|
|
fleetURL string
|
|
scepChallenge string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "valid input with simple values",
|
|
orgName: "Fleet",
|
|
fleetURL: "https://example.com",
|
|
scepChallenge: "testChallenge",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "organization name and enroll secret with special characters",
|
|
orgName: `Fleet & Co. "Special" <Org>`,
|
|
fleetURL: "https://example.com",
|
|
scepChallenge: "test/&Challenge",
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := GenerateEnrollmentProfileMobileconfig(tt.orgName, tt.fleetURL, tt.scepChallenge, "com.foo.bar")
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
|
|
var profile enrollmentProfile
|
|
|
|
require.NoError(t, plist.Unmarshal(result, &profile))
|
|
|
|
for _, p := range profile.PayloadContent {
|
|
switch p.PayloadType {
|
|
case "com.apple.security.scep":
|
|
scepURL, err := ResolveAppleSCEPURL(tt.fleetURL)
|
|
require.NoError(t, err)
|
|
require.Equal(t, scepURL, p.PayloadContent.URL)
|
|
require.Equal(t, tt.scepChallenge, p.PayloadContent.Challenge)
|
|
case "com.apple.mdm":
|
|
mdmURL, err := ResolveAppleMDMURL(tt.fleetURL)
|
|
require.NoError(t, err)
|
|
require.Contains(t, mdmURL, p.ServerURL)
|
|
default:
|
|
require.Failf(t, "unrecognized payload type in enrollment profile: %s", p.PayloadType)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type notFoundError struct{}
|
|
|
|
func (e notFoundError) IsNotFound() bool { return true }
|
|
|
|
func (e notFoundError) Error() string { return "not found" }
|