Filevault during Domain Migration

alexjdale
Valued Contributor III

We're looking at a domain migration (Active Directory domain A to B), and I'm quite comfortable with all of the user account/profile/domain binding angles, but I'm really concerned about the FV user angle.

Has anyone automated a domain migration without decrypting FileVault? I can't think of a process/scenario that doesn't stand a decent chance of leaving the system in a state where the user would have no accounts authorized for FV. The expectation here is that we do not have a technician working with the user/system unless something goes wrong.

6 REPLIES 6

stevewood
Honored Contributor II
Honored Contributor II

@alexjdale you can see how I handled it via this thread:

https://jamfnation.jamfsoftware.com/discussion.html?id=11606#respond

I utilized a Self Service approach so my users could move on their own time. The script I used can be found here:

https://github.com/stevewood-tx/CasperScripts-Public/tree/master/MoveADDomain

Basically, utilizing the plist function of fdesetup along with createmobileaccount was the answer for me. The only problems I've run into were with users whose current home folder/user name were different than the one they had in the new domain (jsmith vs. johnsmith for example), or if their home folder was named different on the local machine. The other problem I run into with it is end users not putting in the proper password for the new AD so they reboot and cannot get past FileVault (we have an admin account that is enabled for FV so I log in and add them to FV).

Hope that helps.

mm2270
Legendary Contributor III

@stevewood,
I never posted back on your linked thread above, but I was actually wondering how you were handling the issue of users entering the wrong password for their account in the dialog. Or even a case where someone accidentally hits return with no password entered and it submits a blank password back. In both cases, FV2 would of course fail to start.

The latter can actually be easily handled right in cocoaDialog with one of the built in options. The former can also be handled, but takes a bit more effort. I had experimented with the process at one point. If you're interested in how I was able to confirm the password the user entered was correct, I can post it here in case you can use it.

stevewood
Honored Contributor II
Honored Contributor II

@mm2270 yeah, if you don't mind posting the code, that'd be great. I still have a few machines left to migrate and only have about 6 days to do it in. :-) Nothing like a little pressure.

mm2270
Legendary Contributor III

Sure.
So, as I mentioned, making sure some input was received in the dialog is easy. cD betas included a new function called:

--value-required

When you throw that into the cocoaDialog line it makes sure some value is entered before the OK or Enter button can be clicked. If its blank, a sheet comes down right in the inputbox dialog with some text explaining they have to enter something.
One note on this. If you're using two buttons, like OK and Cancel, as long as the cancel button is actually labeled "Cancel" there's nothing more to do. If you use some custom button name, like "Exit" you'd need to add some additional flags in for the button to work in conjunction with --value-required
For ex, if I use a dialog with the following included

--button1 "Enter" --button2 "Exit" --value-required

I would also need to put in:

--cancel "button2"

to let it know button 2 is the "cancel" button. Otherwise clicking either button would not allow exiting without some value entered in the password field.
So that prevents any blank passwords being passed to the rest of the script.

The second part's a little more involved. Here's the script I developed to verify an account name+password and was part of a script to then create the account (createmobileaccount) afterwards.
Note that a number of items up top need to be customized for your AD environment. Also, the script is using CD to ask for an account name, but not sure if that's needed with your process?
Also, since this uses ldapsearch for the password verification, you need to enter some account name that will always be in your AD environment to use as the search. Change the samName variable to something in your environment.

Script below:

#!/bin/sh

## Enter the path to cocoaDialog. Edit to your own location
cdPath="/Library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog"

################ AD server settings #################
### Edit these settings to match your environment ###

## For below, enter the full domain path, such as "/Active Directory/DOMAIN/All Domains"
DomainFull="/Active Directory/DOMAIN/All Domains"

## Enter a primary domain controller's FQDN, such as "auth.domain.org.com"
Host="auth.domain.org.com"

## Enter the default port used for ldap search, typically "389"
Port="389"

## Enter the domain in 'domain component' notation, such as "dc=domain,dc=org,dc=com"
Domain="dc=domain,dc=org,dc=com"

## The sAMAccountName to use for the ldap search
## This must be an account that does not get disabled or removed from AD, such as a service account
## (Note that only the name is necessary, not the account password)
samName="bindaccount"

############### End AD server settings #############


## Other than dialog wording, no edits should be made below this line

function noNetwork ()   ## This function runs only in the rare case that a connection to the company network could not be established
{

"$cdPath" msgbox --title "" --text "No network connection" 
--informative-text "$(echo "This Mac is not connected to the ORG network. Please connect directly or over VPN before trying again.

 ")" 
--icon info --width 400 --button1 "   OK   " --quiet

exit 1

}

function wrongPass ()   ## This function runs if the password entered is the wrong password for the account entered
{

passReAsk=$( "$cdPath" msgbox --title "" --text "Password could not be verified" 
--informative-text "$(echo "The password you entered could not be verified for the account ${loggedInUser}.

Please enter the password again on the next screen to continue.
Or click Cancel if you want to exit.")" 
--button1 " Try again " --button2 "  Cancel  " --cancel "button2" --icon caution --timeout 20 --timeout-format " " )

if [ "$passReAsk" == "1" ]; then
    getPass
else
    echo "Operation canceled"
    exit 0
fi

}

function noMatch ()     ## This function runs if the 2 passwords entered do not match
{

echo "The passwords entered by $loggedInUser do not match. Asking user to re-enter passwords"

passReAsk=$( "$cdPath" msgbox --title "" --text "Passwords do not match" 
--informative-text "$(echo "The passwords you entered did not match.

Please click "Try again" to re-enter the password. Or click "Cancel" if you want to exit.")" 
--button1 " Try again " --button2 "  Cancel  " --cancel "button2" --width 400 --icon caution --timeout 20 --timeout-format " " )

if [ "$passReAsk" == "1" ]; then
    getPass
else
    echo "Operation canceled"
    exit 0
fi

}


function getPass ()     ## This function runs after the account name is verified and asks for the account password 2 times
{

userPassword1=""
userPassword2=""

userPassword1=$("$cdPath" secure-inputbox --title "" --informative-text "Please enter the domain password for "$userName":" 
--button1 "Continue" --button2 " Cancel " --cancel "button2" --value-required --empty-text "Please enter the password before clicking "Continue"" 
--icon-file "/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain.icns" --quiet)

if [ "$userPassword1" ]; then
    userPassword2=$("$cdPath" secure-inputbox --title "" --informative-text "Please enter your password again for verification:" 
    --button1 " Enter " --button2 " Cancel " --cancel "button2" --value-required --empty-text "Please enter your password before clicking "Enter"" 
    --icon-file "/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain.icns" --quiet)

    if [[ "$userPassword1" == "$userPassword2" ]]; then
        ldapsearch -LLL -h "$Host" -p $Port -b "$Domain" -D "${DN}" 
        -w "${userPassword1}" "(sAMAccountName=${samName})" 2>/dev/null 1>/dev/null

        if [ "$?" != "0" ]; then
            wrongPass
        fi
    else
        noMatch
    fi
else
    exit 0
fi

}


function userNotFound ()    ## This function runs if the account name entered does not exist in AD
{

userReAsk=$( "$cdPath" msgbox --title "" --text "Username not found" 
--informative-text "$(echo "The username you entered could not be verified in Active Directory.

Please click "Try again" to re-enter the username. Or click "Cancel" if you want to exit")" 
--button1 " Try again " --button2 "  Cancel  " --cancel "button2" --width 400 --icon caution --timeout 20 --timeout-format " " )

if [ "$userReAsk" == "1" ]; then
    askForUser
else
    echo "Operation canceled"
    exit 0
fi

}

function askForUser ()      ## This function runs to ask for an AD username
{

userEntered=$( "$cdPath" inputbox --title "" --informative-text "Enter the username for the account to be created:" 
--icon-file "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/UserIcon.icns" --button1 "  Enter  " --button2 " Cancel " 
--cancel "button2" --value-required --empty-text "Please enter a username before clicking "Enter"" --quiet )

if [ "$userEntered" ]; then
    userName=$(echo "$userEntered" | awk 'NR==1{print}')
    echo "Username entered: $userName"

    ## Get the account's Distinguished Name from AD (we use this in the ldap search)
    DN=$(dscl "${DomainFull}" read /Users/${userName} 2>/dev/null | awk '/AppleMetaRecordName/{getline; print}' | sed 's/^ *//')

    if [[ ! -z "$DN" ]]; then
        getPass
    else
        userNotFound
    fi
else
    echo "User chose to cancel. Exiting..."
    exit 0
fi

}

## Check that the primary host (dc) is available before continuing.
## Errors may mean improper network connectivity, or an unbound Mac.
if ping -q -c 1 -o "$Host" 2>&1 >/dev/null; then
    echo "In range of domain controller. Asking for username..."
    askForUser
else
    echo "We aren't connected to the network, or the Mac isn't bound to AD"
    noNetwork
fi

## If we get to this point in the script, all checks passed
echo "Password verified. We can continue"

## Display message that account and password were verified
"$cdPath" msgbox --title "" --text "Verified!" 
--informative-text "The password was verified for the account ${userName}. The account creation will now continue." 
--icon info --button1 "   OK   " --width 400 --quiet --timeout 10 --timeout-format " "

Its a longish script, but part of it is to account for asking for the password twice to the user, then verifying they match, etc etc. Some of it may not be necessary, but I like to make things as fool proof as possible :)

alexjdale
Valued Contributor III

Thanks for the input (and valuable "I've done that" testimonial), I'm feeling more comfortable.

Deleting the user account while they are logged in and executing the script isn't an issue?

Olivier
New Contributor II

We did it with a AD forest change, without decrypting any machine (sAMAccountName were kept same, but obviously SID changed, and thus, OSX UID for the user also changes).

The thing to know if that FileVault module performs a "cleaning" job after you unlocked the machine during OSX startup (similar to sudo fdesetup sync), check if all FileVault UIDs (sudo fdesetup list) are also still seen in Directory Service database. If there are inconsistencies, these FV entries are removed silently.

We had to cheat OSX a bit, and use the trick that after binding to new domain and restarting the Mac, the "inconsistent" user is still at FV screen.

As you booted OSX, and because your new SID/UID doesn't match the locally-cached SID/UID, you land on logon screen, instead of autologging as usual.
User has to logon, and then we ran a LaunchDaemon (cocoadialog asking the user for his domain password, running under user context), and pass the result as plist file to a LaunchDaemon (executed under root, using "WatchPaths" so that it can communicate with the LaunchAgent and add the user to FV user list again using an admin service account).

To add the user to FV user list, you need to have on the system a temp local admin account (create it before you start whole AD unbind/bind process), so that your script can use this account as main authorized account in the fdesetup plist file, and user's creds as account to add. If you don't do that, the next time user will restart his machine, as you said, the user would have no accounts authorized for FV, because OSX already performed the cleaning job due to mismatch of UID in FV user list, and UID in Directory Service DB.

We did not wanted to use any "createmobileaccount" stuff, as it would maybe create a new account in DS DB or a new folder in /Users, and we would need to deal with moving files from previous account folder to the new one. Unnecessary complexity...

So in short : - add a local admin account with a password you control
- unbind from AD, bind to new AD
- get UID and GID for your new account (dsmemberutil getid -U $USER@your_domain...)
- recursively chown the /Users/xxxx folder, so that at next startup, user will have access to his user's folder.
- install LaunchDaemon script that waits for future user's password input (use WatchPatch key for example). Install LaunchAgent that will run CocoaDialog and prompt user for his (new domain) password, and put into a folder monitored by WatchPatchs.
- reboot
- ask user to logon to pass FV screen (user should ignore the temp admin account). OSX is started, and land on logonscreen (but cannot autologon since UIDs between your 2 accounts are different). IMPORTANT : boot with cable connected!!!
- OSX silently update your DS DB entries (UID/GID, AuthenticationAuthority, PrimaryNTDomain,...) during logon process. That is why you must logon with cable.
- LaunchAgent to ask user's password (LaunchAgent because it is a GUI app). Build plist file on the fly with user's creds and your temp local acct creds, pass them to your LaunchDaemon that runs fdesetup. Delete the plist file and the local admin account.
- Job done.