Tutorial: Protección de Azure Remote Rendering y el almacenamiento de modelos

En este tutorial, aprenderá a:

  • Proteger instancias de Azure Blob Storage que contienen modelos de Azure Remote Rendering.
  • Autenticación con Microsoft Entra ID para acceder a la instancia de Azure Remote Rendering
  • Usar credenciales de Azure para la autenticación de Azure Remote Rendering.

Requisitos previos

¿Por qué se necesita seguridad adicional?

El estado actual de la aplicación y su acceso a los recursos de Azure se parecen a esta imagen:

Initial security

Tanto "AccountID + AccountKey" como "URL + Token de SAS" almacenan básicamente un nombre de usuario y una contraseña juntos. Por ejemplo, si se expusieran "AccountID + AccountKey", un atacante no tendría ningún problema para usar los recursos de ARR sin su permiso a su costa.

Protección del contenido de Azure Blob Storage

Azure Remote Rendering puede acceder de forma segura al contenido de la instancia de Azure Blob Storage con la configuración correcta. Consulte Vinculación de cuentas de almacenamiento para configurar la instancia de Azure Remote Rendering con las cuentas de almacenamiento de blobs.

Cuando se usa un almacenamiento de blobs vinculado, se emplean métodos ligeramente diferentes para cargar los modelos:

var loadModelParams = new LoadModelFromSasOptions(modelPath, modelEntity);
var task = ARRSessionService.CurrentActiveSession.Connection.LoadModelFromSasAsync(loadModelParams);

En las líneas anteriores se usa la versión FromSas de los parámetros y de la acción de sesión. Estos deben convertirse a las versiones que no sean SAS:

var loadModelParams = LoadModelOptions.CreateForBlobStorage(storageAccountPath, blobName, modelPath, modelEntity);
var task = ARRSessionService.CurrentActiveSession.Connection.LoadModelAsync(loadModelParams);

Vamos a modificar RemoteRenderingCoordinator para cargar un modelo personalizado, desde una cuenta de blobs vinculada.

  1. Si aún no lo ha hecho, realice el tutorial Procedimientos: Vinculación de las cuentas de almacenamiento para conceder a la instancia de ARR permiso de acceso a la instancia de Blob Storage.

  2. Agregue el siguiente método modificado LoadModel a RemoteRenderingCoordinator justo debajo del método LoadModel actual:

    /// <summary>
    /// Loads a model from blob storage that has been linked to the ARR instance
    /// </summary>
    /// <param name="storageAccountName">The storage account name, this contains the blob containers </param>
    /// <param name="blobName">The blob container name, i.e. arroutput</param>
    /// <param name="modelPath">The relative path inside the container to the model, i.e. test/MyCustomModel.arrAsset</param>
    /// <param name="parent">The parent Transform for this remote entity</param>
    /// <param name="progress">A call back method that accepts a float progress value [0->1]</param>
    /// <returns></returns>
    public async Task<Entity> LoadModel(string storageAccountName, string blobName, string modelPath, UnityEngine.Transform parent = null, Action<float> progress = null)
    {
        //Create a root object to parent a loaded model to
        var modelEntity = ARRSessionService.CurrentActiveSession.Connection.CreateEntity();
    
        //Get the game object representation of this entity
        var modelGameObject = modelEntity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
    
        //Ensure the entity will sync its transform with the server
        var sync = modelGameObject.GetComponent<RemoteEntitySyncObject>();
        sync.SyncEveryFrame = true;
    
        //Parent the new object under the defined parent
        if (parent != null)
        {
            modelGameObject.transform.SetParent(parent, false);
            modelGameObject.name = parent.name + "_Entity";
        }
    
        //Load a model that will be parented to the entity
        var loadModelParams = LoadModelOptions.CreateForBlobStorage($"{storageAccountName}.blob.core.windows.net", blobName, modelPath, modelEntity);
        var loadModelAsync = ARRSessionService.CurrentActiveSession.Connection.LoadModelAsync(loadModelParams, progress);
        var result = await loadModelAsync;
        return modelEntity;
    }
    

    En su mayor parte, este código es idéntico al método LoadModel original; sin embargo, se ha reemplazado la versión de SAS de las llamadas de método por las versiones que no son de SAS.

    También se han agregado las entradas adicionales storageAccountName y blobName a los argumentos. Llamamos a este nuevo método LoadModel desde otro método parecido al primer método LoadTestModel que hemos creado en el primer tutorial.

  3. Agregue el método siguiente a RemoteRenderingCoordinator justo después de LoadTestModel

    private bool loadingLinkedCustomModel = false;
    
    [SerializeField]
    private string storageAccountName;
    public string StorageAccountName {
        get => storageAccountName.Trim();
        set => storageAccountName = value;
    }
    
    [SerializeField]
    private string blobContainerName;
    public string BlobContainerName {
        get => blobContainerName.Trim();
        set => blobContainerName = value;
    }
    
    [SerializeField]
    private string modelPath;
    public string ModelPath {
        get => modelPath.Trim();
        set => modelPath = value;
    }
    
    [ContextMenu("Load Linked Custom Model")]
    public async void LoadLinkedCustomModel()
    {
        if (CurrentCoordinatorState != RemoteRenderingState.RuntimeConnected)
        {
            Debug.LogError("Please wait for the runtime to connect before loading the test model. Try again later.");
            return;
        }
        if (loadingLinkedCustomModel)
        {
            Debug.Log("Linked Test model already loading or loaded!");
            return;
        }
        loadingLinkedCustomModel = true;
    
        // Create a parent object to use for positioning
        GameObject testParent = new GameObject("LinkedCustomModel");
        testParent.transform.position = new Vector3(0f, 0f, 3f);
    
        await LoadModel(StorageAccountName, BlobContainerName, ModelPath, testParent.transform, (progressValue) => Debug.Log($"Loading Test Model progress: {Math.Round(progressValue * 100, 2)}%"));
    }
    

    Este código agrega tres variables de cadena adicionales al componente RemoteRenderingCoordinator. Screenshot that highlights the Storage Account Name, Blob Container Name, and Model Path of the RemoteRenderingCoordinator component.

  4. Agregue los valores al componente RemoteRenderingCoordinator. Tras haber seguido el inicio rápido para la conversión de modelos, los valores serán:

    • Nombre de cuenta de almacenamiento: el nombre de la cuenta de almacenamiento, el nombre único global que elija para la cuenta de almacenamiento. En el inicio rápido fue arrtutorialstorage; su valor es diferente.
    • Blob Container Name (Nombre de contenedor de blobs): arroutput, el contenedor de Blob Storage.
    • Model Path (Ruta de acceso del modelo): la combinación de "outputFolderPath" y "outputAssetFileName" definida en el archivo arrconfig.json. En el inicio rápido, era "outputFolderPath":"converted/robot", "outputAssetFileName": "robot.arrAsset". El resultado sería un valor de la ruta de acceso del modelo de "converted/robot/robot.arrAsset", pero el suyo es diferente.

    Sugerencia

    Si ejecuta el script Conversion.ps1, sin el argumento "-UseContainerSas", el script generará todos los valores anteriores automáticamente en lugar del token de SAS. Linked Model

  5. Mientras tanto, quite o deshabilite el elemento GameObject TestModel para dejar espacio para la carga del modelo personalizado.

  6. Reproduzca la escena y conéctese a una sesión remota.

  7. Abra el menú contextual en RemoteRenderingCoordinator y seleccione Cargar modelo personalizado vinculado. Load linked model

Estos pasos han aumentado la seguridad de la aplicación al eliminar el token de SAS de la aplicación local.

Ahora, el estado actual de la aplicación y su acceso a los recursos de Azure se parecen a esta imagen:

Better security

Tenemos que quitar una "contraseña" más de la aplicación local, AccountKey. Esto se puede hacer mediante la autenticación de Microsoft Entra.

Autenticación de Microsoft Entra

La autenticación de Microsoft Entra le permite determinar qué individuos o grupos usan ARR de una manera más controlada. ARR integra compatibilidad con tokens de acceso en lugar de usar una clave de cuenta. Los tokens de acceso pueden considerarse claves específicas del usuario de duración limitada que solo desbloquean determinadas partes del recurso específico para el que se solicitaron.

El script RemoteRenderingCoordinator tiene un delegado llamado ARRCredentialGetter, que contiene un método que devuelve un objeto SessionConfiguration que se usa para configurar la administración de sesiones remotas. Podemos asignar un método diferente a ARRCredentialGetter, lo que nos permite usar un flujo de inicio de sesión de Azure y generar un objeto de SessionConfiguration que contenga un token de acceso de Azure. Este token de acceso será específico para el usuario que inicia sesión.

  1. Siga los pasos que se describen en Cómo configurar la autenticación para aplicaciones implementadas, que implica registrar una nueva aplicación de Microsoft Entra y configurar el acceso a la instancia de ARR.

  2. Después de configurar la nueva aplicación Microsoft Entra, compruebe que tenga el aspecto de las imágenes siguientes:

    Aplicación de Microsoft Entra:> AutenticaciónApp authentication

    Aplicación de Microsoft Entra:> Permisos de APIApp APIs

  3. Después de configurar la cuenta de Remote Rendering, compruebe que la configuración se parece a la de la siguiente imagen:

    ARR:> AccessControl (IAM)ARR Role

    Nota:

    El rol Propietario no es suficiente para administrar sesiones mediante la aplicación cliente. A cada usuario al que quiera conceder la capacidad de administrar sesiones le debe proporcionar el rol Cliente de Remote Rendering. A cada usuario que quiera que administre las sesiones y convierta los modelos le debe proporcionar el rol Administrador de Remote Rendering.

Con el lado de Azure de las cosas en su lugar, ahora es necesario modificar cómo se conecta el código al servicio ARR. Para ello, se implementa una instancia de BaseARRAuthentication, que devuelve un nuevo objeto SessionConfiguration. En este caso, la información de la cuenta se configura con el token de acceso de Azure.

  1. Cree un script llamado AADAuthentication y reemplace su código por el siguiente:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Identity.Client;
    using System;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using UnityEngine;
    
    public class AADAuthentication : BaseARRAuthentication
    {
        [SerializeField]
        private string activeDirectoryApplicationClientID;
        public string ActiveDirectoryApplicationClientID
        {
            get => activeDirectoryApplicationClientID.Trim();
            set => activeDirectoryApplicationClientID = value;
        }
    
        [SerializeField]
        private string azureTenantID;
        public string AzureTenantID
        {
            get => azureTenantID.Trim();
            set => azureTenantID = value;
        }
    
        [SerializeField]
        private string azureRemoteRenderingDomain;
        public string AzureRemoteRenderingDomain
        {
            get => azureRemoteRenderingDomain.Trim();
            set => azureRemoteRenderingDomain = value;
        }
    
        [SerializeField]
        private string azureRemoteRenderingAccountID;
        public string AzureRemoteRenderingAccountID
        {
            get => azureRemoteRenderingAccountID.Trim();
            set => azureRemoteRenderingAccountID = value;
        }
    
        [SerializeField]
        private string azureRemoteRenderingAccountDomain;
        public string AzureRemoteRenderingAccountDomain
        {
            get => azureRemoteRenderingAccountDomain.Trim();
            set => azureRemoteRenderingAccountDomain = value;
        }    
    
        public override event Action<string> AuthenticationInstructions;
    
        string authority => "https://login.microsoftonline.com/" + AzureTenantID;
    
        string redirect_uri = "https://login.microsoftonline.com/common/oauth2/nativeclient";
    
        string[] scopes => new string[] { "https://sts.mixedreality.azure.com//.default" };
    
        public void OnEnable()
        {
            RemoteRenderingCoordinator.ARRCredentialGetter = GetARRCredentials;
            this.gameObject.AddComponent<ExecuteOnUnityThread>();
        }
    
        public async override Task<SessionConfiguration> GetARRCredentials()
        {
            var result = await TryLogin();
            if (result != null)
            {
                Debug.Log("Account signin successful " + result.Account.Username);
    
                var AD_Token = result.AccessToken;
    
                return await Task.FromResult(new SessionConfiguration(AzureRemoteRenderingAccountDomain, AzureRemoteRenderingDomain, AzureRemoteRenderingAccountID, "", AD_Token, ""));
            }
            else
            {
                Debug.LogError("Error logging in");
            }
            return default;
        }
    
        private Task DeviceCodeReturned(DeviceCodeResult deviceCodeDetails)
        {
            //Since everything in this task can happen on a different thread, invoke responses on the main Unity thread
            ExecuteOnUnityThread.Enqueue(() =>
            {
                // Display instructions to the user for how to authenticate in the browser
                Debug.Log(deviceCodeDetails.Message);
                AuthenticationInstructions?.Invoke(deviceCodeDetails.Message);
            });
    
            return Task.FromResult(0);
        }
    
        public override async Task<AuthenticationResult> TryLogin()
        {
            var clientApplication = PublicClientApplicationBuilder.Create(ActiveDirectoryApplicationClientID).WithAuthority(authority).WithRedirectUri(redirect_uri).Build();
            AuthenticationResult result = null;
            try
            {
                var accounts = await clientApplication.GetAccountsAsync();
    
                if (accounts.Any())
                {
                    result = await clientApplication.AcquireTokenSilent(scopes, accounts.First()).ExecuteAsync();
    
                    return result;
                }
                else
                {
                    try
                    {
                        result = await clientApplication.AcquireTokenWithDeviceCode(scopes, DeviceCodeReturned).ExecuteAsync(CancellationToken.None);
                        return result;
                    }
                    catch (MsalUiRequiredException ex)
                    {
                        Debug.LogError("MsalUiRequiredException");
                        Debug.LogException(ex);
                    }
                    catch (MsalServiceException ex)
                    {
                        Debug.LogError("MsalServiceException");
                        Debug.LogException(ex);
                    }
                    catch (MsalClientException ex)
                    {
                        Debug.LogError("MsalClientException");
                        Debug.LogException(ex);
                        // Mitigation: Use interactive authentication
                    }
                    catch (Exception ex)
                    {
                        Debug.LogError("Exception");
                        Debug.LogException(ex);
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.LogError("GetAccountsAsync");
                Debug.LogException(ex);
            }
    
            return null;
        }
    }
    

Nota

Este código no está completo y no está listo para uso comercial. Por ejemplo, como mínimo querrá tener también la posibilidad de cerrar la sesión. Para ello se puede usar el método Task RemoveAsync(IAccount account) proporcionado por la aplicación cliente. Este código está concebido para su uso en el tutorial; la implementación de cada usuario será específica de su aplicación.

Primero, el código intenta obtener el token de forma silenciosa mediante AquireTokenSilent. Esta acción se realiza correctamente si el usuario ha autenticado anteriormente esta aplicación. Si no, pase a una estrategia en la que intervenga más el usuario.

En este código, se usa el flujo de código de dispositivo para obtener un token de acceso. Este flujo permite que el usuario inicie sesión en su cuenta de Azure en un equipo o dispositivo móvil y que se envíe el token resultante de vuelta a la aplicación de HoloLens.

Desde la perspectiva de ARR, la parte más importante de esta clase es esta línea:

return await Task.FromResult(new SessionConfiguration(AzureRemoteRenderingAccountDomain, AzureRemoteRenderingDomain, AzureRemoteRenderingAccountID, "", AD_Token, ""));

Aquí, crearemos un nuevo objeto SessionConfiguration. Para ello, usaremos el dominio de Remote Rendering, el identificador de la cuenta, el dominio de la cuenta y el token de acceso. Luego, el servicio ARR usa este token para consultar, crear y combinar sesiones de representación remota siempre y cuando el usuario esté autorizado en función de los permisos basados en rol configurados anteriormente.

Con este cambio, el estado actual de la aplicación y su acceso a los recursos de Azure se asemejan a esta imagen:

Even better security

Puesto que las credenciales de usuario no se almacenan en el dispositivo (o, en este caso, ni siquiera se insertan en el dispositivo), el riesgo de exposición es bajo. Ahora, el dispositivo usa un token de acceso específico del usuario de tiempo limitado para acceder a ARR, que usa el control de acceso (IAM) para acceder a Blob Storage. En estos dos pasos se han eliminado las "contraseñas" del código fuente y ha aumentado la seguridad de manera considerable. Sin embargo, esta no es la mejor seguridad disponible; trasladar el modelo y la administración de sesiones a un servicio web mejorará aún más la seguridad. En el capítulo Preparación comercial se analizan otras cuestiones de seguridad.

Prueba de la autenticación de Microsoft Entra

En el Editor de Unity, cuando la autenticación de Microsoft Entra está activa, tiene que autenticarse cada vez que inicie la aplicación. En el dispositivo, el paso de autenticación se realiza la primera vez y solo se volverá a requerir cuando el token expire o se invalide.

  1. Agregue el componente de autenticación de Microsoft Entra al elemento GameObject RemoteRenderingCoordinator.

    Microsoft Entra auth component

Nota:

Si usa el proyecto completado del repositorio de ejemplos de ARR, asegúrese de habilitar el componente de autenticación de Microsoft Entra; para ello, haga clic en la casilla situada junto a su título.

  1. Rellene los valores de identificador de cliente e identificador de inquilino. Estos valores se pueden encontrar en la página de información general del registro de la aplicación:

    • Id. de cliente de la aplicación de Active Directory es el valor de Id. de aplicación (cliente) que se encuentra en el registro de la aplicación de Microsoft Entra (vea la imagen a continuación).
    • El valor de Id. de inquilino de Azure es el valor de Id. de directorio (inquilino) que se encuentra en el registro de la aplicación de Microsoft Entra (vea la imagen a continuación).
    • El valor de Azure Remote Rendering Domain (Dominio de Azure Remote Rendering) es el mismo que ha estado usando en el dominio de Remote Rendering de RemoteRenderingCoordinator.
    • El valor de Azure Remote Rendering Account ID (Id. de la cuenta de Azure Remote Rendering) es el mismo valor de Account ID (Id. de cuenta) que ha estado usando para RemoteRenderingCoordinator.
    • El valor de Azure Remote Rendering Account Domain (Dominio de cuenta de Azure Remote Rendering) coincide con el dominio de cuenta que ha estado usando en RemoteRenderingCoordinator.

    Screenshot that highlights the Application (client) ID and Directory (tenant) ID.

  2. Presione el botón de reproducción en el editor de Unity y dé su consentimiento a la ejecución de una sesión. Puesto que el componente de autenticación de Microsoft Entra tiene un controlador de vistas, se enlaza automáticamente para mostrar un aviso después del panel modal de autorización de la sesión.

  3. Siga las instrucciones que se encuentran en el panel a la derecha de AppMenu. Verá algo parecido a esto: Illustration that shows the instruction panel that appears to the right of the AppMenu.

    Después de escribir el código proporcionado en el dispositivo secundario (o en el explorador del mismo dispositivo) e iniciar sesión con sus credenciales, se devolverá un token de acceso a la aplicación que realiza la solicitud, en este caso, el editor de Unity.

Llegados a este punto, todas las fases de la aplicación proseguirán normalmente. Si no es así, compruebe la consola de Unity para ver si hay errores.

Compilación en el dispositivo

Si va a compilar una aplicación con MSAL en el dispositivo, debe incluir un archivo en la carpeta Assets(Recursos) del proyecto. De esta manera, el compilador puede compilar la aplicación correctamente mediante el archivo Microsoft.Identity.Client.dll incluido en los recursos del tutorial.

  1. Agregue un nuevo archivo en Assets (Recursos) llamado link.xml.

  2. Agregue al archivo lo siguiente:

    <linker>
        <assembly fullname="Microsoft.Identity.Client" preserve="all"/>
        <assembly fullname="System.Runtime.Serialization" preserve="all"/>
        <assembly fullname="System.Core">
            <type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
        </assembly>
    </linker>
    
  3. Guarde los cambios.

Siga los pasos que se describen en el Inicio rápido: Implementación del ejemplo de Unity en HoloLens: compilación del proyecto de ejemplo, para realizar la compilación en HoloLens.

Pasos siguientes

El resto de este conjunto de tutoriales contiene artículos conceptuales para crear una aplicación lista para producción que usa Azure Remote Rendering.