Revert changes to DEP enrollment flow (#20506)
Reverts #20162 and #20355 per [QA findings](https://github.com/fleetdm/fleet/issues/19185#issuecomment-2229544188)
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
- Fixed bug where MDM devices would fail to renew enrollment profiles if applicable end user authentication
|
||||
settings changed after the device was enrolled.
|
||||
- Improved device user account creation during MDM IdP enrollment flow by removing enrollment
|
||||
reference from MDM server url in Fleet-generated enrollment profiles.
|
||||
@@ -942,8 +942,6 @@ the way that the Fleet server works.
|
||||
"get_frontend",
|
||||
service.ServeFrontend(config.Server.URLPrefix, config.Server.SandboxEnabled, httpLogger),
|
||||
)
|
||||
frontendHandler = service.WithDEPWebviewRedirect(svc, logger, frontendHandler, config.Server.URLPrefix)
|
||||
|
||||
apiHandler = service.MakeHandler(svc, config, httpLogger, limiterStore)
|
||||
|
||||
setupRequired, err := svc.SetupRequired(baseCtx)
|
||||
|
||||
@@ -29,8 +29,6 @@ const EnrollmentGate = ({
|
||||
}: IEnrollmentGateProps) => {
|
||||
const [showEULA, setShowEULA] = useState(Boolean(eulaToken));
|
||||
|
||||
const dep_device_info = localStorage.getItem("dep_device_info") || "";
|
||||
|
||||
if (!profileToken || error) {
|
||||
return <SSOError />;
|
||||
}
|
||||
@@ -57,11 +55,10 @@ const EnrollmentGate = ({
|
||||
|
||||
return (
|
||||
<RedirectTo
|
||||
url={endpoints.MDM_APPLE_ENROLLMENT_PROFILE({
|
||||
token: profileToken,
|
||||
ref: enrollmentReference,
|
||||
dep_device_info,
|
||||
})}
|
||||
url={endpoints.MDM_APPLE_ENROLLMENT_PROFILE(
|
||||
profileToken,
|
||||
enrollmentReference
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import { WithRouterProps } from "react-router";
|
||||
|
||||
import mdmAPI, { IMDMSSOParams } from "services/entities/mdm";
|
||||
import mdmAPI from "services/entities/mdm";
|
||||
|
||||
import SSOError from "components/MDM/SSOError";
|
||||
import Spinner from "components/Spinner/Spinner";
|
||||
@@ -11,15 +10,10 @@ import { IMdmSSOReponse } from "interfaces/mdm";
|
||||
|
||||
const baseClass = "mdm-apple-sso-page";
|
||||
|
||||
const DEPSSOLoginPage = ({
|
||||
location: { query },
|
||||
}: WithRouterProps<object, IMDMSSOParams>) => {
|
||||
const { dep_device_info } = query;
|
||||
localStorage.setItem("dep_device_info", dep_device_info || "");
|
||||
|
||||
const { error } = useQuery<IMdmSSOReponse, AxiosError>(
|
||||
const DEPSSOLoginPage = () => {
|
||||
const { error } = useQuery<void, AxiosError, IMdmSSOReponse>(
|
||||
["dep_sso"],
|
||||
() => mdmAPI.initiateMDMAppleSSO(query),
|
||||
() => mdmAPI.initiateMDMAppleSSO(),
|
||||
{
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
DiskEncryptionStatus,
|
||||
IHostMdmProfile,
|
||||
IMdmProfile,
|
||||
IMdmSSOReponse,
|
||||
MdmProfileStatus,
|
||||
} from "interfaces/mdm";
|
||||
import { API_NO_TEAM_ID } from "interfaces/team";
|
||||
@@ -68,16 +67,6 @@ export interface IAppleSetupEnrollmentProfileResponse {
|
||||
enrollment_profile: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface IMDMSSOParams {
|
||||
dep_device_info: string;
|
||||
}
|
||||
|
||||
export interface IMDMAppleEnrollmentProfileParams {
|
||||
token: string;
|
||||
ref?: string;
|
||||
dep_device_info?: string;
|
||||
}
|
||||
|
||||
const mdmService = {
|
||||
resetEncryptionKey: (token: string) => {
|
||||
const { DEVICE_USER_RESET_ENCRYPTION_KEY } = endpoints;
|
||||
@@ -192,9 +181,9 @@ const mdmService = {
|
||||
});
|
||||
},
|
||||
|
||||
initiateMDMAppleSSO: (params: IMDMSSOParams): Promise<IMdmSSOReponse> => {
|
||||
initiateMDMAppleSSO: () => {
|
||||
const { MDM_APPLE_SSO } = endpoints;
|
||||
return sendRequest("POST", MDM_APPLE_SSO, params);
|
||||
return sendRequest("POST", MDM_APPLE_SSO, {});
|
||||
},
|
||||
|
||||
getBootstrapPackageMetadata: (teamId: number) => {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { IMDMAppleEnrollmentProfileParams } from "services/entities/mdm";
|
||||
|
||||
const API_VERSION = "latest";
|
||||
|
||||
export default {
|
||||
@@ -95,14 +93,11 @@ export default {
|
||||
MDM_PROFILES_STATUS_SUMMARY: `/${API_VERSION}/fleet/mdm/profiles/summary`,
|
||||
MDM_DISK_ENCRYPTION_SUMMARY: `/${API_VERSION}/fleet/mdm/disk_encryption/summary`,
|
||||
MDM_APPLE_SSO: `/${API_VERSION}/fleet/mdm/sso`,
|
||||
MDM_APPLE_ENROLLMENT_PROFILE: ({
|
||||
token,
|
||||
ref,
|
||||
dep_device_info,
|
||||
}: IMDMAppleEnrollmentProfileParams) => {
|
||||
MDM_APPLE_ENROLLMENT_PROFILE: (token: string, ref?: string) => {
|
||||
const query = new URLSearchParams({ token });
|
||||
ref && query.append("enrollment_reference", ref);
|
||||
dep_device_info && query.append("dep_device_info", dep_device_info);
|
||||
if (ref) {
|
||||
query.append("enrollment_reference", ref);
|
||||
}
|
||||
return `/api/mdm/apple/enroll?${query}`;
|
||||
},
|
||||
MDM_APPLE_SETUP_ENROLLMENT_PROFILE: `/${API_VERSION}/fleet/mdm/apple/enrollment_profile`,
|
||||
|
||||
@@ -2732,13 +2732,8 @@ func (ds *Datastore) InsertMDMIdPAccount(ctx context.Context, account *fleet.MDM
|
||||
return ctxerr.Wrap(ctx, err, "creating new MDM IdP account")
|
||||
}
|
||||
|
||||
func (ds *Datastore) AssociateMDMIdPAccount(ctx context.Context, accountUUID, hostUUID string) error {
|
||||
_, err := ds.writer(ctx).ExecContext(ctx, `UPDATE mdm_idp_accounts SET host_uuid = ? WHERE uuid = ?`, hostUUID, accountUUID)
|
||||
return ctxerr.Wrap(ctx, err, "associating MDM IdP account with device")
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetMDMIdPAccountByEmail(ctx context.Context, email string) (*fleet.MDMIdPAccount, error) {
|
||||
stmt := `SELECT uuid, username, fullname, email, host_uuid, fleet_enroll_ref FROM mdm_idp_accounts WHERE email = ?`
|
||||
stmt := `SELECT uuid, username, fullname, email FROM mdm_idp_accounts WHERE email = ?`
|
||||
var acct fleet.MDMIdPAccount
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &acct, stmt, email)
|
||||
if err != nil {
|
||||
@@ -2750,39 +2745,13 @@ func (ds *Datastore) GetMDMIdPAccountByEmail(ctx context.Context, email string)
|
||||
return &acct, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetMDMIdPAccountByAccountUUID(ctx context.Context, accountUUID string) (*fleet.MDMIdPAccount, error) {
|
||||
stmt := `SELECT uuid, username, fullname, email, host_uuid, fleet_enroll_ref FROM mdm_idp_accounts WHERE uuid = ?`
|
||||
func (ds *Datastore) GetMDMIdPAccountByUUID(ctx context.Context, uuid string) (*fleet.MDMIdPAccount, error) {
|
||||
stmt := `SELECT uuid, username, fullname, email FROM mdm_idp_accounts WHERE uuid = ?`
|
||||
var acct fleet.MDMIdPAccount
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &acct, stmt, accountUUID)
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &acct, stmt, uuid)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ctxerr.Wrap(ctx, notFound("MDMIdPAccount").WithMessage(fmt.Sprintf("with uuid %s", accountUUID)))
|
||||
}
|
||||
return nil, ctxerr.Wrap(ctx, err, "select mdm_idp_accounts")
|
||||
}
|
||||
return &acct, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetMDMIdPAccountByHostUUID(ctx context.Context, hostUUID string) (*fleet.MDMIdPAccount, error) {
|
||||
stmt := `SELECT uuid, username, fullname, email, host_uuid, fleet_enroll_ref FROM mdm_idp_accounts WHERE host_uuid = ?`
|
||||
var acct fleet.MDMIdPAccount
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &acct, stmt, hostUUID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ctxerr.Wrap(ctx, notFound("MDMIdPAccount").WithMessage(fmt.Sprintf("with host uuid %s", hostUUID)))
|
||||
}
|
||||
return nil, ctxerr.Wrap(ctx, err, "select mdm_idp_accounts")
|
||||
}
|
||||
return &acct, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetMDMIdPAccountByLegacyEnrollRef(ctx context.Context, ref string) (*fleet.MDMIdPAccount, error) {
|
||||
stmt := `SELECT uuid, username, fullname, email, host_uuid, fleet_enroll_ref FROM mdm_idp_accounts WHERE fleet_enroll_ref = ?`
|
||||
var acct fleet.MDMIdPAccount
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &acct, stmt, ref)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ctxerr.Wrap(ctx, notFound("MDMIdPAccount").WithMessage(fmt.Sprintf("with fleet_enroll_ref %s", ref)))
|
||||
return nil, ctxerr.Wrap(ctx, notFound("MDMIdPAccount").WithMessage(fmt.Sprintf("with uuid %s", uuid)))
|
||||
}
|
||||
return nil, ctxerr.Wrap(ctx, err, "select mdm_idp_accounts")
|
||||
}
|
||||
@@ -3676,20 +3645,6 @@ func (ds *Datastore) MDMResetEnrollment(ctx context.Context, hostUUID string) er
|
||||
return ctxerr.Wrap(ctx, err, "resetting disk encryption key information for host")
|
||||
}
|
||||
|
||||
// Delete any stored host emails sourced from mdm_idp_accounts. Note that we aren't deleting
|
||||
// the mdm_idp_accounts themselves, just the host_emails associated with the host. This
|
||||
// ensures that hosts that reenroll without IdP will have their emails removed. Hosts
|
||||
// that reenroll with IdP will have their emails re-added in the
|
||||
// AppleMDMPostDEPEnrollmentTask.
|
||||
//
|
||||
// TODO: Should we be applying any platform check here or is this ok for macOS, iOS, and Windows?
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
DELETE FROM host_emails
|
||||
WHERE host_id = ? AND source = ?`, host.ID, fleet.DeviceMappingMDMIdpAccounts)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "resetting host_emails sourced from mdm_idp_accounts")
|
||||
}
|
||||
|
||||
if host.Platform == "darwin" {
|
||||
// Deleting the matching entry on this table will cause
|
||||
// the aggregate report to show this host as 'pending' to
|
||||
|
||||
@@ -2470,11 +2470,11 @@ func testMDMAppleIdPAccount(t *testing.T, ds *Datastore) {
|
||||
require.ErrorAs(t, err, &nfe)
|
||||
require.Nil(t, out)
|
||||
|
||||
out, err = ds.GetMDMIdPAccountByAccountUUID(ctx, acc.UUID)
|
||||
out, err = ds.GetMDMIdPAccountByUUID(ctx, acc.UUID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, acc, out)
|
||||
|
||||
out, err = ds.GetMDMIdPAccountByAccountUUID(ctx, "BAD-TOKEN")
|
||||
out, err = ds.GetMDMIdPAccountByUUID(ctx, "BAD-TOKEN")
|
||||
require.ErrorAs(t, err, &nfe)
|
||||
require.Nil(t, out)
|
||||
}
|
||||
|
||||
@@ -3629,78 +3629,19 @@ func (ds *Datastore) SetOrUpdateMDMData(
|
||||
)
|
||||
}
|
||||
|
||||
func (ds *Datastore) SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRef(
|
||||
func (ds *Datastore) SetOrUpdateHostEmailsFromMdmIdpAccounts(
|
||||
ctx context.Context,
|
||||
hostID uint,
|
||||
fleetEnrollmentRef string,
|
||||
) error {
|
||||
if fleetEnrollmentRef == "" {
|
||||
return ctxerr.New(ctx, "missing fleet enroll ref to upsert host emails with mdm idp account")
|
||||
}
|
||||
|
||||
var email *string
|
||||
idp, err := ds.GetMDMIdPAccountByLegacyEnrollRef(ctx, fleetEnrollmentRef)
|
||||
if err != nil {
|
||||
return err
|
||||
if fleetEnrollmentRef != "" {
|
||||
idp, err := ds.GetMDMIdPAccountByUUID(ctx, fleetEnrollmentRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
email = &idp.Email
|
||||
}
|
||||
email = &idp.Email
|
||||
|
||||
return ds.updateOrInsert(
|
||||
ctx,
|
||||
`UPDATE host_emails SET email = ? WHERE host_id = ? AND source = ?`,
|
||||
`INSERT INTO host_emails (email, host_id, source) VALUES (?, ?, ?)`,
|
||||
email, hostID, fleet.DeviceMappingMDMIdpAccounts,
|
||||
)
|
||||
}
|
||||
|
||||
func (ds *Datastore) SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUID(
|
||||
ctx context.Context,
|
||||
hostUUID string,
|
||||
) error {
|
||||
if hostUUID == "" {
|
||||
return ctxerr.New(ctx, "missing host uuid to upsert host emails with mdm idp account")
|
||||
}
|
||||
|
||||
var hid uint
|
||||
var email string
|
||||
host, err := ds.HostLiteByIdentifier(ctx, hostUUID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "getting host by identifier to upsert host emails with mdm idp account")
|
||||
}
|
||||
hid = host.ID
|
||||
|
||||
idp, err := ds.GetMDMIdPAccountByHostUUID(ctx, hostUUID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "getting idp account by host uuid to upsert host emails with mdm idp account")
|
||||
}
|
||||
email = idp.Email
|
||||
|
||||
return ds.updateOrInsert(
|
||||
ctx,
|
||||
`UPDATE host_emails SET email = ? WHERE host_id = ? AND source = ?`,
|
||||
`INSERT INTO host_emails (email, host_id, source) VALUES (?, ?, ?)`,
|
||||
email, hid, fleet.DeviceMappingMDMIdpAccounts,
|
||||
)
|
||||
}
|
||||
|
||||
func (ds *Datastore) SetOrUpdateEmailsFromMDMIdPAccountsByHostID(
|
||||
ctx context.Context,
|
||||
hostID uint,
|
||||
hostUUID string,
|
||||
) error {
|
||||
if hostID == 0 {
|
||||
return ctxerr.New(ctx, "missing host id to upsert host emails with mdm idp account")
|
||||
}
|
||||
if hostUUID == "" {
|
||||
return ctxerr.New(ctx, "missing host uuid to upsert host emails with mdm idp account")
|
||||
}
|
||||
|
||||
var email string
|
||||
idp, err := ds.GetMDMIdPAccountByHostUUID(ctx, hostUUID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "getting idp account by host uuid to upsert host emails with mdm idp account")
|
||||
}
|
||||
email = idp.Email
|
||||
|
||||
return ds.updateOrInsert(
|
||||
ctx,
|
||||
@@ -5194,8 +5135,8 @@ func (ds *Datastore) loadHostLite(ctx context.Context, id *uint, identifier *str
|
||||
SELECT
|
||||
h.id,
|
||||
h.team_id,
|
||||
COALESCE(h.osquery_host_id, '') AS osquery_host_id,
|
||||
COALESCE(h.node_key, '') AS node_key,
|
||||
h.osquery_host_id,
|
||||
h.node_key,
|
||||
h.hostname,
|
||||
h.uuid,
|
||||
h.hardware_serial,
|
||||
|
||||
@@ -7623,7 +7623,6 @@ func testHostsLoadHostByOrbitNodeKey(t *testing.T, ds *Datastore) {
|
||||
// compare only the fields we care about
|
||||
h.CreatedAt = returned.CreatedAt
|
||||
h.UpdatedAt = returned.UpdatedAt
|
||||
h.LastEnrolledAt = returned.LastEnrolledAt // FIXME: this seems to be flaky (off by one second) in CI so don't compare it
|
||||
h.DEPAssignedToFleet = ptr.Bool(false)
|
||||
assert.Equal(t, h, returned)
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
MigrationClient.AddMigration(Up_20240709175341, Down_20240709175341)
|
||||
}
|
||||
|
||||
func Up_20240709175341(tx *sql.Tx) error {
|
||||
alterStmt := `
|
||||
ALTER TABLE mdm_idp_accounts ADD COLUMN (
|
||||
host_uuid varchar(63) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
fleet_enroll_ref varchar(63) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''
|
||||
)`
|
||||
|
||||
if _, err := tx.Exec(alterStmt); err != nil {
|
||||
return fmt.Errorf("failed to alter mdm_idp_accounts table: %w", err)
|
||||
}
|
||||
|
||||
updateStmt := `
|
||||
UPDATE
|
||||
mdm_idp_accounts mia
|
||||
LEFT JOIN host_mdm hmdm ON mia.uuid = hmdm.fleet_enroll_ref
|
||||
LEFT JOIN hosts h ON hmdm.host_id = h.id
|
||||
SET
|
||||
mia.fleet_enroll_ref = mia.uuid,
|
||||
mia.host_uuid = COALESCE(h.uuid, ''),
|
||||
mia.updated_at = mia.updated_at`
|
||||
|
||||
if _, err := tx.Exec(updateStmt); err != nil {
|
||||
return fmt.Errorf("failed to update data in mdm_idp_accounts: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Down_20240709175341(tx *sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUp_20240709175341(t *testing.T) {
|
||||
db := applyUpToPrev(t)
|
||||
then := time.Now().UTC().Add(-time.Hour).Round(time.Second)
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
// create some hosts with uuids that will be used in migration to populate new columns in mdm_idp_accounts
|
||||
id := execNoErrLastID(t, db, `INSERT INTO hosts (uuid) VALUES (?);`,
|
||||
fmt.Sprintf("host_uuid%d", i),
|
||||
)
|
||||
|
||||
// insert host_mdm records that will be used in migration to populate new columns in mdm_idp_accounts
|
||||
execNoErr(t, db, `INSERT INTO host_mdm (host_id, fleet_enroll_ref) VALUES (?, ?);`,
|
||||
id, fmt.Sprintf("uuid%d", i),
|
||||
)
|
||||
|
||||
execNoErr(t, db, `
|
||||
INSERT INTO
|
||||
mdm_idp_accounts (uuid, username, fullname, email, created_at, updated_at)
|
||||
VALUES
|
||||
(?,?,?,?,?,?)
|
||||
`, fmt.Sprintf("uuid%d", i), fmt.Sprintf("username%d", i), fmt.Sprintf("fullname%d", i), fmt.Sprintf("email%d", i), then, then)
|
||||
}
|
||||
|
||||
// insert an orphaned mdm_idp_account
|
||||
execNoErr(t, db, `
|
||||
INSERT INTO
|
||||
mdm_idp_accounts (uuid, username, fullname, email, created_at, updated_at)
|
||||
VALUES
|
||||
(?,?,?,?,?,?)`, "uuid4", "username4", "fullname4", "email4", then, then)
|
||||
|
||||
// Apply current migration.
|
||||
applyNext(t, db)
|
||||
|
||||
var dest []struct {
|
||||
fleet.MDMIdPAccount
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
require.NoError(t, sqlx.SelectContext(context.Background(), db, &dest, `SELECT * FROM mdm_idp_accounts ORDER BY uuid;`))
|
||||
require.Len(t, dest, 5)
|
||||
|
||||
for i, got := range dest {
|
||||
require.Equal(t, fmt.Sprintf("uuid%d", i), got.UUID) // no change
|
||||
require.Equal(t, fmt.Sprintf("username%d", i), got.Username) // no change
|
||||
require.Equal(t, fmt.Sprintf("fullname%d", i), got.Fullname) // no change
|
||||
require.Equal(t, fmt.Sprintf("email%d", i), got.Email) // no change
|
||||
require.Equal(t, then, got.CreatedAt) // no change
|
||||
require.Equal(t, then, got.UpdatedAt) // no change
|
||||
|
||||
if i == 4 {
|
||||
// this is the orphaned mdm_idp_account
|
||||
require.Equal(t, "uuid4", got.FleetEnrollRef) // new column fleet_enroll_ref is set to uuid in migration
|
||||
require.Empty(t, got.HostUUID) // new column host_uuid is not set in migration because there is no matching host
|
||||
} else {
|
||||
require.Equal(t, fmt.Sprintf("host_uuid%d", i), got.HostUUID) // new column host_uuid is set to host uuid in migration
|
||||
require.Equal(t, fmt.Sprintf("uuid%d", i), got.FleetEnrollRef) // new column fleet_enroll_ref is set to uuid in migration
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
MigrationClient.AddMigration(Up_20240710152744, Down_20240710152744)
|
||||
}
|
||||
|
||||
func Up_20240710152744(tx *sql.Tx) error {
|
||||
_, err := tx.Exec(`
|
||||
ALTER TABLE mdm_idp_accounts
|
||||
ADD INDEX idx_mdm_idp_accounts_host_uuid (host_uuid)`,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add idx_mdm_idp_accounts_host_uuid: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Down_20240710152744(tx *sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -861,26 +861,9 @@ type Datastore interface {
|
||||
|
||||
SetOrUpdateMunkiInfo(ctx context.Context, hostID uint, version string, errors, warnings []string) error
|
||||
SetOrUpdateMDMData(ctx context.Context, hostID uint, isServer, enrolled bool, serverURL string, installedFromDep bool, name string, fleetEnrollRef string) error
|
||||
// SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRef sets or updates the host emails associated with the provided
|
||||
// host based on the MDM IdP account information associated with the provided fleet enrollment
|
||||
// reference.
|
||||
//
|
||||
// Deprecated: Use SetOrUpdateHostEmailsFromMdmIdpAccountsByHostUUID instead.
|
||||
SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRef(ctx context.Context, hostID uint, fleetEnrollmentRef string) error
|
||||
// SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUID sets or updates the host emails associated
|
||||
// with the provided host uuid based on the MDM IdP account information (if any) associated with the
|
||||
// provided host uuid.
|
||||
//
|
||||
// Note: Use this method only if the host ID is not available to the caller, otherwise use
|
||||
// SetOrUpdateHostEmailsFromMdmIdpAccountsByHostID.
|
||||
SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUID(ctx context.Context, hostUUID string) error
|
||||
// SetOrUpdateEmailsFromMDMIdPAccountsByHostID sets or updates the host emails associated with
|
||||
// the provided host ID based on the MDM IdP account information (if any) associated with the
|
||||
// provided host UUID.
|
||||
//
|
||||
// Note: Use this method when both the host ID and host UUID are known to the caller, otherwise
|
||||
// use SetOrUpdateHostEmailsFromMdmIdpAccountsByHostUUID.
|
||||
SetOrUpdateEmailsFromMDMIdPAccountsByHostID(ctx context.Context, hostID uint, hostUUID string) error
|
||||
// SetOrUpdateHostEmailsFromMdmIdpAccounts sets or updates the host emails associated with the provided
|
||||
// host based on the MDM IdP account information associated with the provided fleet enrollment reference.
|
||||
SetOrUpdateHostEmailsFromMdmIdpAccounts(ctx context.Context, hostID uint, fleetEnrollmentRef string) error
|
||||
SetOrUpdateHostDisksSpace(ctx context.Context, hostID uint, gigsAvailable, percentAvailable, gigsTotal float64) error
|
||||
SetOrUpdateHostDisksEncryption(ctx context.Context, hostID uint, encrypted bool) error
|
||||
// SetOrUpdateHostDiskEncryptionKey sets the base64, encrypted key for
|
||||
@@ -1187,21 +1170,8 @@ type Datastore interface {
|
||||
// InsertMDMIdPAccount inserts a new MDM IdP account
|
||||
InsertMDMIdPAccount(ctx context.Context, account *MDMIdPAccount) error
|
||||
|
||||
// AssociateMDMIdPAccount adds device info to an existing MDM IdP account
|
||||
AssociateMDMIdPAccount(ctx context.Context, accountUUID string, deviceUUID string) error
|
||||
|
||||
// GetMDMIdPAccountByAccountUUID returns MDM IdP account that matches the given account uuid.
|
||||
GetMDMIdPAccountByAccountUUID(ctx context.Context, accountUUID string) (*MDMIdPAccount, error)
|
||||
|
||||
// GetMDMIdPAccountByHostUUID returns MDM IdP account that matches the given host uuid.
|
||||
GetMDMIdPAccountByHostUUID(ctx context.Context, hostUUID string) (*MDMIdPAccount, error)
|
||||
|
||||
// GetMDMIdPAccountByLegacyEnrollRef returns MDM IdP account that matches the given Fleet
|
||||
// enrollment ref.
|
||||
//
|
||||
// Deprecated: This method is deprecated and only used for backwards compatibility.
|
||||
// GetMDMIdPAccountByAccountUUID and GetMDMIdPAccountByDeviceUUID are the preferred methods.
|
||||
GetMDMIdPAccountByLegacyEnrollRef(ctx context.Context, ref string) (*MDMIdPAccount, error)
|
||||
// GetMDMIdPAccountByUUID returns MDM IdP account that matches the given token.
|
||||
GetMDMIdPAccountByUUID(ctx context.Context, uuid string) (*MDMIdPAccount, error)
|
||||
|
||||
// GetMDMIdPAccountByEmail returns MDM IdP account that matches the given email.
|
||||
GetMDMIdPAccountByEmail(ctx context.Context, email string) (*MDMIdPAccount, error)
|
||||
|
||||
@@ -66,22 +66,10 @@ type AppConfigUpdater interface {
|
||||
// MDMIdPAccount contains account information of a third-party IdP that can be
|
||||
// later used for MDM operations like creating local accounts.
|
||||
type MDMIdPAccount struct {
|
||||
// UUID is the unique identifier created when a new user email is ingested (e.g., from the IdP response
|
||||
// payload during the DEP automatic enrollment flow). It is used to subsequently associate the
|
||||
// IdP account info to the device UUID extracted from the DEP webview client request.
|
||||
UUID string
|
||||
Username string
|
||||
Fullname string
|
||||
Email string
|
||||
// HostUUID is the unique device identifier associated with the MDM enrollment. For Apple
|
||||
// devices, it corresponds to the UDID extracted from the `x-apple-aspen-deviceinfo` header of
|
||||
// the DEP webview client request.
|
||||
HostUUID string `db:"host_uuid"`
|
||||
// FleetEnrollRef is a legacy reference that is preserved for devices that enrolled
|
||||
// via a mobileconfig that included an enrollment reference query param in the service URL. It
|
||||
// is preserved for backwards compatibility with existing enrollments because Apple requires
|
||||
// server URLs to match exactly when re-enrolling (e.g., via `profiles renew -type enrollment`).
|
||||
FleetEnrollRef string `db:"fleet_enroll_ref"`
|
||||
}
|
||||
|
||||
type MDMAppleBootstrapPackage struct {
|
||||
|
||||
@@ -749,7 +749,7 @@ type Service interface {
|
||||
GetMDMAppleProfilesSummary(ctx context.Context, teamID *uint) (*MDMProfilesSummary, error)
|
||||
|
||||
// GetMDMAppleEnrollmentProfileByToken returns the Apple enrollment from its secret token.
|
||||
GetMDMAppleEnrollmentProfileByToken(ctx context.Context, enrollmentToken string, enrollmentRef string, deviceinfo string) (profile []byte, err error)
|
||||
GetMDMAppleEnrollmentProfileByToken(ctx context.Context, enrollmentToken string, enrollmentRef string) (profile []byte, err error)
|
||||
|
||||
// GetDeviceMDMAppleEnrollmentProfile loads the raw (PList-format) enrollment
|
||||
// profile for the currently authenticated device.
|
||||
|
||||
Binary file not shown.
@@ -1,173 +0,0 @@
|
||||
// The contents of this file have been copied and modified pursuant to the following
|
||||
// license from the original source:
|
||||
// https://github.com/korylprince/dep-webview-oidc/blob/2dd846a54fed04c16dd227b8c6c31665b4d0ebd8/header/header.go
|
||||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2023 Kory Prince
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package apple_mdm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1" // nolint:gosec // See comments regarding Apple's Root CA below
|
||||
"crypto/x509"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/groob/plist"
|
||||
"go.mozilla.org/pkcs7"
|
||||
)
|
||||
|
||||
const DeviceInfoHeader = "x-apple-aspen-deviceinfo"
|
||||
|
||||
// appleRootCert is https://www.apple.com/appleca/AppleIncRootCertificate.cer
|
||||
//
|
||||
//go:embed AppleIncRootCertificate.cer
|
||||
var appleRootCert []byte
|
||||
|
||||
func newAppleRootCert() *x509.Certificate {
|
||||
cert, err := x509.ParseCertificate(appleRootCert)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not parse cert: %w", err))
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
// appleRootCA is Apple's Root CA parsed to an *x509.Certificate
|
||||
var appleRootCA = newAppleRootCert()
|
||||
|
||||
// MachineInfo is a [device's information] sent as part of an MDM enrollment profile request
|
||||
//
|
||||
// [device's information]: https://developer.apple.com/documentation/devicemanagement/machineinfo
|
||||
type MachineInfo struct {
|
||||
IMEI string `plist:"IMEI,omitempty"`
|
||||
Language string `plist:"LANGUAGE,omitempty"`
|
||||
MEID string `plist:"MEID,omitempty"`
|
||||
Product string `plist:"PRODUCT"`
|
||||
Serial string `plist:"SERIAL"`
|
||||
UDID string `plist:"UDID"`
|
||||
Version string `plist:"VERSION"`
|
||||
}
|
||||
|
||||
// verifyPKCS7SHA1RSA performs a manual SHA1withRSA verification, since it's deprecated in Go 1.18.
|
||||
// If verifyChain is true, the signer certificate and its chain of certificates is verified against Apple's Root CA.
|
||||
// Also note that the certificate validity time window of the signing cert is not checked, since the cert is expired.
|
||||
// This follows guidance from Apple on the expired certificate.
|
||||
func verifyPKCS7SHA1RSA(p7 *pkcs7.PKCS7, verifyChain bool) error {
|
||||
if len(p7.Signers) == 0 {
|
||||
return errors.New("not signed")
|
||||
}
|
||||
|
||||
// get signing cert
|
||||
issuer := p7.Signers[0].IssuerAndSerialNumber
|
||||
var signer *x509.Certificate
|
||||
for _, cert := range p7.Certificates {
|
||||
if bytes.Equal(cert.RawIssuer, issuer.IssuerName.FullBytes) && cert.SerialNumber.Cmp(issuer.SerialNumber) == 0 {
|
||||
signer = cert
|
||||
}
|
||||
}
|
||||
|
||||
// get sha1 hash of content
|
||||
hashed := sha1.Sum(p7.Content) // nolint:gosec
|
||||
|
||||
// verify content signature
|
||||
signature := p7.Signers[0].EncryptedDigest
|
||||
if err := rsa.VerifyPKCS1v15(signer.PublicKey.(*rsa.PublicKey), crypto.SHA1, hashed[:], signature); err != nil {
|
||||
return fmt.Errorf("signature could not be verified: %w", err)
|
||||
}
|
||||
|
||||
if !verifyChain {
|
||||
return nil
|
||||
}
|
||||
|
||||
// verify chain from signer to root
|
||||
cert := signer
|
||||
outer:
|
||||
for {
|
||||
// check if cert is signed by root
|
||||
if bytes.Equal(cert.RawIssuer, appleRootCA.RawSubject) {
|
||||
hashed := sha1.Sum(cert.RawTBSCertificate) // nolint:gosec
|
||||
// check signature
|
||||
if err := rsa.VerifyPKCS1v15(appleRootCA.PublicKey.(*rsa.PublicKey), crypto.SHA1, hashed[:], cert.Signature); err != nil {
|
||||
return fmt.Errorf("could not verify root CA signature: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, c := range p7.Certificates {
|
||||
if cert == c {
|
||||
continue
|
||||
}
|
||||
// check if cert is signed by intermediate cert in chain
|
||||
if bytes.Equal(cert.RawIssuer, c.RawSubject) {
|
||||
// check signature
|
||||
hashed := sha1.Sum(cert.RawTBSCertificate) // nolint:gosec
|
||||
if err := rsa.VerifyPKCS1v15(c.PublicKey.(*rsa.PublicKey), crypto.SHA1, hashed[:], cert.Signature); err != nil {
|
||||
return fmt.Errorf("could not verify chained certificate signature: %w", err)
|
||||
}
|
||||
cert = c
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
return errors.New("certificate root not found")
|
||||
}
|
||||
}
|
||||
|
||||
// ParseDeviceinfo attempts to parse the provided string, assuming it to be the base64-encoded value
|
||||
// of an x-apple-aspen-deviceinfo header. If successful, it returns the parsed *MachineInfo. If the
|
||||
// verify parameter is specified as true, the signature is also verified against Apple's Root CA and
|
||||
// an error will be returned if the signature is invalid.
|
||||
//
|
||||
// Warning: The information in this header, despite being signed by Apple PKI, shouldn't be trusted
|
||||
// for device attestation or other security purposes. See the related [documentation] and referenced
|
||||
// [article] for more information.
|
||||
//
|
||||
// [documentation]: https://github.com/korylprince/dep-webview-oidc/blob/2dd846a54fed04c16dd227b8c6c31665b4d0ebd8/docs/Architecture.md#x-apple-aspen-deviceinfo-header
|
||||
// [article]: https://duo.com/labs/research/mdm-me-maybe
|
||||
func ParseDeviceinfo(b64 string, verify bool) (*MachineInfo, error) {
|
||||
buf, err := base64.StdEncoding.DecodeString(b64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decode base64: %w", err)
|
||||
}
|
||||
|
||||
p7, err := pkcs7.Parse(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decode pkcs7: %w", err)
|
||||
}
|
||||
|
||||
// verify signature and certificate chain
|
||||
if verify {
|
||||
if err = verifyPKCS7SHA1RSA(p7, verify); err != nil {
|
||||
return nil, fmt.Errorf("could not verify signature: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
info := new(MachineInfo)
|
||||
if err = plist.Unmarshal(p7.Content, info); err != nil {
|
||||
return nil, fmt.Errorf("could not decode plist: %w", err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
@@ -601,11 +601,7 @@ type SetOrUpdateMunkiInfoFunc func(ctx context.Context, hostID uint, version str
|
||||
|
||||
type SetOrUpdateMDMDataFunc func(ctx context.Context, hostID uint, isServer bool, enrolled bool, serverURL string, installedFromDep bool, name string, fleetEnrollRef string) error
|
||||
|
||||
type SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFunc func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error
|
||||
|
||||
type SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUIDFunc func(ctx context.Context, hostUUID string) error
|
||||
|
||||
type SetOrUpdateEmailsFromMDMIdPAccountsByHostIDFunc func(ctx context.Context, hostID uint, hostUUID string) error
|
||||
type SetOrUpdateHostEmailsFromMdmIdpAccountsFunc func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error
|
||||
|
||||
type SetOrUpdateHostDisksSpaceFunc func(ctx context.Context, hostID uint, gigsAvailable float64, percentAvailable float64, gigsTotal float64) error
|
||||
|
||||
@@ -781,13 +777,7 @@ type GetMDMAppleProfilesSummaryFunc func(ctx context.Context, teamID *uint) (*fl
|
||||
|
||||
type InsertMDMIdPAccountFunc func(ctx context.Context, account *fleet.MDMIdPAccount) error
|
||||
|
||||
type AssociateMDMIdPAccountFunc func(ctx context.Context, accountUUID string, deviceUUID string) error
|
||||
|
||||
type GetMDMIdPAccountByAccountUUIDFunc func(ctx context.Context, accountUUID string) (*fleet.MDMIdPAccount, error)
|
||||
|
||||
type GetMDMIdPAccountByHostUUIDFunc func(ctx context.Context, hostUUID string) (*fleet.MDMIdPAccount, error)
|
||||
|
||||
type GetMDMIdPAccountByLegacyEnrollRefFunc func(ctx context.Context, ref string) (*fleet.MDMIdPAccount, error)
|
||||
type GetMDMIdPAccountByUUIDFunc func(ctx context.Context, uuid string) (*fleet.MDMIdPAccount, error)
|
||||
|
||||
type GetMDMIdPAccountByEmailFunc func(ctx context.Context, email string) (*fleet.MDMIdPAccount, error)
|
||||
|
||||
@@ -1871,14 +1861,8 @@ type DataStore struct {
|
||||
SetOrUpdateMDMDataFunc SetOrUpdateMDMDataFunc
|
||||
SetOrUpdateMDMDataFuncInvoked bool
|
||||
|
||||
SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFunc SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFunc
|
||||
SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFuncInvoked bool
|
||||
|
||||
SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUIDFunc SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUIDFunc
|
||||
SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUIDFuncInvoked bool
|
||||
|
||||
SetOrUpdateEmailsFromMDMIdPAccountsByHostIDFunc SetOrUpdateEmailsFromMDMIdPAccountsByHostIDFunc
|
||||
SetOrUpdateEmailsFromMDMIdPAccountsByHostIDFuncInvoked bool
|
||||
SetOrUpdateHostEmailsFromMdmIdpAccountsFunc SetOrUpdateHostEmailsFromMdmIdpAccountsFunc
|
||||
SetOrUpdateHostEmailsFromMdmIdpAccountsFuncInvoked bool
|
||||
|
||||
SetOrUpdateHostDisksSpaceFunc SetOrUpdateHostDisksSpaceFunc
|
||||
SetOrUpdateHostDisksSpaceFuncInvoked bool
|
||||
@@ -2141,17 +2125,8 @@ type DataStore struct {
|
||||
InsertMDMIdPAccountFunc InsertMDMIdPAccountFunc
|
||||
InsertMDMIdPAccountFuncInvoked bool
|
||||
|
||||
AssociateMDMIdPAccountFunc AssociateMDMIdPAccountFunc
|
||||
AssociateMDMIdPAccountFuncInvoked bool
|
||||
|
||||
GetMDMIdPAccountByAccountUUIDFunc GetMDMIdPAccountByAccountUUIDFunc
|
||||
GetMDMIdPAccountByAccountUUIDFuncInvoked bool
|
||||
|
||||
GetMDMIdPAccountByHostUUIDFunc GetMDMIdPAccountByHostUUIDFunc
|
||||
GetMDMIdPAccountByHostUUIDFuncInvoked bool
|
||||
|
||||
GetMDMIdPAccountByLegacyEnrollRefFunc GetMDMIdPAccountByLegacyEnrollRefFunc
|
||||
GetMDMIdPAccountByLegacyEnrollRefFuncInvoked bool
|
||||
GetMDMIdPAccountByUUIDFunc GetMDMIdPAccountByUUIDFunc
|
||||
GetMDMIdPAccountByUUIDFuncInvoked bool
|
||||
|
||||
GetMDMIdPAccountByEmailFunc GetMDMIdPAccountByEmailFunc
|
||||
GetMDMIdPAccountByEmailFuncInvoked bool
|
||||
@@ -4505,25 +4480,11 @@ func (s *DataStore) SetOrUpdateMDMData(ctx context.Context, hostID uint, isServe
|
||||
return s.SetOrUpdateMDMDataFunc(ctx, hostID, isServer, enrolled, serverURL, installedFromDep, name, fleetEnrollRef)
|
||||
}
|
||||
|
||||
func (s *DataStore) SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRef(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
func (s *DataStore) SetOrUpdateHostEmailsFromMdmIdpAccounts(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
s.mu.Lock()
|
||||
s.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFuncInvoked = true
|
||||
s.SetOrUpdateHostEmailsFromMdmIdpAccountsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFunc(ctx, hostID, fleetEnrollmentRef)
|
||||
}
|
||||
|
||||
func (s *DataStore) SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUID(ctx context.Context, hostUUID string) error {
|
||||
s.mu.Lock()
|
||||
s.SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUIDFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUIDFunc(ctx, hostUUID)
|
||||
}
|
||||
|
||||
func (s *DataStore) SetOrUpdateEmailsFromMDMIdPAccountsByHostID(ctx context.Context, hostID uint, hostUUID string) error {
|
||||
s.mu.Lock()
|
||||
s.SetOrUpdateEmailsFromMDMIdPAccountsByHostIDFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.SetOrUpdateEmailsFromMDMIdPAccountsByHostIDFunc(ctx, hostID, hostUUID)
|
||||
return s.SetOrUpdateHostEmailsFromMdmIdpAccountsFunc(ctx, hostID, fleetEnrollmentRef)
|
||||
}
|
||||
|
||||
func (s *DataStore) SetOrUpdateHostDisksSpace(ctx context.Context, hostID uint, gigsAvailable float64, percentAvailable float64, gigsTotal float64) error {
|
||||
@@ -5135,32 +5096,11 @@ func (s *DataStore) InsertMDMIdPAccount(ctx context.Context, account *fleet.MDMI
|
||||
return s.InsertMDMIdPAccountFunc(ctx, account)
|
||||
}
|
||||
|
||||
func (s *DataStore) AssociateMDMIdPAccount(ctx context.Context, accountUUID string, deviceUUID string) error {
|
||||
func (s *DataStore) GetMDMIdPAccountByUUID(ctx context.Context, uuid string) (*fleet.MDMIdPAccount, error) {
|
||||
s.mu.Lock()
|
||||
s.AssociateMDMIdPAccountFuncInvoked = true
|
||||
s.GetMDMIdPAccountByUUIDFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.AssociateMDMIdPAccountFunc(ctx, accountUUID, deviceUUID)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetMDMIdPAccountByAccountUUID(ctx context.Context, accountUUID string) (*fleet.MDMIdPAccount, error) {
|
||||
s.mu.Lock()
|
||||
s.GetMDMIdPAccountByAccountUUIDFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetMDMIdPAccountByAccountUUIDFunc(ctx, accountUUID)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetMDMIdPAccountByHostUUID(ctx context.Context, hostUUID string) (*fleet.MDMIdPAccount, error) {
|
||||
s.mu.Lock()
|
||||
s.GetMDMIdPAccountByHostUUIDFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetMDMIdPAccountByHostUUIDFunc(ctx, hostUUID)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetMDMIdPAccountByLegacyEnrollRef(ctx context.Context, ref string) (*fleet.MDMIdPAccount, error) {
|
||||
s.mu.Lock()
|
||||
s.GetMDMIdPAccountByLegacyEnrollRefFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetMDMIdPAccountByLegacyEnrollRefFunc(ctx, ref)
|
||||
return s.GetMDMIdPAccountByUUIDFunc(ctx, uuid)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetMDMIdPAccountByEmail(ctx context.Context, email string) (*fleet.MDMIdPAccount, error) {
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/server"
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxdb"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/logging"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
@@ -1286,17 +1285,8 @@ func (svc *Service) EnqueueMDMAppleCommand(
|
||||
}
|
||||
|
||||
type mdmAppleEnrollRequest struct {
|
||||
// Token is expected to be a UUID string that identifies a template MDM Apple enrollment profile.
|
||||
Token string `query:"token"`
|
||||
// EnrollmentReference is expected to be a UUID string that identifies the MDM IdP account used
|
||||
// to authenticate the end user as part of the MDM IdP flow.
|
||||
Token string `query:"token"`
|
||||
EnrollmentReference string `query:"enrollment_reference,optional"`
|
||||
// DEPDeviceInfo is expected to be a base64 encoded string containing DEP deviceinfo extracted
|
||||
// from the x-apple-aspen-deviceinfo header of the original configuration web view request and
|
||||
// persisted by the client in local storage for inclusion in a subsequent enrollment request as
|
||||
// part of the MDM IdP flow.
|
||||
// See https://developer.apple.com/documentation/devicemanagement/device_assignment/authenticating_through_web_views
|
||||
DEPDeviceInfo string `query:"dep_device_info,optional"`
|
||||
}
|
||||
|
||||
func (r mdmAppleEnrollResponse) error() error { return r.Err }
|
||||
@@ -1326,7 +1316,7 @@ func (r mdmAppleEnrollResponse) hijackRender(ctx context.Context, w http.Respons
|
||||
func mdmAppleEnrollEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||
req := request.(*mdmAppleEnrollRequest)
|
||||
|
||||
profile, err := svc.GetMDMAppleEnrollmentProfileByToken(ctx, req.Token, req.EnrollmentReference, req.DEPDeviceInfo)
|
||||
profile, err := svc.GetMDMAppleEnrollmentProfileByToken(ctx, req.Token, req.EnrollmentReference)
|
||||
if err != nil {
|
||||
return mdmAppleEnrollResponse{Err: err}, nil
|
||||
}
|
||||
@@ -1335,7 +1325,7 @@ func mdmAppleEnrollEndpoint(ctx context.Context, request interface{}, svc fleet.
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetMDMAppleEnrollmentProfileByToken(ctx context.Context, token string, ref string, deviceinfo string) (profile []byte, err error) {
|
||||
func (svc *Service) GetMDMAppleEnrollmentProfileByToken(ctx context.Context, token string, ref string) (profile []byte, err error) {
|
||||
// skipauth: The enroll profile endpoint is unauthenticated.
|
||||
svc.authz.SkipAuthorization(ctx)
|
||||
|
||||
@@ -1352,44 +1342,9 @@ func (svc *Service) GetMDMAppleEnrollmentProfileByToken(ctx context.Context, tok
|
||||
return nil, ctxerr.Wrap(ctx, err)
|
||||
}
|
||||
|
||||
enrollURL := appConfig.ServerSettings.ServerURL
|
||||
// check if there is a legacy enroll ref and preserve it for backwards compatibility
|
||||
if ref != "" {
|
||||
idpAcct, err := svc.ds.GetMDMIdPAccountByAccountUUID(
|
||||
// use the primary db as the account might have been just inserted
|
||||
ctxdb.RequirePrimary(ctx, true),
|
||||
ref,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "getting MDM IdP account")
|
||||
}
|
||||
if idpAcct.FleetEnrollRef != "" {
|
||||
level.Debug(svc.logger).Log("msg", "using legacy enroll ref", "enroll_ref", idpAcct.FleetEnrollRef)
|
||||
// we have a legacy enroll ref, add it to the enroll URL
|
||||
enrollURL, err = apple_mdm.AddEnrollmentRefToFleetURL(appConfig.ServerSettings.ServerURL, idpAcct.FleetEnrollRef)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "adding reference to fleet URL")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have device info, we need to parse it and associate the device with an MDM IdP account
|
||||
if deviceinfo != "" {
|
||||
di, err := apple_mdm.ParseDeviceinfo(deviceinfo, true)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "parsing deviceinfo")
|
||||
}
|
||||
|
||||
// try to associate the device with an MDM IdP account
|
||||
if ref == "" {
|
||||
// this is unexpected, there should be a reference if we have device info
|
||||
level.Debug(svc.logger).Log("msg", "associating mdm idp account, missing idp account uuid", "device_uuid", di.UDID)
|
||||
} else {
|
||||
// associate the device with the DEP account
|
||||
if err := svc.ds.AssociateMDMIdPAccount(ctx, ref, di.UDID); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "associating MDM IdP account")
|
||||
}
|
||||
}
|
||||
enrollURL, err := apple_mdm.AddEnrollmentRefToFleetURL(appConfig.ServerSettings.ServerURL, ref)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "adding reference to fleet URL")
|
||||
}
|
||||
|
||||
topic, err := svc.mdmPushCertTopic(ctx)
|
||||
|
||||
@@ -274,7 +274,7 @@ func TestAppleMDMAuthorization(t *testing.T) {
|
||||
ctx = test.UserContext(ctx, test.UserNoRoles)
|
||||
_, err := svc.GetMDMAppleInstallerByToken(ctx, "foo")
|
||||
require.NoError(t, err)
|
||||
_, err = svc.GetMDMAppleEnrollmentProfileByToken(ctx, "foo", "", "")
|
||||
_, err = svc.GetMDMAppleEnrollmentProfileByToken(ctx, "foo", "")
|
||||
require.NoError(t, err)
|
||||
_, err = svc.GetMDMAppleInstallerDetailsByToken(ctx, "foo")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
@@ -1038,44 +1037,6 @@ func RedirectSetupToLogin(svc fleet.Service, logger kitlog.Logger, next http.Han
|
||||
}
|
||||
}
|
||||
|
||||
func WithDEPWebviewRedirect(svc fleet.Service, logger kitlog.Logger, next http.Handler, urlPrefix string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/mdm/sso" && r.URL.RawQuery == "" {
|
||||
level.Debug(logger).Log("msg", "handling mdm sso", "url", r.URL.String())
|
||||
// Note: We'll apply this redirect only if query params are empty because want to
|
||||
// redirect to the same URL with added query params after parsing the x-apple-aspen-deviceinfo
|
||||
// header. Whenever we see a request with any query params already present, we'll
|
||||
// skip this step and just continue to the next handler.
|
||||
di := r.Header.Get("X-apple-aspen-deviceinfo")
|
||||
if di != "" {
|
||||
level.Debug(logger).Log("msg", "parsing X-apple-aspen-deviceinfo", "url", r.URL.String())
|
||||
// extract x-apple-aspen-deviceinfo custom header from request
|
||||
_, err := apple_mdm.ParseDeviceinfo(di, true)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "parsing X-apple-aspen-deviceinfo", "err", err)
|
||||
http.Redirect(w, r, r.URL.String()+"?error=true", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
// redirect to the same URL with added deviceinfo query params
|
||||
newURL := r.URL
|
||||
q := url.Values{
|
||||
"dep_device_info": []string{di},
|
||||
}
|
||||
newURL.RawQuery = q.Encode()
|
||||
level.Debug(logger).Log("msg", "adding query params to redirect url", "query", newURL.RawQuery)
|
||||
http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
// TODO: consider whether we want always return an error here if the header is missing for this endpoint?
|
||||
level.Info(logger).Log("msg", "missig x-apple-aspen-deviceinfo header, continuing to next")
|
||||
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterAppleMDMProtocolServices registers the HTTP handlers that serve
|
||||
// the MDM services to Apple devices.
|
||||
func RegisterAppleMDMProtocolServices(
|
||||
|
||||
@@ -327,6 +327,7 @@ func (s *integrationMDMTestSuite) SetupSuite() {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
require.NoError(s.T(), json.NewEncoder(w).Encode(s.mockedDownloadFleetdmMeta))
|
||||
|
||||
}
|
||||
}))
|
||||
s.T().Setenv("FLEET_DEV_DOWNLOAD_FLEETDM_URL", downloadFleetdmSrv.URL)
|
||||
@@ -4593,7 +4594,7 @@ func (s *integrationMDMTestSuite) TestSSO() {
|
||||
require.Equal(t, lastSubmittedProfile.ConfigurationWebURL, lastSubmittedProfile.URL)
|
||||
|
||||
checkStoredIdPInfo := func(uuid, username, fullname, email string) {
|
||||
acc, err := s.ds.GetMDMIdPAccountByAccountUUID(context.Background(), uuid)
|
||||
acc, err := s.ds.GetMDMIdPAccountByUUID(context.Background(), uuid)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, username, acc.Username)
|
||||
require.Equal(t, fullname, acc.Fullname)
|
||||
@@ -4722,11 +4723,6 @@ func (s *integrationMDMTestSuite) TestSSO() {
|
||||
// test runs.
|
||||
mdmDevice.EnrollInfo.MDMURL = strings.Replace(enrollURL, "https://localhost:8080", s.server.URL, 1)
|
||||
mdmDevice.EnrollInfo.SCEPURL = strings.Replace(scepURL, "https://localhost:8080", s.server.URL, 1)
|
||||
|
||||
// associate the mdm idp account with the host uuid manually (we can't mock the Apple webview client
|
||||
// deviceinfo header because it is signed with the Apple Root CA)
|
||||
require.NoError(t, s.ds.AssociateMDMIdPAccount(context.Background(), user1EnrollRef, mdmDevice.UUID))
|
||||
|
||||
err = mdmDevice.Enroll()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -4754,22 +4750,13 @@ func (s *integrationMDMTestSuite) TestSSO() {
|
||||
require.Equal(t, "SSO User 1", fullAccCmd.Command.AccountConfiguration.PrimaryAccountFullName)
|
||||
require.Equal(t, "sso_user", fullAccCmd.Command.AccountConfiguration.PrimaryAccountUserName)
|
||||
|
||||
// check that the host was created and get the host id and other details for the next steps
|
||||
// report host details for the device
|
||||
var hostResp getHostResponse
|
||||
s.DoJSON("GET", "/api/v1/fleet/hosts/identifier/"+mdmDevice.UUID, nil, http.StatusOK, &hostResp)
|
||||
|
||||
// ensure that host_emails entry was created from mdm_idp_accounts
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
var email string
|
||||
err := sqlx.GetContext(context.Background(), q, &email, `SELECT email FROM host_emails WHERE host_id = ? AND source = ?`, hostResp.Host.ID, fleet.DeviceMappingMDMIdpAccounts)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "sso_user@example.com", email)
|
||||
return nil
|
||||
})
|
||||
|
||||
// get app config and host detail queries
|
||||
ac, err := s.ds.AppConfig(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
detailQueries := osquery_utils.GetDetailQueries(context.Background(), config.FleetConfig{}, ac, &ac.Features)
|
||||
|
||||
// simulate osquery reporting mdm information
|
||||
@@ -8306,6 +8293,7 @@ func (s *integrationMDMTestSuite) TestLockUnlockWipeMacOS() {
|
||||
|
||||
// lock the host without viewing the PIN
|
||||
s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", host.ID), nil, http.StatusNoContent)
|
||||
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestZCustomConfigurationWebURL() {
|
||||
|
||||
@@ -1712,18 +1712,11 @@ func directIngestMDMMac(ctx context.Context, logger log.Logger, host *fleet.Host
|
||||
fleetEnrollRef = serverURL.Query().Get("enrollment_reference")
|
||||
}
|
||||
if fleetEnrollRef != "" {
|
||||
if err := ds.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRef(ctx, host.ID, fleetEnrollRef); err != nil {
|
||||
level.Warn(logger).Log(
|
||||
"component", "service",
|
||||
"method", "directIngestMDMMac",
|
||||
"msg", err.Error(),
|
||||
)
|
||||
}
|
||||
} else if installedFromDep {
|
||||
// ensure that mdm_idp_accounts are included in host_emails for Apple DEP-enrolled hosts;
|
||||
// note that we're relying on the osquery platform compatibility checks to ensure that this
|
||||
// ingest function is only applied darwin hosts
|
||||
if err := ds.SetOrUpdateEmailsFromMDMIdPAccountsByHostID(ctx, host.ID, host.UUID); err != nil {
|
||||
if err := ds.SetOrUpdateHostEmailsFromMdmIdpAccounts(ctx, host.ID, fleetEnrollRef); err != nil {
|
||||
if !fleet.IsNotFound(err) {
|
||||
return ctxerr.Wrap(ctx, err, "updating host emails from mdm idp accounts")
|
||||
}
|
||||
|
||||
level.Warn(logger).Log(
|
||||
"component", "service",
|
||||
"method", "directIngestMDMMac",
|
||||
|
||||
@@ -596,15 +596,12 @@ func TestDirectIngestMDMMac(t *testing.T) {
|
||||
require.Equal(t, fleetEnrollmentRef, c.enrollRef)
|
||||
return nil
|
||||
}
|
||||
ds.SetOrUpdateEmailsFromMDMIdPAccountsByHostIDFunc = func(ctx context.Context, hostID uint, hostUUID string) error {
|
||||
return nil
|
||||
}
|
||||
ds.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFunc = func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
ds.SetOrUpdateHostEmailsFromMdmIdpAccountsFunc = func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.name == "with invalid enrollment reference" {
|
||||
ds.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFunc = func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
ds.SetOrUpdateHostEmailsFromMdmIdpAccountsFunc = func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
return &nfe{}
|
||||
}
|
||||
}
|
||||
@@ -619,7 +616,7 @@ func TestDirectIngestMDMMac(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
ds.SetOrUpdateMDMDataFuncInvoked = false
|
||||
if c.name != "with invalid enrollment reference" {
|
||||
require.False(t, ds.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFuncInvoked)
|
||||
require.False(t, ds.SetOrUpdateHostEmailsFromMdmIdpAccountsFuncInvoked)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -687,13 +684,10 @@ func TestDirectIngestMDMFleetEnrollRef(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ds.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFunc = func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
ds.SetOrUpdateHostEmailsFromMdmIdpAccountsFunc = func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
require.Equal(t, tc.wantEnrollRef, fleetEnrollmentRef)
|
||||
return nil
|
||||
}
|
||||
ds.SetOrUpdateEmailsFromMDMIdPAccountsByHostIDFunc = func(ctx context.Context, hostID uint, hostUUID string) error {
|
||||
return nil
|
||||
}
|
||||
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, isServer, enrolled bool, serverURL string, installedFromDep bool, name string, fleetEnrollmentRef string) error {
|
||||
require.False(t, isServer)
|
||||
require.True(t, enrolled)
|
||||
@@ -717,8 +711,8 @@ func TestDirectIngestMDMFleetEnrollRef(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.SetOrUpdateMDMDataFuncInvoked)
|
||||
ds.SetOrUpdateMDMDataFuncInvoked = false
|
||||
require.Equal(t, tc.wantHostEmailsCalled, ds.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFuncInvoked)
|
||||
ds.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFuncInvoked = false
|
||||
require.Equal(t, tc.wantHostEmailsCalled, ds.SetOrUpdateHostEmailsFromMdmIdpAccountsFuncInvoked)
|
||||
ds.SetOrUpdateHostEmailsFromMdmIdpAccountsFuncInvoked = false
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -942,10 +936,7 @@ func TestDirectIngestMDMWindows(t *testing.T) {
|
||||
require.Empty(t, fleetEnrollmentRef)
|
||||
return nil
|
||||
}
|
||||
ds.SetOrUpdateEmailsFromMDMIdPAccountsByHostIDFunc = func(ctx context.Context, hostID uint, hostUUID string) error {
|
||||
return nil
|
||||
}
|
||||
ds.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFunc = func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
ds.SetOrUpdateHostEmailsFromMdmIdpAccountsFunc = func(ctx context.Context, hostID uint, fleetEnrollmentRef string) error {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
@@ -953,7 +944,7 @@ func TestDirectIngestMDMWindows(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.SetOrUpdateMDMDataFuncInvoked)
|
||||
ds.SetOrUpdateMDMDataFuncInvoked = false
|
||||
require.False(t, ds.SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRefFuncInvoked)
|
||||
require.False(t, ds.SetOrUpdateHostEmailsFromMdmIdpAccountsFuncInvoked)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/pkg/fleetdbase"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxdb"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||||
@@ -107,9 +106,6 @@ func (a *AppleMDM) runPostManualEnrollment(ctx context.Context, args appleMDMArg
|
||||
func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs) error {
|
||||
var awaitCmdUUIDs []string
|
||||
|
||||
// use primary db to ensure we have the latest records for enrollments
|
||||
ctx = ctxdb.RequirePrimary(ctx, true)
|
||||
|
||||
if isMacOS(args.Platform) {
|
||||
fleetdCmdUUID, err := a.installFleetd(ctx, args.HostUUID)
|
||||
if err != nil {
|
||||
@@ -126,24 +122,18 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs)
|
||||
}
|
||||
}
|
||||
|
||||
idpAcct, err := a.Datastore.GetMDMIdPAccountByHostUUID(ctx, args.HostUUID)
|
||||
if err != nil && !fleet.IsNotFound(err) {
|
||||
return ctxerr.Wrap(ctx, err, "getting idp account details")
|
||||
}
|
||||
|
||||
if ref := args.EnrollReference; ref != "" {
|
||||
a.Log.Log("info", "got an enroll_reference", "host_uuid", args.HostUUID, "ref", ref)
|
||||
idpAcct, err = a.Datastore.GetMDMIdPAccountByLegacyEnrollRef(ctx, ref)
|
||||
if err != nil {
|
||||
return ctxerr.Wrapf(ctx, err, "getting idp account details for enroll reference %s", ref)
|
||||
}
|
||||
}
|
||||
|
||||
if idpAcct != nil {
|
||||
appCfg, err := a.Datastore.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "getting app config")
|
||||
}
|
||||
|
||||
acct, err := a.Datastore.GetMDMIdPAccountByUUID(ctx, ref)
|
||||
if err != nil {
|
||||
return ctxerr.Wrapf(ctx, err, "getting idp account details for enroll reference %s", ref)
|
||||
}
|
||||
|
||||
ssoEnabled := appCfg.MDM.MacOSSetup.EnableEndUserAuthentication
|
||||
if args.TeamID != nil {
|
||||
team, err := a.Datastore.Team(ctx, *args.TeamID)
|
||||
@@ -160,19 +150,12 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs)
|
||||
ctx,
|
||||
[]string{args.HostUUID},
|
||||
cmdUUID,
|
||||
idpAcct.Fullname,
|
||||
idpAcct.Username,
|
||||
acct.Fullname,
|
||||
acct.Username,
|
||||
); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "sending AccountConfiguration command")
|
||||
}
|
||||
awaitCmdUUIDs = append(awaitCmdUUIDs, cmdUUID)
|
||||
|
||||
// NOTE: We only set the email address here if we have an MDM IdP account and sso is enabled. We rely on the
|
||||
// `resetDarwin` lifecycle event to delete from `host_emails` for this host uuid if there is
|
||||
// any email where `source = 'mdm_idp_account'`.
|
||||
if err := a.Datastore.SetOrUpdateHostEmailsFromMDMIdPAccountsByHostUUID(ctx, args.HostUUID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "setting host emails from mdm idp accounts")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -403,7 +402,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
require.Equal(t, "custom-team-bootstrap", ms.BootstrapPackageName)
|
||||
})
|
||||
|
||||
t.Run("unknown legacy enroll reference", func(t *testing.T) {
|
||||
t.Run("unknown enroll reference", func(t *testing.T) {
|
||||
mysql.SetTestABMAssets(t, ds)
|
||||
defer mysql.TruncateTables(t, ds)
|
||||
|
||||
@@ -431,12 +430,12 @@ func TestAppleMDM(t *testing.T) {
|
||||
jobs, err := ds.GetQueuedJobs(ctx, 1, time.Time{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, jobs, 1)
|
||||
require.Contains(t, jobs[0].Error, "MDMIdPAccount with fleet_enroll_ref abcd was not found")
|
||||
require.Contains(t, jobs[0].Error, "MDMIdPAccount with uuid abcd was not found")
|
||||
require.Equal(t, fleet.JobStateQueued, jobs[0].State)
|
||||
require.Equal(t, 1, jobs[0].Retries)
|
||||
})
|
||||
|
||||
t.Run("legacy reference associated MDM IdP account but SSO disabled", func(t *testing.T) {
|
||||
t.Run("enroll reference but SSO disabled", func(t *testing.T) {
|
||||
mysql.SetTestABMAssets(t, ds)
|
||||
defer mysql.TruncateTables(t, ds)
|
||||
|
||||
@@ -450,15 +449,8 @@ func TestAppleMDM(t *testing.T) {
|
||||
idpAcc, err := ds.GetMDMIdPAccountByEmail(ctx, "test@example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
mysql.ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, `UPDATE mdm_idp_accounts mia SET fleet_enroll_ref = mia.uuid WHERE uuid = ?`, idpAcc.UUID)
|
||||
return err
|
||||
})
|
||||
|
||||
h := createEnrolledHost(t, 1, nil, true)
|
||||
|
||||
require.NoError(t, ds.AssociateMDMIdPAccount(ctx, idpAcc.UUID, h.UUID))
|
||||
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
@@ -489,17 +481,9 @@ func TestAppleMDM(t *testing.T) {
|
||||
|
||||
// confirm that AccountConfiguration command was not enqueued
|
||||
require.ElementsMatch(t, []string{"InstallEnterpriseApplication"}, getEnqueuedCommandTypes(t))
|
||||
|
||||
// ensure that host_emails entry was not created from mdm_idp_accounts
|
||||
mysql.ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
var gotEmail string
|
||||
err := sqlx.GetContext(ctx, q, &gotEmail, `SELECT email FROM host_emails WHERE host_id = ? AND source = ?`, h.ID, fleet.DeviceMappingMDMIdpAccounts)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("legacy reference associated MDM IdP account with SSO enabled", func(t *testing.T) {
|
||||
t.Run("enroll reference with SSO enabled", func(t *testing.T) {
|
||||
mysql.SetTestABMAssets(t, ds)
|
||||
defer mysql.TruncateTables(t, ds)
|
||||
|
||||
@@ -513,11 +497,6 @@ func TestAppleMDM(t *testing.T) {
|
||||
idpAcc, err := ds.GetMDMIdPAccountByEmail(ctx, "test@example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
mysql.ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, `UPDATE mdm_idp_accounts mia SET fleet_enroll_ref = mia.uuid WHERE uuid = ?`, idpAcc.UUID)
|
||||
return err
|
||||
})
|
||||
|
||||
tm, err := ds.NewTeam(ctx, &fleet.Team{Name: "test"})
|
||||
require.NoError(t, err)
|
||||
tm, err = ds.Team(ctx, tm.ID)
|
||||
@@ -528,8 +507,6 @@ func TestAppleMDM(t *testing.T) {
|
||||
|
||||
h := createEnrolledHost(t, 1, &tm.ID, true)
|
||||
|
||||
require.NoError(t, ds.AssociateMDMIdPAccount(ctx, idpAcc.UUID, h.UUID))
|
||||
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
@@ -559,139 +536,6 @@ func TestAppleMDM(t *testing.T) {
|
||||
require.Equal(t, 0, jobs[0].Retries) // hasn't run yet
|
||||
|
||||
require.ElementsMatch(t, []string{"InstallEnterpriseApplication", "AccountConfiguration"}, getEnqueuedCommandTypes(t))
|
||||
|
||||
// ensure that the host_emails entry was created from mdm_idp_accounts
|
||||
mysql.ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
var gotEmail string
|
||||
err := sqlx.GetContext(ctx, q, &gotEmail, `SELECT email FROM host_emails WHERE host_id = ? AND source = ?`, h.ID, fleet.DeviceMappingMDMIdpAccounts)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test@example.com", gotEmail)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("associated MDM IdP account with SSO enabled", func(t *testing.T) {
|
||||
mysql.SetTestABMAssets(t, ds)
|
||||
defer mysql.TruncateTables(t, ds)
|
||||
|
||||
err := ds.InsertMDMIdPAccount(ctx, &fleet.MDMIdPAccount{
|
||||
Username: "test",
|
||||
Fullname: "test",
|
||||
Email: "test@example.com",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
idpAcc, err := ds.GetMDMIdPAccountByEmail(ctx, "test@example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
tm, err := ds.NewTeam(ctx, &fleet.Team{Name: "test"})
|
||||
require.NoError(t, err)
|
||||
tm, err = ds.Team(ctx, tm.ID)
|
||||
require.NoError(t, err)
|
||||
tm.Config.MDM.MacOSSetup.EnableEndUserAuthentication = true
|
||||
_, err = ds.SaveTeam(ctx, tm)
|
||||
require.NoError(t, err)
|
||||
|
||||
h := createEnrolledHost(t, 1, &tm.ID, true)
|
||||
|
||||
require.NoError(t, ds.AssociateMDMIdPAccount(ctx, idpAcc.UUID, h.UUID))
|
||||
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
|
||||
err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", &tm.ID, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// run the worker, should succeed
|
||||
err = w.ProcessJobs(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// ensure the job's not_before allows it to be returned if it were to run
|
||||
// again
|
||||
time.Sleep(time.Second)
|
||||
|
||||
jobs, err := ds.GetQueuedJobs(ctx, 1, time.Now().UTC().Add(time.Minute)) // look in the future to catch any delayed job
|
||||
require.NoError(t, err)
|
||||
|
||||
// the post-DEP release device job is pending
|
||||
require.Len(t, jobs, 1)
|
||||
require.Equal(t, appleMDMJobName, jobs[0].Name)
|
||||
require.Contains(t, string(*jobs[0].Args), AppleMDMPostDEPReleaseDeviceTask)
|
||||
require.Equal(t, 0, jobs[0].Retries) // hasn't run yet
|
||||
|
||||
require.ElementsMatch(t, []string{"InstallEnterpriseApplication", "AccountConfiguration"}, getEnqueuedCommandTypes(t))
|
||||
|
||||
// ensure that the host_emails entry was created from mdm_idp_accounts
|
||||
mysql.ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
var gotEmail string
|
||||
err := sqlx.GetContext(ctx, q, &gotEmail, `SELECT email FROM host_emails WHERE host_id = ? AND source = ?`, h.ID, fleet.DeviceMappingMDMIdpAccounts)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test@example.com", gotEmail)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("associated MDM IdP account but SSO disabled", func(t *testing.T) {
|
||||
mysql.SetTestABMAssets(t, ds)
|
||||
defer mysql.TruncateTables(t, ds)
|
||||
|
||||
err := ds.InsertMDMIdPAccount(ctx, &fleet.MDMIdPAccount{
|
||||
Username: "test",
|
||||
Fullname: "test",
|
||||
Email: "test@example.com",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
idpAcc, err := ds.GetMDMIdPAccountByEmail(ctx, "test@example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
h := createEnrolledHost(t, 1, nil, true)
|
||||
|
||||
require.NoError(t, ds.AssociateMDMIdPAccount(ctx, idpAcc.UUID, h.UUID))
|
||||
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
|
||||
err = QueueAppleMDMJob(ctx, ds, nopLog, AppleMDMPostDEPEnrollmentTask, h.UUID, "darwin", nil, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// run the worker, should succeed
|
||||
err = w.ProcessJobs(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// ensure the job's not_before allows it to be returned if it were to run
|
||||
// again
|
||||
time.Sleep(time.Second)
|
||||
|
||||
jobs, err := ds.GetQueuedJobs(ctx, 1, time.Now().UTC().Add(time.Minute)) // look in the future to catch any delayed job
|
||||
require.NoError(t, err)
|
||||
|
||||
// the post-DEP release device job is pending, having failed its first attempt
|
||||
require.Len(t, jobs, 1)
|
||||
require.Equal(t, appleMDMJobName, jobs[0].Name)
|
||||
require.Contains(t, string(*jobs[0].Args), AppleMDMPostDEPReleaseDeviceTask)
|
||||
require.Equal(t, 0, jobs[0].Retries) // hasn't run yet
|
||||
|
||||
// confirm that AccountConfiguration command was not enqueued
|
||||
require.ElementsMatch(t, []string{"InstallEnterpriseApplication"}, getEnqueuedCommandTypes(t))
|
||||
|
||||
// ensure that host_emails entry was not created from mdm_idp_accounts
|
||||
mysql.ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
var gotEmail string
|
||||
err := sqlx.GetContext(ctx, q, &gotEmail, `SELECT email FROM host_emails WHERE host_id = ? AND source = ?`, h.ID, fleet.DeviceMappingMDMIdpAccounts)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("installs fleetd for manual enrollments", func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user