Tutorial: Media Services trusted storage

Media Services logo v3


Warning

Azure Media Services will be retired June 30th, 2024. For more information, see the AMS Retirement Guide.

In this tutorial, you'll learn:

  • How to enable trusted storage for Azure Media Services
  • How to use Managed Identities for trusted storage
  • How to give Azure Services to access to a storage account when using network access control such as a firewall or VPN

With the 2020-05-01 API, you can enable trusted storage by associating a Managed Identity with a Media Services account.

Note

Trusted storage is only available in the API, and is not currently enabled in the Azure portal.

Media Services can automatically access your storage account using system authentication. Media Services validates that the user adding the association has access the storage account with Azure Resource Manager RBAC.

Usage of cross-subscription storage accounts

Note

When Media Services is configured to use Managed Identity to access storage, Media Services can use any storage account that the Managed Identity can access.

When using System authentication to storage, the storage account must be in the same subscription as the Media Services account. Use storage accounts in the same region as the Media Services account to avoid additional data egress costs.

For both authentication types, the principal that creates or updates the Media Services account must have the 'Microsoft.Storage/storageAccounts/listkeys/action' permission over the storage account.

Overview

Important

Use the 2020-05-01 API for all requests to Media Services.

These are the general steps for creating trusted storage for Media Services:

  1. Create a resource group.
  2. Create a storage account.
  3. Poll the storage account until it's ready. When the storage account is ready, the request will return the service principal ID.
  4. Find the ID of the Storage Blob Data Contributor role.
  5. Call the authorization provider and add a role assignment.
  6. Update the media services account to authenticate to the storage account using Managed Identity.
  7. Delete the resources if you don't want to continue to use them and be charged for them.

Prerequisites

You need an Azure subscription to get started. If you don't have an Azure subscription, create a trial account.

Get your tenant ID and subscription ID

If you don't know how to get your tenant ID and subscription ID, see How to find your subscription and tenant ID.

Create a service principal and secret

If you don't know how to create a service principal and secret, see Get credentials to access Media Services API.

Use a REST client

This script is intended for use with a REST client such as what is available in Visual Studio code extensions. Adapt it for your development environment.

Set initial variables

This part of the script is for use in a REST client. You may use variables differently within your development environment.

### AAD details
@tenantId = your tenant ID
@servicePrincipalId = the service principal ID
@servicePrincipalSecret = the service principal secret

### AAD resources
@armResource = https%3A%2F%2Fmanagement.core.chinacloudapi.cn%2F
@graphResource = https%3A%2F%2Fgraph.chinacloudapi.cn%2F
@storageResource = https%3A%2F%2Fstorage.azure.com%2F

### Service endpoints
@armEndpoint = management.chinacloudapi.cn
@graphEndpoint = graph.chinacloudapi.cn
@aadEndpoint = login.partner.microsoftonline.cn

### ARM details
@subscription = your subscription id
@resourceGroup = the resource group you'll be creating
@storageName = the name of the storage you'll be creating
@accountName = the name of the account you'll be creating
@resourceLocation = China East 2 (or the location that works best for your region)

Get a token for Azure Resource Manager

// @name getArmToken
POST https://{{aadEndpoint}}/{{tenantId}}/oauth2/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded

resource={{armResource}}&client_id={{servicePrincipalId}}&client_secret={{servicePrincipalSecret}}&grant_type=client_credentials

Get a token for the Graph API

This part of the script is for use in a REST client. You may use variables differently within your development environment.

// @name getGraphToken
POST https://{{aadEndpoint}}/{{tenantId}}/oauth2/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded

resource={{graphResource}}&client_id={{servicePrincipalId}}&client_secret={{servicePrincipalSecret}}&grant_type=client_credentials

Get the service principal details

// @name getServicePrincipals
GET https://{{graphEndpoint}}/{{tenantId}}/servicePrincipals?$filter=appId%20eq%20'{{servicePrincipalId}}'&api-version=1.6
x-ms-client-request-id: cae3e4f7-17a0-476a-a05a-0dab934ba959
Authorization:  Bearer {{getGraphToken.response.body.access_token}}

Store the service principal ID

@servicePrincipalObjectId = {{getServicePrincipals.response.body.value[0].objectId}}

Create a resource group

// @name createResourceGroup
PUT https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}
    ?api-version=2016-09-01
Authorization: Bearer {{getArmToken.response.body.access_token}}
Content-Type: application/json; charset=utf-8

{
    "location": "{{resourceLocation}}"
}

Create storage account

// @name createStorageAccount
PUT https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Storage/storageAccounts/{{storageName}}
    ?api-version=2019-06-01
Authorization: Bearer {{getArmToken.response.body.access_token}}
Content-Type: application/json; charset=utf-8

{
    "sku": {
    "name": "Standard_GRS"
    },
    "kind": "StorageV2",
    "location": "{{resourceLocation}}",
    "properties": {
    }
}

Get the storage account status

The storage account will take a while to be ready so this request polls for its status. Repeat this request until the storage account is ready.

// @name getStorageAccountStatus
GET {{createStorageAccount.response.headers.Location}}
Authorization: Bearer {{getArmToken.response.body.access_token}}

Get the storage account details

When the storage account is ready, get the properties of the storage account.

// @name getStorageAccount
GET https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Storage/storageAccounts/{{storageName}}
    ?api-version=2019-06-01
Authorization: Bearer {{getArmToken.response.body.access_token}}

Get a token for ARM

// @name getStorageToken
POST https://{{aadEndpoint}}/{{tenantId}}/oauth2/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded

resource={{storageResource}}&client_id={{servicePrincipalId}}&client_secret={{servicePrincipalSecret}}&grant_type=client_credentials

Create a Media Services account with a system-assigned managed identity

// @name createMediaServicesAccount
PUT https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Media/mediaservices/{{accountName}}?api-version=2020-05-01
Authorization: Bearer {{getArmToken.response.body.access_token}}
Content-Type: application/json; charset=utf-8

{
  "identity": {
      "type": "SystemAssigned"
  },
  "properties": {
    "storageAccounts": [
      {
        "id": "{{getStorageAccountStatus.response.body.id}}"
      }
    ],
    "encryption": {
      "type": "SystemKey"
    }
  },
  "location": "{{resourceLocation}}"
}

Get the storage Storage Blob Data role definition

// @name getStorageBlobDataContributorRoleDefinition
GET https://management.chinacloudapi.cn/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Storage/storageAccounts/{{storageName}}/providers/Microsoft.Authorization/roleDefinitions?$filter=roleName%20eq%20%27Storage%20Blob%20Data%20Contributor%27&api-version=2015-07-01
Authorization: Bearer {{getArmToken.response.body.access_token}}

Set the storage role assignment

The role assignment says that the service principal for the Media Services account has the storage role Storage Blob Data Contributor. This may take a while and it's important to wait or the Media Services account won't be set up correctly.

PUT https://management.chinacloudapi.cn/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Storage/storageAccounts/{{storageName}}/providers/Microsoft.Authorization/roleAssignments/{{$guid}}?api-version=2020-04-01-preview
Authorization: Bearer {{getArmToken.response.body.access_token}}
Content-Type: application/json; charset=utf-8

{
  "properties": {
    "roleDefinitionId": "/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Storage/storageAccounts/{{storageName}}/providers/Microsoft.Authorization/roleDefinitions/{{getStorageBlobDataContributorRoleDefinition.response.body.value[0].name}}",
    "principalId": "{{createMediaServicesAccount.response.body.identity.principalId}}"
  }
}

Give Managed Identity bypass access to the storage account

This action changes the access from system-managed identity to the Managed Identity. In this way, the storage account can access the storage account through a firewall as Azure services can access the storage account regardless of IP access rules (ACLs).

Again, wait until the role has been assigned in the storage account, or the Media Services account will be set up incorrectly.

// @name setStorageAccountFirewall
PUT https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Storage/storageAccounts/{{storageName}}
    ?api-version=2019-06-01
Authorization: Bearer {{getArmToken.response.body.access_token}}
Content-Type: application/json; charset=utf-8

{
    "sku": {
    "name": "Standard_GRS"
    },
    "kind": "StorageV2",
    "location": "{{resourceLocation}}",
    "properties": {
      "minimumTlsVersion": "TLS1_2",
      "networkAcls": {
        "bypass": "AzureServices",
        "virtualNetworkRules": [],
        "ipRules": [],
        "defaultAction": "Deny"
      }
    }
}

Update the Media Services account to use the Managed Identity

This request may need to be retried a few times as the storage role assignment can take a few minutes to propagate.

// @name updateMediaServicesAccountWithManagedStorageAuth
PUT https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Media/mediaservices/{{accountName}}?api-version=2020-05-01
Authorization: Bearer {{getArmToken.response.body.access_token}}
Content-Type: application/json; charset=utf-8

{
  "identity": {
      "type": "SystemAssigned"
  },
  "properties": {
    "storageAccounts": [
      {
        "id": "{{getStorageAccountStatus.response.body.id}}"
      }
    ],
    "storageAuthentication": "ManagedIdentity",
    "encryption": {
      "type": "SystemKey"
    }
  },
  "location": "{{resourceLocation}}"
}

Test access

Test access by creating an asset in the storage account.

// @name createAsset
PUT https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Media/mediaservices/{{accountName}}/assets/testasset{{index}}withoutmi?api-version=2018-07-01
Authorization: Bearer {{getArmToken.response.body.access_token}}
Content-Type: application/json; charset=utf-8

{
}

Delete resources

If you don't want to keep the resources that you created and continue to be charged for them, delete them.

### Clean up the Storage account
DELETE https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Storage/storageAccounts/{{storageName}}
    ?api-version=2019-06-01
Authorization: Bearer {{getArmToken.response.body.access_token}}

### Clean up the Media Services account
DELETE https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Media/mediaservices/{{accountName}}?api-version=2020-05-01
Authorization: Bearer {{getArmToken.response.body.access_token}}

### Clean up the Media Services account
GET https://{{armEndpoint}}/subscriptions/{{subscription}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Media/mediaservices/{{accountName}}?api-version=2020-05-01
Authorization: Bearer {{getArmToken.response.body.access_token}}