Script para crear tokens seguros para macOS: guía para profesionales de TI

En el panorama de TI actual, gestionar las cuentas de usuario y garantizar un acceso seguro es fundamental para mantener una seguridad del sistema sólida. Uno de los aspectos clave de esta gestión en macOS es el uso de tokens seguros. Los tokens seguros son vitales para varias funciones de seguridad, incluyendo la activación de FileVault y la realización de ciertas tareas administrativas.

Este post profundizará en un script que automatiza el proceso de concesión de acceso seguro mediante token a cuentas de usuario en macOS, explicando su importancia, funcionalidad y casos de uso para profesionales de TI y proveedores de servicios gestionados (MSP).

Contexto

Los tokens seguros son una función de seguridad de macOS que proporciona medidas de autenticación adicionales, especialmente cuando se trata del cifrado de FileVault. Para los profesionales de TI y los MSP, la gestión de estos tokens es esencial para mantener entornos seguros en numerosos dispositivos.

El script proporcionado simplifica el proceso de conceder acceso seguro mediante token a una cuenta de usuario, incluso creando la cuenta si aún no existe. Esta automatización es especialmente beneficiosa en entornos a gran escala en los que la configuración manual sería poco práctica y llevaría mucho tiempo.

El script

#!/usr/bin/env bash
# Description: Grants secure token access to Service Account. Account will be created if it doesn't exist. Service Accounts will not show up at the desktop login.
# Release Notes: Initial Release
#
# Custom Fields:
#  New Account Password Custom Field: A secure custom field that stores the password for the new user account.
#  Optional Authentication Account Username Custom Field: A secure custom field that stores the username of the admin account that has secure token already on the device.
#
# Parameters:
#  username: Username to grant secure token access to
#  password: Password of user to grant secure token access to
#  adminuser: (Optional) Secure token Admin username - leave blank to prompt local user
#  adminpassword: (Optional) Secure token Admin password - leave blank to prompt local user
#
# Usage: ./Create-SecureTokenAccount.sh <-u|--username <arg>> <-p|--password <arg>> [-a|--adminuser <arg>] [-d|--adminpassword <arg>]
# <> are required
# [] are optional
# Example: ./Create-SecureTokenAccount.sh --username test --password Password1 --adminuser admin --adminpassword Password2
#
# Notes:
# By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
# Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
# Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
# Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
# Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
# Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
# Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
# EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#
#

die() {
    local _ret="${2:-1}"
    test "${_PRINT_HELP:-no}" = yes && print_help >&2
    echo "$1" >&2
    exit "${_ret}"
}

begins_with_short_option() {
    local first_option all_short_options='upadvh'
    first_option="${1:0:1}"
    test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}

GetCustomField() {
    customfieldName=$1
    dataPath=$(printenv | grep -i NINJA_DATA_PATH | awk -F = '{print $2}')
    value=""
    if [ -e "${dataPath}/ninjarmm-cli" ]; then
        value=$("${dataPath}"/ninjarmm-cli get "$customfieldName")
    else
        value=$(/Applications/NinjaRMMAgent/programdata/ninjarmm-cli get "$customfieldName")
    fi
    if [[ "${value}" == *"Unable to find the specified field"* ]]; then
        echo ""
        return 1
    else
        echo "$value"
    fi
}

# THE DEFAULTS INITIALIZATION - OPTIONALS
_arg_username=
_arg_password=
_arg_adminuser=
_arg_adminpassword=

print_help() {
    printf '%s\n' "Grants secure token access to an account. Account will be created if it doesn't exist."
    printf 'Usage: %s <-u|--username <arg>> <-p|--password <arg>> [-a|--adminuser <arg>] [-d|--adminpassword <arg>] [-h|--help]\n' "$0"
    printf '\t%s\n' "-u, --username: Username to grant secure token access to. (Required)"
    printf '\t%s\n' "-p, --password: Password of user to grant secure token access to. (Required)"
    printf '\t%s\n' "-a, --adminuser: (Optional) Secure token Admin username. (Leave blank to prompt local user)"
    printf '\t%s\n' "-d, --adminpassword: (Optional) Secure token Admin password. (Leave blank to prompt local user)"
    printf '\t%s\n' "-h, --help: Prints help"
}

parse_commandline() {
    while test $# -gt 0; do
        _key="$1"
        case "$_key" in
        -u | --username)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_username="$2"
            shift
            ;;
        --username=*)
            _arg_username="${_key##--username=}"
            ;;
        -u*)
            _arg_username="${_key##-u}"
            ;;
        -p | --password)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_password="$2"
            shift
            ;;
        --password=*)
            _arg_password="${_key##--password=}"
            ;;
        -p*)
            _arg_password="${_key##-p}"
            ;;
        -a | --adminuser)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_adminuser="$2"
            shift
            ;;
        --adminuser=*)
            _arg_adminuser="${_key##--adminuser=}"
            ;;
        -a*)
            _arg_adminuser="${_key##-a}"
            ;;
        -d | --adminpassword)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_adminpassword="$2"
            shift
            ;;
        --adminpassword=*)
            _arg_adminpassword="${_key##--adminpassword=}"
            ;;
        -d*)
            _arg_adminpassword="${_key##-d}"
            ;;
        -h | --help)
            print_help
            exit 0
            ;;
        -h*)
            print_help
            exit 0
            ;;
        *)
            _PRINT_HELP=yes die "FATAL ERROR: Got an unexpected argument '$1'" 1
            ;;
        esac
        shift
    done
}

parse_commandline "$@"

# Get Script Variables and override parameters
if [[ -n $(printenv | grep -i newAccountUsername | awk -F = '{print $2}') ]]; then
    _arg_username=$(printenv | grep -i newAccountUsername | awk -F = '{print $2}')
fi
if [[ -n $(printenv | grep -i newAccountPasswordCustomField | awk -F = '{print $2}') ]]; then
    # Get the password from the custom field
    if ! _arg_password=$(GetCustomField "$(printenv | grep -i newAccountPasswordCustomField | awk -F = '{print $2}')"); then
        # Exit if the custom field is empty
        if [[ -z "${_arg_password}" ]]; then
            echo "[Error] Custom Field ($(printenv | grep -i newAccountPasswordCustomField | awk -F = '{print $2}')) was not found. Please check that the custom field contains a password."
            exit 1
        fi
        # Exit if the custom field is not found
        echo "[Error] Custom Field ($(printenv | grep -i newAccountPasswordCustomField | awk -F = '{print $2}')) was not found. Please check the custom field name."
        exit 1
    fi
fi
if [[ -n $(printenv | grep -i optionalAuthenticationAccountUsername | awk -F = '{print $2}') ]]; then
    _arg_adminuser=$(printenv | grep -i optionalAuthenticationAccountUsername | awk -F = '{print $2}')
fi
if [[ -n $(printenv | grep -i optionalAuthenticationAccountPasswordCustomField | awk -F = '{print $2}') ]]; then
    # Get the password from the custom field
    if ! _arg_adminpassword=$(GetCustomField "$(printenv | grep -i optionalAuthenticationAccountPasswordCustomField | awk -F = '{print $2}')"); then
        # Exit if the custom field is empty
        if [[ -z "${_arg_adminpassword}" ]]; then
            echo "[Error] Custom Field ($(printenv | grep -i optionalAuthenticationAccountPasswordCustomField | awk -F = '{print $2}')) was not found. Please check that the custom field contains a password."
            exit 1
        fi
        # Exit if the custom field is not found
        echo "[Error] Custom Field ($(printenv | grep -i optionalAuthenticationAccountPasswordCustomField | awk -F = '{print $2}')) was not found. Please check the custom field name."
        exit 1
    fi
fi

# If both username and password are empty
if [[ -z "${_arg_username}" ]]; then
    echo "[Error] User Name is required."
    if [[ -z "${_arg_password}" ]]; then
        echo "[Error] Password is required, please set the password in the secure custom field."
    fi
    exit 1
fi

# If username is not empty and password is empty
if [[ -n "${_arg_username}" ]] && [[ -z "${_arg_password}" ]]; then
    echo "[Error] Password is required, please set the password in the secure custom field."
    exit 1
fi

# If username is not empty and password is empty
if [[ -n "${_arg_adminuser}" ]] && [[ -z "${_arg_adminpassword}" ]]; then
    echo "[Error] Password is required, please set the password in the secure custom field."
    exit 1
fi

UserAccount=$_arg_username
UserPass=$_arg_password
UserFullName="ServiceAccount"
secureTokenAdmin=$_arg_adminuser
secureTokenAdminPass=$_arg_adminpassword
macOSVersionMajor=$(sw_vers -productVersion | awk -F . '{print $1}')
macOSVersionMinor=$(sw_vers -productVersion | awk -F . '{print $2}')
macOSVersionBuild=$(sw_vers -productVersion | awk -F . '{print $3}')

# Check script prerequisites.

# Exits if macOS version predates the use of SecureToken functionality.
# Exit if macOS < 10.
if [ "$macOSVersionMajor" -lt 10 ]; then
    echo "[Warn] macOS version ${macOSVersionMajor} predates the use of SecureToken functionality, no action required."
    exit 0
# Exit if macOS 10 < 10.13.4.
elif [ "$macOSVersionMajor" -eq 10 ]; then
    if [ "$macOSVersionMinor" -lt 13 ]; then
        echo "[Warn] macOS version ${macOSVersionMajor}.${macOSVersionMinor} predates the use of SecureToken functionality, no action required."
        exit 0
    elif [ "$macOSVersionMinor" -eq 13 ] && [ "$macOSVersionBuild" -lt 4 ]; then
        echo "[Warn] macOS version ${macOSVersionMajor}.${macOSVersionMinor}.${macOSVersionBuild} predates the use of SecureToken functionality, no action required."
        exit 0
    fi
fi

# Exits if $UserAccount already has SecureToken.
if sysadminctl -secureTokenStatus "$UserAccount" 2>&1 | grep -q "ENABLED"; then
    echo "${UserAccount} already has a SecureToken. No action required."
    exit 0
fi

# Exits with error if $secureTokenAdmin does not have SecureToken
# (unless running macOS 10.15 or later, in which case exit with explanation).

if [ -n "$secureTokenAdmin" ]; then
    if sysadminctl -secureTokenStatus "$secureTokenAdmin" 2>&1 | grep -q "DISABLED"; then
        if [ "$macOSVersionMajor" -gt 10 ] || [ "$macOSVersionMajor" -eq 10 ] && [ "$macOSVersionMinor" -gt 14 ]; then
            echo "[Warn] Neither ${secureTokenAdmin} nor ${UserAccount} has a SecureToken, but in macOS 10.15 or later, a SecureToken is automatically granted to the first user to enable FileVault (if no other users have SecureToken), so this may not be necessary. Try enabling FileVault for ${UserAccount}. If that fails, see what other user on the system has SecureToken, and use its credentials to grant SecureToken to ${UserAccount}."
            exit 0
        else
            echo "[Error] ${secureTokenAdmin} does not have a valid SecureToken, unable to proceed. Please update to another admin user with SecureToken."
            exit 1
        fi
    else
        echo "[Info] Verified ${secureTokenAdmin} has SecureToken."
    fi
fi

# Creates a new user account.
create_user() {
    # Check if the user account exists
    if id "$1" >/dev/null 2>&1; then
        echo "[Info] Found existing user account $1."
    else
        echo "[Warn] Account $1 doesn't exist. Attempting to create..."
        # Create a new user
        dscl . -create /Users/"$1"
        # Add the display name of the User
        dscl . -create /Users/"$1" RealName "$3"
        # Replace password_here with your desired password to set the password for this user
        dscl . -passwd /Users/"$1" "$2"
        # Set the Unique ID for the New user. Replace with a number that is not already taken.
        LastID=$(dscl . -list /Users UniqueID | sort -nr -k 2 | head -1 | grep -oE '[0-9]+$')
        NextID=$((LastID + 1))
        dscl . -create /Users/"$1" UniqueID $NextID
        # Set the group ID for the user
        dscl . -create /Users/"$1" PrimaryGroupID 20
        # Append the User with admin privilege. If this line is not included the user will be set as standard user.
        # sudo dscl . -append /Groups/admin GroupMembership "$1"
        echo "[Info] Account $1 created."
    fi
}
# Adds SecureToken to target user.
securetoken_add() {
    if [ -n "$3" ]; then
        # Admin user name was given. Do not prompt the user.
        sysadminctl \
            -secureTokenOn "$1" \
            -password "$2" \
            -adminUser "$3" \
            -adminPassword "$4"
    else
        # Admin user name was not given. Prompt the local user.
        currentUser=$(stat -f%Su /dev/console)
        currentUserUID=$(id -u "$currentUser")
        launchctl asuser "$currentUserUID" sudo -iu "$currentUser" \
            sysadminctl \
            -secureTokenOn "$1" \
            -password "$2" \
            interactive
    fi
    # Verify successful SecureToken add.
    secureTokenCheck=$(sysadminctl -secureTokenStatus "${1}" 2>&1)
    if echo "$secureTokenCheck" | grep -q "DISABLED"; then
        echo "[Error] Failed to add SecureToken to ${1}. Please rerun policy; if issue persists, a manual SecureToken add will be required to continue."
        exit 126
    elif echo "$secureTokenCheck" | grep -q "ENABLED"; then
        echo "[Info] Successfully added SecureToken to ${1}."
    else
        echo "[Error] Unexpected result, unable to proceed. Please rerun policy; if issue persists, a manual SecureToken add will be required to continue."
        exit 1
    fi
}

# Create new user if it doesn't already exist.
create_user "$UserAccount" "$UserPass" "$UserFullName"
# Add SecureToken using provided credentials.
securetoken_add "$UserAccount" "$UserPass" "$secureTokenAdmin" "$secureTokenAdminPass"

 

Accede a más de 300 scripts en el Dojo de NinjaOne

Obtén acceso

Análisis detallado

Visión general del script

El script en cuestión está diseñado para conceder acceso seguro mediante token a una cuenta de usuario en macOS, con la capacidad de crear la cuenta si aún no existe. Aquí tienes un desglose detallado paso a paso de cómo funciona el script:

  1. Análisis de parámetros: el script comienza definiendo una función die para manejar errores y una función print_help para mostrar información de uso. A continuación, analiza los argumentos de la línea de comandos para extraer el nombre de usuario, la contraseña y, opcionalmente, el nombre de usuario y la contraseña de administrador.
  2. Variables de entorno: comprueba si existen variables de entorno que puedan anular los parámetros de la línea de comandos. Si se establecen variables de entorno específicas, el script recupera sus valores para utilizarlos como parámetros.
  3. Comprobación de la versión de macOS: el script comprueba la versión de macOS para asegurarse de que admite la funcionalidad de token seguro. Sale si la versión de macOS es demasiado antigua para utilizar tokens seguros.
  4. Comprobación del estado del token de seguridad: comprueba si la cuenta de usuario especificada ya tiene un token seguro. Si la cuenta de usuario ya tiene un token seguro, el script finaliza, ya que no es necesaria ninguna otra acción.
  5. Comprobación del token de usuario administrador: si se proporciona un nombre de usuario admin, el script verifica que este usuario admin tiene un token seguro. Si no, sale con un error a menos que la versión de macOS sea 10.15 o posterior, en cuyo caso se recomienda un proceso diferente.
  6. Creación de cuentas de usuario: el script incluye una función para crear una nueva cuenta de usuario si aún no existe. Asigna un ID único, establece una contraseña y configura otros atributos necesarios.
  7. Concesión de token seguro: el script intenta conceder un token seguro a la cuenta de usuario especificada utilizando las credenciales proporcionadas. Si se proporciona el nombre de usuario admin, utiliza esas credenciales; de lo contrario, solicita al usuario local que se autentique.

Posibles casos de uso

Imagina a un profesional de TI llamado Alex que gestiona una flota de dispositivos macOS para una gran empresa. Alex debe asegurarse de que todas las cuentas de usuario de estos dispositivos tengan tokens seguros para el cifrado de FileVault. Comprobar manualmente y conceder tokens seguros en cada dispositivo llevaría muchísimo tiempo.

Al desplegar este script a través de una herramienta de gestión centralizada, Alex puede automatizar el proceso, garantizando que todas las cuentas de usuario de la organización dispongan de los tokens seguros necesarios, manteniendo así el cumplimiento de las políticas de seguridad de la empresa.

Comparaciones

Otros métodos para conceder tokens seguros suelen implicar la intervención manual a través de las Preferencias del Sistema de macOS o el uso de comandos sysadminctl individualmente para cada usuario. Aunque estos métodos funcionan, no son escalables para gestionar un gran número de dispositivos. El script automatiza estos pasos, haciéndolos más eficientes y reduciendo la probabilidad de error humano.

FAQ

  • ¿Qué ocurre si la cuenta de usuario ya existe?

    El script comprueba la existencia de la cuenta de usuario y omite el paso de creación si ya existe.

  • ¿Puedo utilizar este script en versiones anteriores de macOS?

    El script incluye comprobaciones para garantizar que sólo se ejecuta en versiones de macOS compatibles con tokens seguros, concretamente macOS 10.13.4 y posteriores.

  • ¿Qué pasa si el usuario administrador no tiene un token seguro?

    El script saldrá con un error si el usuario administrador no tiene un token seguro, excepto en macOS 10.15 o posterior, donde se sugiere un proceso alternativo.

Implicaciones

Conceder tokens seguros a las cuentas de usuario es crucial para habilitar FileVault y realizar tareas administrativas de forma segura. La automatización de este proceso ayuda a mantener altos niveles de seguridad, garantiza el cumplimiento de las políticas de la organización y reduce el riesgo de accesos no autorizados.

Recomendaciones

  • Actualiza regularmente el script: asegúrate de que el script se mantiene actualizado con los últimos cambios y prácticas de seguridad de macOS.
  • Campos personalizados seguros: utiliza campos personalizados seguros para almacenar información confidencial, como contraseñas.
  • Gestión centralizada: despliega el script a través de una herramienta de gestión centralizada para garantizar la coherencia en todos los dispositivos.

Reflexiones finales

La automatización del proceso de concesión de tokens seguros mediante este script mejora significativamente la eficacia y la seguridad de la gestión de dispositivos macOS. Para los profesionales de TI y los MSP, este script es una herramienta valiosa para mantener unas prácticas de seguridad sólidas.

NinjaOne ofrece soluciones integrales que se integran a la perfección con dichos scripts, proporcionando un enfoque holístico de la gestión y la seguridad de TI. Con NinjaOne, puedes agilizar tus flujos de trabajo y garantizar que todos tus dispositivos son seguros y cumplen con las políticas de tu organización.

Categorías:

Quizá también te interese…

×

¡Vean a NinjaOne en acción!

Al enviar este formulario, acepto la política de privacidad de NinjaOne.

Términos y condiciones de NinjaOne

Al hacer clic en el botón “Acepto” que aparece a continuación, estás aceptando los siguientes términos legales, así como nuestras Condiciones de uso:

  • Derechos de propiedad: NinjaOne posee y seguirá poseyendo todos los derechos, títulos e intereses sobre el script (incluidos los derechos de autor). NinjaOne concede al usuario una licencia limitada para utilizar el script de acuerdo con estos términos legales.
  • Limitación de uso: solo podrás utilizar el script para tus legítimos fines personales o comerciales internos, y no podrás compartirlo con terceros.
  • Prohibición de republicación: bajo ninguna circunstancia está permitido volver a publicar el script en ninguna biblioteca de scripts que pertenezca o esté bajo el control de cualquier otro proveedor de software.
  • Exclusión de garantía: el script se proporciona “tal cual” y “según disponibilidad”, sin garantía de ningún tipo. NinjaOne no promete ni garantiza que el script esté libre de defectos o que satisfaga las necesidades o expectativas específicas del usuario.
  • Asunción de riesgos: el uso que el usuario haga del script corre por su cuenta y riesgo. El usuario reconoce que existen ciertos riesgos inherentes al uso del script, y entiende y asume cada uno de esos riesgos.
  • Renuncia y exención: el usuario no hará responsable a NinjaOne de cualquier consecuencia adversa o no deseada que resulte del uso del script y renuncia a cualquier derecho o recurso legal o equitativo que pueda tener contra NinjaOne en relación con su uso del script.
  • CLUF: si el usuario es cliente de NinjaOne, su uso del script está sujeto al Contrato de Licencia para el Usuario Final (CLUF).