Skip to content

Commit

Permalink
Improve device user account creation during MDM IdP enrollment flow (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gillespi314 committed Jul 10, 2024
1 parent 14722e6 commit 571c7df
Show file tree
Hide file tree
Showing 27 changed files with 900 additions and 86 deletions.
4 changes: 4 additions & 0 deletions changes/19185-dep-deviceinfo-mdm-idp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- 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.
2 changes: 2 additions & 0 deletions cmd/fleet/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,8 @@ 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const EnrollmentGate = ({
}: IEnrollmentGateProps) => {
const [showEULA, setShowEULA] = useState(Boolean(eulaToken));

const dep_device_info = localStorage.getItem("dep_device_info") || "";

if (!profileToken || error) {
return <SSOError />;
}
Expand All @@ -55,10 +57,11 @@ const EnrollmentGate = ({

return (
<RedirectTo
url={endpoints.MDM_APPLE_ENROLLMENT_PROFILE(
profileToken,
enrollmentReference
)}
url={endpoints.MDM_APPLE_ENROLLMENT_PROFILE({
token: profileToken,
ref: enrollmentReference,
dep_device_info,
})}
/>
);
};
Expand Down
14 changes: 10 additions & 4 deletions frontend/pages/MDMAppleSSOPage/MDMAppleSSOPage.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import React from "react";
import { useQuery } from "react-query";
import { AxiosError } from "axios";
import { WithRouterProps } from "react-router";

import mdmAPI from "services/entities/mdm";
import mdmAPI, { IMDMSSOParams } from "services/entities/mdm";

import SSOError from "components/MDM/SSOError";
import Spinner from "components/Spinner/Spinner";
import { IMdmSSOReponse } from "interfaces/mdm";

const baseClass = "mdm-apple-sso-page";

const DEPSSOLoginPage = () => {
const { error } = useQuery<void, AxiosError, IMdmSSOReponse>(
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>(
["dep_sso"],
() => mdmAPI.initiateMDMAppleSSO(),
() => mdmAPI.initiateMDMAppleSSO(query),
{
retry: false,
refetchOnWindowFocus: false,
Expand Down
15 changes: 13 additions & 2 deletions frontend/services/entities/mdm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DiskEncryptionStatus,
IHostMdmProfile,
IMdmProfile,
IMdmSSOReponse,
MdmProfileStatus,
} from "interfaces/mdm";
import { API_NO_TEAM_ID } from "interfaces/team";
Expand Down Expand Up @@ -67,6 +68,16 @@ 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;
Expand Down Expand Up @@ -181,9 +192,9 @@ const mdmService = {
});
},

initiateMDMAppleSSO: () => {
initiateMDMAppleSSO: (params: IMDMSSOParams): Promise<IMdmSSOReponse> => {
const { MDM_APPLE_SSO } = endpoints;
return sendRequest("POST", MDM_APPLE_SSO, {});
return sendRequest("POST", MDM_APPLE_SSO, params);
},

getBootstrapPackageMetadata: (teamId: number) => {
Expand Down
13 changes: 9 additions & 4 deletions frontend/utilities/endpoints.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { IMDMAppleEnrollmentProfileParams } from "services/entities/mdm";

const API_VERSION = "latest";

export default {
Expand Down Expand Up @@ -93,11 +95,14 @@ 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: string, ref?: string) => {
MDM_APPLE_ENROLLMENT_PROFILE: ({
token,
ref,
dep_device_info,
}: IMDMAppleEnrollmentProfileParams) => {
const query = new URLSearchParams({ token });
if (ref) {
query.append("enrollment_reference", ref);
}
ref && query.append("enrollment_reference", ref);
dep_device_info && query.append("dep_device_info", dep_device_info);
return `/api/mdm/apple/enroll?${query}`;
},
MDM_APPLE_SETUP_ENROLLMENT_PROFILE: `/${API_VERSION}/fleet/mdm/apple/enrollment_profile`,
Expand Down
55 changes: 50 additions & 5 deletions server/datastore/mysql/apple_mdm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2725,8 +2725,13 @@ 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 FROM mdm_idp_accounts WHERE email = ?`
stmt := `SELECT uuid, username, fullname, email, host_uuid, fleet_enroll_ref FROM mdm_idp_accounts WHERE email = ?`
var acct fleet.MDMIdPAccount
err := sqlx.GetContext(ctx, ds.reader(ctx), &acct, stmt, email)
if err != nil {
Expand All @@ -2738,13 +2743,39 @@ func (ds *Datastore) GetMDMIdPAccountByEmail(ctx context.Context, email string)
return &acct, nil
}

func (ds *Datastore) GetMDMIdPAccountByUUID(ctx context.Context, uuid string) (*fleet.MDMIdPAccount, error) {
stmt := `SELECT uuid, username, fullname, email FROM mdm_idp_accounts WHERE uuid = ?`
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 = ?`
var acct fleet.MDMIdPAccount
err := sqlx.GetContext(ctx, ds.reader(ctx), &acct, stmt, uuid)
err := sqlx.GetContext(ctx, ds.reader(ctx), &acct, stmt, accountUUID)
if err != nil {
if err == sql.ErrNoRows {
return nil, ctxerr.Wrap(ctx, notFound("MDMIdPAccount").WithMessage(fmt.Sprintf("with uuid %s", uuid)))
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, err, "select mdm_idp_accounts")
}
Expand Down Expand Up @@ -3638,6 +3669,20 @@ 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
Expand Down
4 changes: 2 additions & 2 deletions server/datastore/mysql/apple_mdm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2469,11 +2469,11 @@ func testMDMAppleIdPAccount(t *testing.T, ds *Datastore) {
require.ErrorAs(t, err, &nfe)
require.Nil(t, out)

out, err = ds.GetMDMIdPAccountByUUID(ctx, acc.UUID)
out, err = ds.GetMDMIdPAccountByAccountUUID(ctx, acc.UUID)
require.NoError(t, err)
require.Equal(t, acc, out)

out, err = ds.GetMDMIdPAccountByUUID(ctx, "BAD-TOKEN")
out, err = ds.GetMDMIdPAccountByAccountUUID(ctx, "BAD-TOKEN")
require.ErrorAs(t, err, &nfe)
require.Nil(t, out)
}
Expand Down
77 changes: 68 additions & 9 deletions server/datastore/mysql/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3629,19 +3629,78 @@ func (ds *Datastore) SetOrUpdateMDMData(
)
}

func (ds *Datastore) SetOrUpdateHostEmailsFromMdmIdpAccounts(
func (ds *Datastore) SetOrUpdateHostEmailsFromMDMIdPAccountsByLegacyEnrollRef(
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
if fleetEnrollmentRef != "" {
idp, err := ds.GetMDMIdPAccountByUUID(ctx, fleetEnrollmentRef)
if err != nil {
return err
}
email = &idp.Email
idp, err := ds.GetMDMIdPAccountByLegacyEnrollRef(ctx, fleetEnrollmentRef)
if err != nil {
return err
}
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,
Expand Down Expand Up @@ -5135,8 +5194,8 @@ func (ds *Datastore) loadHostLite(ctx context.Context, id *uint, identifier *str
SELECT
h.id,
h.team_id,
h.osquery_host_id,
h.node_key,
COALESCE(h.osquery_host_id, '') AS osquery_host_id,
COALESCE(h.node_key, '') AS node_key,
h.hostname,
h.uuid,
h.hardware_serial,
Expand Down
1 change: 1 addition & 0 deletions server/datastore/mysql/hosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7623,6 +7623,7 @@ 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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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
}
Loading

0 comments on commit 571c7df

Please sign in to comment.