Skip to content

Commit

Permalink
Support for static encryption of the content.
Browse files Browse the repository at this point in the history
Add options to enabled encryption of the content.
Fixes #98.
Remove shaka packager messages from the console (Still logged in the
file)
  • Loading branch information
duggaraju committed Aug 2, 2023
1 parent 3dd8b75 commit 8c01001
Show file tree
Hide file tree
Showing 22 changed files with 195 additions and 77 deletions.
35 changes: 8 additions & 27 deletions AMSMigrate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<PackageProjectUrl>https://github.com/Azure/azure-media-migration</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Azure/azure-media-migration</RepositoryUrl>
<ShakaUrl>https://github.com/shaka-project/shaka-packager/releases/download/v2.6.1/</ShakaUrl>
<Company>Microsoft Corporation</Company>
</PropertyGroup>

<ItemGroup>
Expand All @@ -37,43 +39,22 @@
<PackageReference Include="System.Threading.Channels" Version="7.0.0" />
<PackageReference Include="vertical-spectreconsolelogger" Version="0.10.1-dev.20230712.19" />
</ItemGroup>

<ItemGroup>
<None Update="LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Update="packager-linux-x64">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<PackagePath>tools\$(TargetFramework)\any\%(Filename)%(Extension)</PackagePath>
</None>
<None Update="packager-osx-x64">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<PackagePath>tools\$(TargetFramework)\any\%(Filename)%(Extension)</PackagePath>
</None>
<None Update="packager-win-x64.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<PackagePath>tools\$(TargetFramework)\any\%(Filename)%(Extension)</PackagePath>
</None>
<None Update="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<ShakaPackager Include="packager-linux-x64;packager-osx-x64;packager-win-x64.exe" />
<None Update="@(ShakaPackager)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<PropertyGroup>
<ShakaWin>https://github.com/shaka-project/shaka-packager/releases/download/v2.6.1/packager-win-x64.exe</ShakaWin>
<ShakaLinux>https://github.com/shaka-project/shaka-packager/releases/download/v2.6.1/packager-linux-x64</ShakaLinux>
<ShakaOSX>https://github.com/shaka-project/shaka-packager/releases/download/v2.6.1/packager-osx-x64</ShakaOSX>
<Company>Microsoft Corporation</Company>
</PropertyGroup>
<Target Name="DownloadShakaPackager" BeforeTargets="Build">
<DownloadFile SourceUrl="$(ShakaWin)" DestinationFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="DownloadedFile" ItemName="Content" />
</DownloadFile>
<DownloadFile SourceUrl="$(ShakaLinux)" DestinationFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="DownloadedFile" ItemName="Content" />
</DownloadFile>
<DownloadFile SourceUrl="$(ShakaOSX)" DestinationFolder="$(MSBuildProjectDirectory)">
<Target Name="DownloadShakaPackager" BeforeTargets="Build" Outputs="@(ShakaPackager)">
<DownloadFile SourceUrl="$(ShakaUrl)%(ShakaPackager.Identity)" DestinationFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="DownloadedFile" ItemName="Content" />
</DownloadFile>
<Exec Command="chmod +x packager-linux-x64" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
Expand Down
65 changes: 52 additions & 13 deletions AssetOptionsBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ This is specific to the cloud you are migrating to.
{
Arity = ArgumentArity.ZeroOrOne
};

private readonly Option<string?> _outputManifest = new Option<string?>(
aliases: new[] { "--output-manifest-name", "-m" },
description: @"The output manifest name without extension,
if it is not set, use input asset's manifest name.")
{
Arity = ArgumentArity.ZeroOrOne
};
{
Arity = ArgumentArity.ZeroOrOne
};

private readonly Option<DateTimeOffset?> _creationTimeStart = new Option<DateTimeOffset?>(
aliases: new[] { "--creation-time-start", "-cs" },
Expand Down Expand Up @@ -75,9 +75,9 @@ This is specific to the cloud you are migrating to.
};

private readonly Option<Packager> _packagerType = new(
aliases: new[] { "--packager" },
() => Packager.Shaka,
description: "The packager to use.")
aliases: new[] { "--packager" },
() => Packager.Shaka,
description: "The packager to use.")
{
IsHidden = true,
IsRequired = false
Expand Down Expand Up @@ -112,6 +112,22 @@ This is specific to the cloud you are migrating to.
() => DefaultBatchSize,
description: @"Batch size for parallel processing.");

private readonly Option<bool> _encryptContent = new (
aliases: new[] { "-e", "--encrypt-content" },
() => false,
description: "Encrypt the content using CENC"
);

private readonly Option<Uri?> _keyVaultUri = new (
aliases: new[] { "--key-vault-uri" },
description: "The key vault to store encryption keys."
);

private readonly Option<string?> _keyUri = new(
aliases: new[] { "--key-uri" },
description: "The key URI to use for requesting the key. This is saved to the manifest."
);

const int SegmentDurationInSeconds = 2;

public AssetOptionsBinder()
Expand All @@ -125,7 +141,8 @@ public AssetOptionsBinder()
}
});

_pathTemplate.AddValidator(result => {
_pathTemplate.AddValidator(result =>
{
var value = result.GetValueOrDefault<string>();
if (!string.IsNullOrEmpty(value))
{
Expand Down Expand Up @@ -154,15 +171,34 @@ public Command GetCommand(string name, string description)
command.AddOption(_workingDirectory);
command.AddOption(_copyNonStreamable);
command.AddOption(_batchSize);
command.AddOption(_encryptContent);
command.AddOption(_keyVaultUri);
command.AddOption(_keyUri);
command.AddValidator(result =>
{
if (result.GetValueForOption(_encryptContent))
{
if (result.FindResultFor(_keyVaultUri) == null || result.FindResultFor(_keyUri) == null)
{
result.ErrorMessage = "Key vault and key URI must be specified when encryption is enabled.";
}
else if (result.GetValueForOption(_packagerType) != Packager.Shaka)
{
result.ErrorMessage = "Static encryption is only supported with shaka packager.";
}
}
});
return command;
}

protected override AssetOptions GetBoundValue(BindingContext bindingContext)
protected override AssetOptions GetBoundValue(BindingContext bindingContext) => GetValue(bindingContext);

public AssetOptions GetValue(BindingContext bindingContext)
{
var workingDirectory = bindingContext.ParseResult.GetValueForOption(_workingDirectory)!;
Directory.CreateDirectory(workingDirectory);
return new AssetOptions(
bindingContext.ParseResult.GetValueForOption(_sourceAccount)!,
bindingContext.ParseResult.GetValueForOption(_sourceAccount)!,
bindingContext.ParseResult.GetValueForOption(_storageAccount)!,
bindingContext.ParseResult.GetValueForOption(_packagerType),
bindingContext.ParseResult.GetValueForOption(_pathTemplate)!,
Expand All @@ -176,9 +212,12 @@ protected override AssetOptions GetBoundValue(BindingContext bindingContext)
bindingContext.ParseResult.GetValueForOption(_skipMigrated),
SegmentDurationInSeconds,
bindingContext.ParseResult.GetValueForOption(_batchSize)
);
)
{
EncryptContent = bindingContext.ParseResult.GetValueForOption(_encryptContent),
KeyUri = bindingContext.ParseResult.GetValueForOption(_keyUri),
KeyVaultUri = bindingContext.ParseResult.GetValueForOption(_keyVaultUri)
};
}

public AssetOptions GetValue(BindingContext bindingContext) => GetBoundValue(bindingContext);
}
}
6 changes: 6 additions & 0 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
using System.Diagnostics;
using System.Text;
using Vertical.SpectreLogger;
using Vertical.SpectreLogger.Core;
using Vertical.SpectreLogger.Options;
using Events = AMSMigrate.Contracts.Events;

namespace AMSMigrate
{
Expand Down Expand Up @@ -140,11 +143,14 @@ static void SetupServices(IServiceCollection collection, GlobalOptions options,
{
Level = SourceLevels.All
};
LogEventFilterDelegate filter = (in LogEventContext context) => context.EventId != Events.ShakaPackager;
builder
.SetMinimumLevel(LogLevel.Trace)
.AddSpectreConsole(builder =>
builder
.SetMinimumLevel(options.LogLevel)
.SetLogEventFilter(filter)
.UseSerilogConsoleStyle()
.UseConsole(console)
.WriteInBackground())
.AddTraceSource(logSwitch, listener);
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ The content is converted to CMAF format with both a DASH and HLS manifest to sup
* Support for packaging VOD assets.
* Support for copying non-streamable assets.
* Marks migrated assets and provides HTML summary on analyze
* Support for statically encrypting the cotnent while packaging.

## Open Issues
* Live assets are not supported but will be in a future version of this tool.
Expand All @@ -63,6 +64,7 @@ You'll need to have the following permissions:

- The identity used to migrate must have 'Contributor' role on the Azure Media Services account being migrated.
- The identity that runs this migration tool should be added to the ['Storage Blob Data Contributor'](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#storage-blob-data-contributor) role for the source and destination storage accounts.
- The identity used to migrate must have the role ['Key Vault Secrets Officer'](https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-guide?tabs=azure-cli#azure-built-in-roles-for-key-vault-data-plane-operations) on the key vault used to store the keys. If the key vault is not using Azure RBAC then you will have to create an Access policy giving secrets management permission to the identity used.

# Quick Start

Expand Down
9 changes: 9 additions & 0 deletions ams/AmsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Azure.ResourceManager.Media;
using Azure.Storage.Blobs;
using Azure;
using AMSMigrate.Transform;
using AMSMigrate.Contracts;

namespace AMSMigrate.Ams
{
Expand Down Expand Up @@ -73,5 +75,12 @@ public static async Task CreateStreamingLocator(
StreamingPolicyName = "migration"
});
}

public static void GetEncryptionDetails(this AssetDetails details, MigratorOptions options, TemplateMapper templateMapper)
{
details.EncryptionKey = Guid.NewGuid().ToString("n");
details.KeyId = Guid.NewGuid().ToString("n");
details.LicenseUri = templateMapper.ExpandKeyUriTemplate(options.KeyUri!, details.KeyId);
}
}
}
8 changes: 8 additions & 0 deletions ams/TemplateMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ public string ExpandKeyTemplate(StreamingLocatorContentKey contentKey, string? t
});
}

public string ExpandKeyUriTemplate(string uriTemplate, string keyId)
{
return ExpandTemplate(uriTemplate, key => key switch {
"KeyId" => keyId,
_ => null
});
}

private async Task<string> GetLocatorIdAsync(MediaAssetResource asset)
{
var locators = asset.GetStreamingLocatorsAsync();
Expand Down
2 changes: 1 addition & 1 deletion azure/AzureProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public AzureProvider(
public IFileUploader GetStorageProvider(MigratorOptions migratorOptions)
=> new AzureStorageUploader(migratorOptions, _credentials, _loggerFactory.CreateLogger<AzureStorageUploader>());

public ISecretUploader GetSecretProvider(KeyOptions keyOptions)
public ISecretUploader GetSecretProvider(KeyVaultOptions keyOptions)
=> new KeyVaultUploader(keyOptions, _credentials, _loggerFactory.CreateLogger<KeyVaultUploader>());
}
}
1 change: 0 additions & 1 deletion azure/AzureStorageUploader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
using Microsoft.Extensions.Logging;
using System.Reflection.PortableExecutable;
using System.Text;

namespace AMSMigrate.Azure
Expand Down
8 changes: 4 additions & 4 deletions azure/KeyVaultUploader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ internal class KeyVaultUploader : ISecretUploader
{
private readonly ILogger _logger;
private readonly SecretClient _secretClient;
private readonly KeyOptions _keyOptions;
private readonly KeyVaultOptions _options;

public KeyVaultUploader(
KeyOptions options,
KeyVaultOptions options,
TokenCredential credential,
ILogger<KeyVaultUploader> logger)
{
_keyOptions = options;
_options = options;
_logger = logger;
_secretClient = new SecretClient(options.KeyVaultUri, credential);
}

public async Task UploadAsync(string secretName, string secretValue, CancellationToken cancellationToken)
{
_logger.LogInformation("Saving secret {name} to key vault {vault}", secretName, _keyOptions.KeyVaultUri);
_logger.LogInformation("Saving secret {name} to key vault {vault}", secretName, _options.KeyVaultUri);
await _secretClient.SetSecretAsync(secretName, secretValue, cancellationToken);
}
}
Expand Down
8 changes: 8 additions & 0 deletions contracts/Events.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace AMSMigrate.Contracts
{
internal class Events
{
public const int ShakaPackager = 101;
public const int Ffmpeg = 102;
}
}
2 changes: 1 addition & 1 deletion contracts/ICloudProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ interface ICloudProvider
{
IFileUploader GetStorageProvider(MigratorOptions migratorOptions);

ISecretUploader GetSecretProvider(KeyOptions keyOptions);
ISecretUploader GetSecretProvider(KeyVaultOptions keyOptions);
}
}
4 changes: 3 additions & 1 deletion contracts/KeyOptions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
namespace AMSMigrate
{
public record KeyVaultOptions(Uri KeyVaultUri);

public record KeyOptions(
string AccountName,
string? ResourceFilter,
Uri KeyVaultUri,
string? KeyTemplate,
int BatchSize);
int BatchSize) : KeyVaultOptions(KeyVaultUri);
}
9 changes: 8 additions & 1 deletion contracts/MigratorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,12 @@ public record MigratorOptions(
bool OverWrite,
bool SkipMigrated,
int SegmentDuration,
int BatchSize);
int BatchSize)
{
public bool EncryptContent { get; set; }

public string? KeyUri { get; set; }

public Uri? KeyVaultUri { get; set; }
}
}
1 change: 0 additions & 1 deletion decryption/AssetDecryptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
//-----------------------------------------------------------------------

using System.Security.Cryptography;
using System.Threading;
using Azure.ResourceManager.Media.Models;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
Expand Down
2 changes: 1 addition & 1 deletion local/LocalFileProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public LocalFileProvider(ILoggerFactory loggerFactory)
_loggerFactory = loggerFactory;
}

public ISecretUploader GetSecretProvider(KeyOptions keyOptions)
public ISecretUploader GetSecretProvider(KeyVaultOptions keyOptions)
{
throw new NotImplementedException();
}
Expand Down
1 change: 0 additions & 1 deletion transform/AssetTransform.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using AMSMigrate.Ams;
using AMSMigrate.Contracts;
using AMSMigrate.Decryption;
using Azure.ResourceManager.Media;
using Azure.ResourceManager.Media.Models;
using Azure.Storage.Blobs;
Expand Down
Loading

0 comments on commit 8c01001

Please sign in to comment.