Руководство по защите azure Удаленная отрисовка и хранилища моделей

В этом руководстве описано следующее:

  • Защита Хранилища BLOB-объектов Azure, содержащего модели Удаленной отрисовки Azure
  • Проверка подлинности с помощью идентификатора Microsoft Entra для доступа к экземпляру Azure Удаленная отрисовка
  • Использование учетных данных Azure для проверки подлинности в службе "Удаленная отрисовка Azure"

Необходимые компоненты

Почему требуется дополнительная безопасность

Текущее состояние приложения и его доступ к ресурсам Azure выглядят следующим образом:

Initial security

Какая бы схема ни использовалась, "AccountID + AccountKey" или "URL-адрес + маркер SAS", имя пользователя и пароль, по сути, хранятся вместе. Например, если предоставляется "AccountID + AccountKey", злоумышленник сможет с легкостью использовать ваши ресурсы Удаленной отрисовки Azure без вашего разрешения за ваш счет.

Защита содержимого в Хранилище BLOB-объектов Azure

При правильной настройке доступ службы "Удаленная отрисовка Azure" к содержимому Хранилища BLOB-объектов Azure будет безопасен. См. практическое руководство. Связывание учетных записей хранения для настройки экземпляра Azure Удаленная отрисовка с учетными записями хранения BLOB-объектов.

При использовании связанного хранилища BLOB-объектов для загрузки моделей используются несколько разные методы:

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

В приведенных выше строках используется версия FromSas действия params и действия session. Их необходимо преобразовать в версии, не связанные с SAS.

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

Давайте изменим RemoteRenderingCoordinator, чтобы загрузить настраиваемую модель из связанной учетной записи Хранилища BLOB-объектов.

  1. Если вы еще не сделали этого, выполните инструкции по связыванию учетных записей хранения, чтобы предоставить экземплярУ ARR разрешение на доступ к экземпляру BLOB-объектов служба хранилища.

  2. Добавьте следующий измененный метод LoadModel в RemoteRenderingCoordinator непосредственно под текущим методом LoadModel.

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

    Этот код идентичен исходному LoadModel методу, однако мы заменили версию SAS метода на версии, отличной от SAS.

    Дополнительные входные storageAccountName данные и blobName также были добавлены к аргументам. Мы вызываем этот новый метод LoadModel из другого метода, аналогичного первому методу LoadTestModel, созданному в первом руководстве.

  3. Добавьте следующий метод в RemoteRenderingCoordinator непосредственно после 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)}%"));
    }
    

    Этот код добавляет три дополнительных строковых переменных в компонент RemoteRenderingCoordinator . Screenshot that highlights the Storage Account Name, Blob Container Name, and Model Path of the RemoteRenderingCoordinator component.

  4. Добавьте значения в компонент RemoteRenderingCoordinator. После выполнения инструкций в кратком руководстве по преобразованию модели значения должны быть такими:

    • служба хранилища имя учетной записи хранения: имя учетной записи хранения, глобально уникальное имя, выбранное для учетной записи хранения. В кратком руководстве это было arrtutorialstorage, ваше значение отличается.
    • Имя контейнера BLOB-объектов. Для контейнера Хранилища BLOB-объектов используется arroutput.
    • Путь модели: сочетание outputFolderPath и outputAssetFileName, определенного в файле arrconfig.json . В кратком руководстве это было "outputFolderPath":"converted/robot", "outputAssetFileName": "robot.arrAsset". Что приведет к значению пути модели "converted/robot/robot.arrAsset", ваше значение отличается.

    Совет

    Если вы выполните скрипт Conversion.ps1 без аргумента "-UseContainerSas", он выведет все указанные выше значения вместо маркера SAS. Linked Model

  5. Удалите или отключите на время игровой объект TestModel, чтобы освободить место для загрузки настраиваемой модели.

  6. Воспроизведите сцену и подключитесь к удаленному сеансу.

  7. Откройте контекстное меню в RemoteRenderingCoordinator и выберите "Загрузить связанную пользовательскую модель". Load linked model

После выполнения этих действий уровень безопасности приложения повысился за счет удаления маркера SAS из локального приложения.

Теперь текущее состояние приложения и его доступ к ресурсам Azure выглядят следующим образом:

Better security

Нам нужно удалить из локального приложения еще один "пароль", AccountKey. Это можно сделать с помощью проверки подлинности Microsoft Entra.

Проверка подлинности Microsoft Entra

Проверка подлинности Microsoft Entra позволяет определить, какие лица или группы используют ARR более контролируемым способом. Вместо использования ключа учетной записи служба "Удаленная отрисовка Azure" может принимать маркеры доступа. Маркеры доступа можно рассматривать как ключ конкретного пользователя с ограниченным сроком действия, который разблокирует только определенные части конкретного ресурса, для которого он был запрошен.

В скрипте RemoteRenderingCoordinator есть делегат с именем ARRCredentialGetter. Он содержит метод, который возвращает объект SessionConfiguration, который, в свою очередь, используется для настройки удаленного управления сеансами. Этому делегату ARRCredentialGetter можно назначить другой метод. Это позволит использовать поток входа Azure, создающий объект SessionConfiguration, который содержит маркер доступа Azure. Этот маркер доступа будет действовать только для пользователя, который выполнил вход.

  1. Следуйте инструкциям. Настройка проверки подлинности — проверка подлинности для развернутых приложений, которая включает регистрацию нового приложения Microsoft Entra и настройку доступа к экземпляру ARR.

  2. После настройки нового приложения Microsoft Entra проверка приложение Microsoft Entra выглядит следующим образом:

    Приложение Microsoft Entra —> проверка подлинностиApp authentication

    Приложение Microsoft Entra —> разрешения APIApp APIs

  3. После настройки учетной записи Удаленной отрисовки убедитесь, что ваша конфигурацию выглядит так, как на следующем рисунке:

    ARR —> AccessControl (IAM)ARR Role

    Примечание.

    Для управления сеансами через клиентское приложение роли владельца недостаточно. Каждому пользователю, которому необходимо предоставить возможность управления сеансами, следует назначить роль клиента Удаленной отрисовки. Каждому пользователю, которому необходимо управлять сеансами и преобразовывать модели, следует назначить роль администратора Удаленной отрисовки.

Теперь необходимо изменить способ подключения кода к службе ARR. Для этого мы реализуем экземпляр BaseARRAuthentication, который возвращает новый объект SessionConfiguration. В этом случае сведения об учетной записи настраиваются с помощью маркера доступа Azure.

  1. Создайте скрипт с именем AADAuthentication и замените его код следующим:

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

Примечание.

Этот код не является полным и не готов к использованию в коммерческой программе. Например, вам, вероятно, потребуется добавить как минимум возможность выхода из нее. Это можно сделать с помощью метода Task RemoveAsync(IAccount account), предоставляемого клиентским приложением. Этот код предназначен только для учебных целей. У вас будет своя реализация, характерная для вашего приложения.

Код сначала пытается получить маркер автоматически с помощью AquireTokenSilent. Это происходит успешно, если пользователь ранее прошел проверку подлинности этого приложения. Если это не так, перейдем к более сложной стратегии, требующей участия пользователя.

В этом коде для получения маркера доступа мы используем поток кода устройства. Этот поток позволяет пользователю войти в свою учетную запись Azure на компьютере или мобильном устройстве, после чего приложению HoloLens будет возвращен полученный в итоге маркер.

Наиболее важной частью этого класса с точки зрения Удаленной отрисовки Azure является эта строка:

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

Здесь мы создаем новый объект SessionConfiguration, используя домен удаленной обработки, идентификатор учетной записи, домен учетной записи и маркер доступа. Этот маркер затем используется службой "Удаленная отрисовка Azure" для запроса, создания и объединения сеансов удаленной отрисовки при условии, что пользователь авторизован в соответствии с разрешениями на основе ролей, настроенными ранее.

После этих изменений текущее состояние приложения и его доступ к ресурсам Azure выглядят следующим образом:

Even better security

Так как учетные данные пользователя не хранятся на устройстве (или в этом случае даже введены на устройстве), их риск воздействия не является низким. Теперь устройство использует маркер доступа конкретного пользователя с ограниченным сроком действия для доступа к службе "Удаленная отрисовка Azure", которая, в свою очередь, использует управление доступом (IAM) для доступа к Хранилищу BLOB-объектов. Эти два шага удалили "пароли" из исходного кода и значительно увеличили безопасность. Тем не менее это не самый безопасный способ защиты. Перемещение модели и управления сеансами в веб-службу позволит обеспечить еще более надежную защиту. Дополнительные вопросы безопасности рассматриваются в главе о готовности к коммерческой работе .

Тестирование проверки подлинности Microsoft Entra

В редакторе Unity при активной проверке подлинности Microsoft Entra необходимо пройти проверку подлинности при каждом запуске приложения. На устройстве шаг проверки подлинности выполняется в первый раз и требуется повторно, когда срок действия маркера истекает или является недействительным.

  1. Добавьте компонент проверки подлинности Microsoft Entra в RemoteRenderingCoordinator GameObject.

    Microsoft Entra auth component

Примечание.

Если вы используете завершенный проект из репозитория примеров ARR, обязательно включите компонент проверки подлинности Microsoft Entra, щелкнув поле проверка box рядом с его заголовком.

  1. Введите значения в поля идентификатора клиента и идентификатора арендатора. Эти значения можно найти на странице обзора регистрации приложения.

    • Идентификатор клиента приложения Active Directory — это идентификатор приложения (клиента), найденный в регистрации приложения Microsoft Entra (см. изображение ниже).
    • Идентификатор клиента Azure — это идентификатор каталога (клиента), найденный в регистрации приложения Microsoft Entra (см. изображение ниже).
    • Домен удаленной отрисовки Azure представляет собой тот же домен, который вы использовали в домене удаленной отрисовки RemoteRenderingCoordinator.
    • Azure Remote Rendering Account ID (Идентификатор учетной записи Удаленной отрисовки Azure) — это тот же идентификатор учетной записи, который вы использовали для RemoteRenderingCoordinator.
    • Домен учетной записи удаленной отрисовки Azure представляет собой тот же домен учетной записи, который вы использовали в RemoteRenderingCoordinator.

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

  2. Нажмите кнопку воспроизведения в редакторе Unity и дайте согласие на выполнение сеанса. Так как компонент проверки подлинности Microsoft Entra имеет контроллер представления, он автоматически подключается к отображению запроса после модальной панели авторизации сеанса.

  3. Следуйте инструкциям, приведенным на панели справа от AppMenu. Вы должны увидеть примерно следующее: Illustration that shows the instruction panel that appears to the right of the AppMenu.

    После ввода указанного кода на дополнительном устройстве (или в браузере на том же устройстве) и входа с использованием ваших учетных данных запрашивающему приложению (в данном случае редактору Unity) будет возвращен маркер доступа.

После этого все функции приложения должны работать надлежащим образом. Проверьте наличие ошибок в консоли Unity, если вам не удалось выполнить эти этапы так, как ожидалось.

Сборка на устройстве

Если вы создаете приложение с помощью MSAL для устройства, необходимо включить файл в папку ресурсов проекта. Это помогает компилятору правильно создавать приложение с помощью библиотеки Microsoft.Identity.Client.dll , включенной в ресурсы учебника.

  1. Добавьте в папку Assets новый файл с именем link.xml.

  2. Добавьте в файл следующий код:

    <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. Сохранить изменения

Выполните действия, описанные в кратком руководстве. Развертывание примера Unity в HoloLens — создание примера проекта для сборки в HoloLens.

Следующие шаги

Оставшаяся часть этого набора учебников содержит концептуальные статьи для создания готового к работе приложения, использующего Azure Удаленная отрисовка.