Esercitazione: Protezione dell'archiviazione di modelli e Rendering remoto di Azure

In questa esercitazione apprenderai a:

  • Proteggere l'istanza di Archiviazione BLOB di Azure contenente i modelli di Rendering remoto di Azure
  • Eseguire l'autenticazione con Microsoft Entra ID per accedere all'istanza di Azure Rendering remoto
  • Usare le credenziali di Azure per l'autenticazione di Rendering remoto di Azure

Prerequisiti

Perché è necessaria una maggiore sicurezza

Lo stato corrente dell'applicazione e il relativo accesso alle risorse di Azure sono come segue:

Initial security

"ID account + chiave account" e "URL + token di firma di accesso condiviso" essenzialmente nell'insieme archiviano un nome utente e una password. Se ad esempio i valori di "ID account + chiave account" venissero esposti, un utente malintenzionato potrebbe facilmente usare le risorse di Rendering remoto di Azure senza il consenso dell'utente e a sue spese.

Protezione del contenuto in Archiviazione BLOB di Azure

Rendering remoto di Azure può accedere in sicurezza al contenuto di Archiviazione BLOB di Azure con la corretta configurazione. Vedere Procedura: Collegare gli account di archiviazione per configurare l'istanza di Azure Rendering remoto con gli account di archiviazione BLOB.

Quando si usa un archivio BLOB collegato, si usano metodi leggermente diversi per il caricamento dei modelli:

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

Le righe sopra riportate usano la versione FromSas dell'azione di parametri e sessione. Devono essere convertite nelle versioni non di firma di accesso condiviso:

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

Modificare RemoteRenderingCoordinator per caricare un modello personalizzato, da un account di archiviazione BLOB collegato.

  1. Se non è già stato fatto, completare la procedura: Collegare gli account di archiviazione per concedere all'istanza di ARR l'autorizzazione per accedere all'istanza di BLOB Archiviazione.

  2. Aggiungere il seguente metodo LoadModel modificato a RemoteRenderingCoordinator, subito sotto il metodo LoadModel corrente:

    /// <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;
    }
    

    Questo codice è identico al metodo originale LoadModel , ma è stata sostituita la versione SAS delle chiamate al metodo con le versioni non sas.

    Gli input aggiuntivi storageAccountName e blobName sono stati aggiunti anche agli argomenti. Questo nuovo metodo LoadModel viene chiamato da un altro metodo simile al primo metodo LoadTestModel creato nella prima esercitazione.

  3. Aggiungere il metodo seguente a RemoteRenderingCoordinator subito dopo 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)}%"));
    }
    

    Questo codice aggiunge tre variabili stringa aggiuntive al componente RemoteRenderingCoordinator . Screenshot that highlights the Storage Account Name, Blob Container Name, and Model Path of the RemoteRenderingCoordinator component.

  4. Aggiungere i valori al componente RemoteRenderingCoordinator. Dopo aver seguito l'argomento di avvio rapido per la conversione del modello, i valori saranno:

    • Archiviazione Nome account: nome dell'account di archiviazione, nome univoco globale scelto per l'account di archiviazione. Nell'argomento di avvio rapido si tratta di arrtutorialstorage, il valore è diverso.
    • Nome del contenitore BLOB: arroutput, ovvero il contenitore di archiviazione BLOB
    • Percorso modello: combinazione di "outputFolderPath" e "outputAssetFileName" definiti nel file arrconfig.json . Nella guida introduttiva si tratta di "outputFolderPath":"convert/robot", "outputAssetFileName": "robot.arrAsset". Il valore risultante sarà "convert/robot/robot.arrAsset", il valore è diverso.

    Suggerimento

    Se si esegue lo script Conversion. ps1, senza l'argomento "-UseContainerSas", verranno restituiti tutti i valori indicati sopra invece del token di firma di accesso condiviso. Linked Model

  5. Per il momento, rimuovere o disabilitare il GameObject TestModel per consentire il caricamento del modello personalizzato.

  6. Riprodurre la scena e connettersi a una sessione remota.

  7. Aprire il menu di scelta rapida in RemoteRenderingCoordinator e selezionare Carica modello personalizzato collegato. Load linked model

Questa procedura ha aumentato la sicurezza dell'applicazione rimuovendo il token di firma di accesso condiviso dall'applicazione locale.

Lo stato corrente dell'applicazione e il relativo accesso alle risorse di Azure sono ora come segue:

Better security

È presente un'altra password, la chiave dell'account, da rimuovere dall'applicazione locale. Questa operazione può essere eseguita usando l'autenticazione di Microsoft Entra.

Autenticazione Microsoft Entra

L'autenticazione di Microsoft Entra consente di determinare quali utenti o gruppi usano ARR in modo più controllato. Rendering remoto di Azure prevede il supporto predefinito per accettare token di accesso invece dell'uso di una chiave dell'account. I token di accesso possono essere paragonati a una chiave specifica dell'utente, limitata nel tempo, che sblocca solo determinate parti della risorsa specifica per cui è stata richiesta.

Lo script RemoteRenderingCoordinator ha un delegato denominato ARRCredentialGetter, che contiene un metodo che restituisce un oggetto SessionConfiguration , che viene usato per configurare la gestione della sessione remota. È possibile assegnare un metodo diverso a ARRCredentialGetter, consentendo di usare un flusso di accesso di Azure, generando un oggetto SessionConfiguration che contiene un token di accesso di Azure. Questo token di accesso sarà specifico per l'utente che effettua l'accesso.

  1. Seguire la procedura : Configurare l'autenticazione - Autenticazione per le applicazioni distribuite, che comporta la registrazione di una nuova applicazione Microsoft Entra e la configurazione dell'accesso all'istanza di ARR.

  2. Dopo aver configurato la nuova applicazione Microsoft Entra, verificare che l'applicazione Microsoft Entra abbia un aspetto simile alle immagini seguenti:

    Applicazione Microsoft Entra -> AutenticazioneApp authentication

    Applicazione Microsoft Entra -> Autorizzazioni APIApp APIs

  3. Dopo aver configurato l'account di Rendering remoto, verificare che la configurazione corrisponda all'immagine seguente:

    ARR -> AccessControl (IAM)ARR Role

    Nota

    Un ruolo di Proprietario non è sufficiente per gestire le sessioni tramite l'applicazione client. A ogni utente a cui si vuole concedere la possibilità di gestire le sessioni è necessario fornire il ruolo di client di Rendering remoto. A ogni utente che dovrà gestire le sessioni e convertire i modelli è necessario fornire il ruolo di amministratore di Rendering remoto.

Con il lato Azure degli elementi disponibili, è ora necessario modificare il modo in cui il codice si connette al servizio ARR. A tale scopo, implementando un'istanza di BaseARRAuthentication, che restituisce un nuovo oggetto SessionConfiguration . In questo caso, le informazioni sull'account vengono configurate con il token di accesso di Azure.

  1. Creare un nuovo script denominato AADAuthentication e sostituirne il codice con quello seguente:

    // 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

Questo codice non è completo e non è pronto per un'applicazione commerciale. Come minimo, ad esempio, si dovrà aggiungere anche la possibilità di disconnettersi. A questo scopo, è possibile usare il metodo Task RemoveAsync(IAccount account) fornito dall'applicazione client. Questo codice è destinato esclusivamente all'uso nell'esercitazione, mentre la propria implementazione sarà specifica per l'applicazione.

Il codice prova prima di tutto a ottenere il token in modo invisibile all'utente usando AquireTokenSilent. Questa operazione ha esito positivo se l'utente ha precedentemente autenticato l'applicazione. Se non riesce, passare a una strategia che coinvolge maggiormente l'utente.

Per questo codice viene usato il flusso di codice del dispositivo per ottenere un token di accesso. Questo flusso consente all'utente di accedere al suo account Azure in un computer o in un dispositivo mobile e di ricevere il token risultante nell'applicazione HoloLens.

La parte più importante di questa classe dal punto di vista di Rendering remoto di Azure è questa riga:

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

In questo caso viene creato un nuovo oggetto SessionConfiguration usando il dominio di rendering remoto, l'ID account, il dominio dell'account e il token di accesso. Questo token viene quindi usato dal servizio Rendering remoto di Azure per eseguire query, creare e partecipare alle sessioni di rendering remoto, purché l'utente sia autorizzato in base alle autorizzazioni basate sul ruolo configurate in precedenza.

Con questa modifica, lo stato corrente dell'applicazione e il relativo accesso alle risorse di Azure sono come segue:

Even better security

Poiché le credenziali utente non vengono archiviate nel dispositivo (o in questo caso anche immesse nel dispositivo), il rischio di esposizione è basso. A questo punto il dispositivo usa un token di accesso specifico dell'utente e limitato nel tempo per accedere a Rendering remoto di Azure, che usa il controllo di accesso (IAM) per accedere all'account di archiviazione BLOB. Questi due passaggi hanno rimosso le "password" dal codice sorgente e una maggiore sicurezza. Tuttavia, non si tratta del livello massimo di sicurezza disponibile. Spostando la gestione di modelli e sessioni in un servizio Web sarà possibile migliorare ulteriormente la sicurezza. Le considerazioni aggiuntive sulla sicurezza sono illustrate nel capitolo Preparazione commerciale.

Test dell'autenticazione di Microsoft Entra

Nell'editor di Unity, quando l'autenticazione di Microsoft Entra è attiva, è necessario eseguire l'autenticazione ogni volta che si avvia l'applicazione. Nel dispositivo, il passaggio di autenticazione viene eseguito la prima volta e viene richiesto di nuovo solo quando il token scade o viene invalidato.

  1. Aggiungere il componente di autenticazione Microsoft Entra a RemoteRenderingCoordinator GameObject.

    Microsoft Entra auth component

Nota

Se si usa il progetto completato dal repository degli esempi ARR, assicurarsi di abilitare il componente di autenticazione Microsoft Entra facendo clic sulla casella di controllo accanto al relativo titolo.

  1. Immettere i valori per ID client e ID tenant. Questi valori sono disponibili nella pagina Panoramica della registrazione dell'app:

    • L'ID client dell'applicazione Active Directory è l'ID applicazione (client) disponibile nella registrazione dell'app Microsoft Entra (vedere l'immagine seguente).
    • L'ID tenant di Azure è l'IDdirectory (tenant) disponibile nella registrazione dell'app Microsoft Entra (vedere l'immagine seguente).
    • Azure Rendering remoto Dominio è lo stesso dominio usato nel dominio di Rendering remoto remoteRenderingCoordinator.
    • L'ID account di Rendering remoto di Azure è lo stesso ID account usato per RemoteRenderingCoordinator.
    • Il dominio account di Azure Rendering remoto è lo stesso dominio account usato in RemoteRenderingCoordinator.

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

  2. Premere Play nell'editor di Unity e fornire il consenso per l'esecuzione di una sessione. Poiché il componente di autenticazione Di Microsoft Entra ha un controller di visualizzazione, viene collegato automaticamente per visualizzare un prompt dopo il pannello modale di autorizzazione della sessione.

  3. Seguire le istruzioni disponibili nel pannello a destra di AppMenu. Verrà visualizzato un aspetto simile al seguente: Illustration that shows the instruction panel that appears to the right of the AppMenu.

    Dopo aver immesso il codice fornito nel dispositivo secondario (o nel browser nello stesso dispositivo) e aver eseguito l'accesso usando le credenziali, verrà restituito un token di accesso all'applicazione richiedente, in questo caso l'editor di Unity.

A questo punto, tutti gli elementi dell'applicazione dovrebbero procedere normalmente. Se non si procede attraverso le fasi come previsto, verificare la presenza di eventuali errori nella console di Unity.

Compilare nel dispositivo

Se si compila un'applicazione usando MSAL per dispositivo, è necessario includere un file nella cartella Assets del progetto. Ciò consente al compilatore di compilare correttamente l'applicazione usando Microsoft.Identity.Client.dll incluso in Asset dell'esercitazione.

  1. Aggiungere un nuovo file in Assets denominato link.xml

  2. Aggiungere quanto segue per il file:

    <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. Salvare le modifiche

Seguire la procedura descritta in Avvio rapido: Distribuire l'esempio unity in HoloLens - Compilare il progetto di esempio per compilare in HoloLens.

Passaggi successivi

La parte restante di questo set di esercitazioni contiene articoli concettuali per la creazione di un'applicazione pronta per la produzione che usa Azure Rendering remoto.