Macjutsu / super

S.U.P.E.R.M.A.N. optimizes the macOS software update experience.
Apache License 2.0
626 stars 84 forks source link

Use user's authentication to create the service account #134

Open arilewis93 opened 1 year ago

arilewis93 commented 1 year ago

When the user is asked for their password to install an update, use the user account to create a service account.

When the user clicks "install" and is prompted for their password (if their is no super service account and MDM fails), use those credentials to create a super service account so that that are never asked again.

This would require inserting the following code before line 7494:

adminACCOUNT=$currentUserNAME
adminPASSWORD="$dialogRESULT"
# if the user is not an admin, make them an admin and store the demote var to user later.
[[ $(dseditgroup -o checkmember -m "$currentUserNAME" admin) != "yes $currentUserNAME is a member of admin" ]] && demote=true && sudo /usr/sbin/dseditgroup -o edit -a $currentUserNAME -t user admin
# Create service account now
manageUpdateCredentials
# Demote user
[[ $demote ]] && sudo /usr/sbin/dseditgroup -o edit -d $currentUserNAME -t user admin
Macjutsu commented 1 year ago

That code is based on v3 so it won't work in v4.

There is also a lot of other ancillary preference management code that needs to be added for a new feature. Further this will require the creation of a new function for the service account creation because there would be two different workflows using similar code.

In other words, while this is a good idea that I plan to implement, it's more complicated than just the changes you have here.

arilewis93 commented 1 year ago

Cool. I'm testing it on v3 in our fleet of 800 or so macs and so far it seems to be working... A though I had (which I'm gonna mention even though this enhancement will make it irrelevant) is a way to run the service account creation workflow without everything else. I made a script to ask the user for their password and pass it into super and then check if the account was created and then tell them it was successful with a popup. The issue is that super get "sidetracked" and the pop up takes forever to show because super is busy checking for updates....

Macjutsu commented 1 year ago

Full disclosure... I don't ever plan to look at the v3 code again.

arilewis93 commented 1 year ago

Cool, I hear that. So I guess I'll use as I have it, and my comment above is clear enough (I think?) for others to also use if they wish. But at least the idea will be taken forward to v4. BTW, thanks for such a fantastic tool! Whenever I look at the dashboard after an update, I can't believe how fast the compliance rises - I never knew being so annoying could be so affective!

arilewis93 commented 11 months ago

I added the following in version 4.0.0 after line 7983 (for reference, it was after this line if [[ "${dialog_user_auth_valid}" == "TRUE" ]]; then - in case you want to use it on a newer version)

    auth_service_add_via_admin_account_option="${current_user_account_name}"
    auth_service_add_via_admin_password_option="${auth_local_password}"
    [[ $(dseditgroup -o checkmember -m "$auth_service_add_via_admin_account_option" admin) != "yes $auth_service_add_via_admin_account_option is a member of admin" ]] && demote=true && sudo /usr/sbin/dseditgroup -o edit -a $auth_service_add_via_admin_account_option -t user admin
    # Create service account now
    manage_authentication_options
    [[ $demote ]] && sudo /usr/sbin/dseditgroup -o edit -d $auth_service_add_via_admin_account_option -t user admin
    sudo super --workflow-install-now

This worked whether the user was an admin or not. The last line sudo super --workflow-install-now causes a loop if for some reason creation of the account failed. so you may want to leave it out.

Macjutsu commented 11 months ago

Yikes! It's not safe to put that function there.

I'm happy that this works for you... and I do plan to implement this officially... but it will take quite a bit more changes to support this feature in a safe way.

arilewis93 commented 11 months ago

I'm interested why you say its not safe - maybe using this code with other implementation options (like trying to use MDM auth or a local existing account) wont work as expected, but using it without any of those and just a plist to define deferrals, I cant see the issue (yes I have gone through the code, every line in the func)

Macjutsu commented 11 months ago

That code was designed to run during the startup workflow, and where you have it is now much later in the workflow. Again, it might work... but it was never designed for or tested to run that late in the workflow.

To implement this properly (as I plan to do in a future version) there is a lot more work involved to implement this as a configurable option, test the crap out of it, and document.

arilewis93 commented 11 months ago
manage_authentication_create_service_account () {

    log_super "Status: Creating and validating new super service account..."

    # Validate that ${auth_service_add_via_admin_account_option} exists, is a volume owner, a local admin, and that ${auth_service_add_via_admin_password_option} is correct.
    if [[ "${auth_legacy_service_migrate}" != "TRUE" ]]; then # If migrating a legacy sevice account, then the ${auth_service_add_via_admin_account_option} doesn't have to be validated.
        if [[ $(groups "${auth_service_add_via_admin_account_option}" 2> /dev/null | grep -c 'admin') -eq 0 ]]; then
            # User is not an admin, elevate them to create the service account.
            demote=true 
            /usr/sbin/dseditgroup -o edit -a $auth_service_add_via_admin_account_option -t user admin
        fi
        if [[ "${auth_error_new}" != "TRUE" ]]; then
            auth_local_account="${auth_service_add_via_admin_account_option}"
            auth_local_password="${auth_service_add_via_admin_password_option}"
            check_auth_local_account
            unset auth_local_account
            unset auth_local_password
            [[ "${auth_error_local}" == "TRUE" ]] && auth_error_new="TRUE"
        fi
    fi

    # Set the ${auth_service_account}, ${auth_service_real_name}, and ${auth_service_password} in preparation to create the super service account.
    if [[ "${auth_error_new}" != "TRUE" ]]; then
        if [[ "${auth_legacy_service_migrate}" != "TRUE" ]]; then # If migrating a legacy sevice account, then a new service account doesn't have to be created.
            local auth_service_account
            local auth_service_real_name
            if [[ -n "${auth_service_account_option}" ]]; then
                auth_service_account="${auth_service_account_option}"
                auth_service_real_name="${auth_service_account_option}"
            else
                auth_service_account="super"
                auth_service_real_name="Super Update Service"
            fi

            local auth_service_password
            if [[ -n "${auth_service_password_option}" ]]; then
                auth_service_password="${auth_service_password_option}"
            else
                auth_service_password=$(uuidgen)
            fi
        fi

        # Save ${auth_service_account} and ${auth_service_password} credentials to keychain and then validate retrieval by setting ${auth_service_account_keychain} and ${auth_service_password_keychain}.
        security add-generic-password -a "super_auth_service_account" -s "Super Update Service" -w "$(echo "${auth_service_account}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" > /dev/null 2>&1
        auth_service_account_keychain=$(security find-generic-password -w -a "super_auth_service_account" "/Library/Keychains/System.keychain" 2> /dev/null | base64 --decode)
        if [[ "${auth_service_account}" != "${auth_service_account_keychain}" ]]; then
            [[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: auth_service_account is: ${auth_service_account}"
            [[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: auth_service_account_keychain is: ${auth_service_account_keychain}"
            log_super "Auth Error: Unable to validate keychain item for the super service account, deleting keychain item."; auth_error_new="TRUE"
            security delete-generic-password -a "super_auth_service_account" "/Library/Keychains/System.keychain" > /dev/null 2>&1
            create_service_account_failed=true
        fi
        security add-generic-password -a "super_auth_service_password" -s "Super Update Service" -w "$(echo "${auth_service_password}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" > /dev/null 2>&1
        auth_service_password_keychain=$(security find-generic-password -w -a "super_auth_service_password" "/Library/Keychains/System.keychain" 2> /dev/null | base64 --decode)
        if [[ "${auth_service_password}" != "${auth_service_password_keychain}" ]]; then
            [[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: auth_service_password is: ${auth_service_password}"
            [[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: auth_service_password_keychain is: ${auth_service_password_keychain}"
            log_super "Auth Error: Unable to validate keychain item for the super service password, deleting keychain item."; auth_error_new="TRUE"
            security delete-generic-password -a "super_auth_service_password" "/Library/Keychains/System.keychain" > /dev/null 2>&1
            create_service_account_failed=true
        fi
    fi

    # If the saved credentials are valid then create the new super service account.
    if [[ "${auth_error_new}" != "TRUE" ]] && [[ "${auth_legacy_service_migrate}" != "TRUE" ]]; then # If migrating a legacy sevice account, then a new service account doesn't have to be created.
        local auth_service_uid
        auth_service_uid=501
        while [[ $(id "${auth_service_uid}" 2>&1 | grep -c 'no such user') -eq 0 ]]; do
            auth_service_uid=$((auth_service_uid + 1))
        done
        local sysadminctl_response
        sysadminctl_response=$(sysadminctl -addUser "${auth_service_account}" -fullName "${auth_service_real_name}" -password "${auth_service_password}" -UID "${auth_service_uid}" -GID 20 -shell "/dev/null" -home "/dev/null" -picture "${DISPLAY_ICON_FILE_CACHE}" -adminUser "${auth_service_add_via_admin_account_option}" -adminPassword "${auth_service_add_via_admin_password_option}" 2>&1)
        [[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: sysadminctl_response is:\n${sysadminctl_response}"
        dscl . create /Users/"${auth_service_account}" IsHidden 1
    fi

    # Validate the super service account locally.
    auth_local_account="${auth_service_account}"
    auth_local_password="${auth_service_password}"
    check_auth_local_account
    unset auth_local_account
    unset auth_local_password
    [[ "${auth_error_local}" == "TRUE" ]] && auth_error_new="TRUE"

    # If the super service account is valid then update ${SUPER_LOCAL_PLIST}.
    if [[ "${auth_error_new}" != "TRUE" ]]; then
        [[ "${auth_legacy_service_migrate}" != "TRUE" ]] && log_super "Status: Created new super serivce account."
        [[ "${auth_legacy_service_migrate}" == "TRUE" ]] && log_super "Status: Validated migrated super serivce account."
        defaults write "${SUPER_LOCAL_PLIST}" AuthServiceAccount -bool true
        auth_service_account_saved="TRUE"
    else
        [[ "${auth_legacy_service_migrate}" != "TRUE" ]] && log_super "Auth Error: Unable to validate newly created super service account, deleting account"; auth_error_new="TRUE"
        [[ "${auth_legacy_service_migrate}" == "TRUE" ]] && log_super "Auth Error: Unable to validate migrated super service account, deleting account."; auth_error_new="TRUE"
        sysadminctl -deleteUser "${auth_service_account}" > /dev/null 2>&1
        security delete-generic-password -a "super_auth_service_account" "/Library/Keychains/System.keychain" > /dev/null 2>&1
        security delete-generic-password -a "super_auth_service_password" "/Library/Keychains/System.keychain" > /dev/null 2>&1
        auth_service_account_saved="FALSE"
        unset auth_service_account_keychain
        unset auth_service_password_keychain
        create_service_account_failed=true
    fi

    # If the user was not an admin initially, remove admin access that was granted to create the service account.
    [[ $demote ]] && sudo /usr/sbin/dseditgroup -o edit -d $auth_service_add_via_admin_account_option -t user admin

}

I made this as a standalone function - not sure what you want to call it or where you want to insert it.

It means that after this line: if [[ "${dialog_user_auth_valid}" == "TRUE" ]]; then you need these 5 lines of code:

    if [[ -z "${create_service_account_failed}" ]]; then
        auth_service_add_via_admin_account_option="${current_user_account_name}"
        auth_service_add_via_admin_password_option="${auth_local_password}"
        manage_authentication_create_service_account; super --workflow-install-now
    fi

(obviously the last line depends on what you want to call the new func, and how you want to make it configurable by making an if before it)... Hope this helps others!

Macjutsu commented 11 months ago

That's a better approach.... and similar to the one I will take when I have time to implement this in v4.1... other priorities first.