KeyVault plugin (#1)
Adds initial plugin solution Adds KeyVault plugin / project
This commit is contained in:
Родитель
086a2c0774
Коммит
23d858663a
|
@ -0,0 +1,36 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26430.6
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7D8B793C-F4AC-432A-8EE7-2EE3C2B16EC1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Azure.ServiceBus.KeyVault", "src\Microsoft.Azure.ServiceBus.KeyVault\Microsoft.Azure.ServiceBus.KeyVault.csproj", "{F942A66E-A1B7-41C3-85E8-1A108D18F890}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B18143EB-5FF4-4AFC-B086-709DC9B92546}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Azure.ServiceBus.KeyVault.Test", "test\Microsoft.Azure.ServiceBus.KeyVault.Test\Microsoft.Azure.ServiceBus.KeyVault.Test.csproj", "{DC8DC685-DCB5-47C7-A2B7-41D31EDDCAED}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F942A66E-A1B7-41C3-85E8-1A108D18F890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F942A66E-A1B7-41C3-85E8-1A108D18F890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F942A66E-A1B7-41C3-85E8-1A108D18F890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F942A66E-A1B7-41C3-85E8-1A108D18F890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DC8DC685-DCB5-47C7-A2B7-41D31EDDCAED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DC8DC685-DCB5-47C7-A2B7-41D31EDDCAED}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DC8DC685-DCB5-47C7-A2B7-41D31EDDCAED}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DC8DC685-DCB5-47C7-A2B7-41D31EDDCAED}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{F942A66E-A1B7-41C3-85E8-1A108D18F890} = {7D8B793C-F4AC-432A-8EE7-2EE3C2B16EC1}
|
||||
{DC8DC685-DCB5-47C7-A2B7-41D31EDDCAED} = {B18143EB-5FF4-4AFC-B086-709DC9B92546}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,21 @@
|
|||
version: 1.0.{build}
|
||||
skip_branch_with_pr: true
|
||||
skip_tags: true
|
||||
matrix:
|
||||
fast_finish: true
|
||||
image: Visual Studio 2017
|
||||
environment:
|
||||
matrix:
|
||||
# First build
|
||||
- DotNetRunTime: netcoreapp1.0
|
||||
# Second build
|
||||
- DotNetRunTime: net46
|
||||
azure-service-bus-dotnet/SkipCodeCoverage: true
|
||||
skip_commits:
|
||||
files:
|
||||
- '**/*.md'
|
||||
artifacts:
|
||||
- path: .\build\artifacts\*
|
||||
build_script:
|
||||
- ps: .\build\build.ps1
|
||||
test: off
|
|
@ -0,0 +1,130 @@
|
|||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$isAppveyor = [bool]$env:APPVEYOR
|
||||
$configuration = if ($CONFIGURATION -ne $null) { $CONFIGURATION } else { 'Debug' }
|
||||
$platform = if ($PLATFORM -ne $null) { $PLATFORM } else { 'Any CPU' }
|
||||
$projectFolder = if ($ENV:APPVEYOR_BUILD_FOLDER -ne $null) { "$ENV:APPVEYOR_BUILD_FOLDER" } else { $(Get-Location).path }
|
||||
$buildFolder = $projectFolder + '\build\'
|
||||
$runtime = if ($ENV:DotNetRunTime -ne $null) { $ENV:DotNetRunTime } else { 'netcoreapp1.0' }
|
||||
$artifactsFolder = $buildFolder + 'artifacts\'
|
||||
$appProject = $projectFolder + '\src\\Microsoft.Azure.ServiceBus.KeyVault\Microsoft.Azure.ServiceBus.KeyVault.csproj'
|
||||
$testProject = $projectFolder + '\test\Microsoft.Azure.ServiceBus.KeyVault.Test\Microsoft.Azure.ServiceBus.KeyVault.Test.csproj'
|
||||
$coverageFile = $buildFolder + 'coverage.xml'
|
||||
$appNamespace = 'Microsoft.Azure.ServiceBus.KeyVault'
|
||||
$testNamespace = 'Microsoft.Azure.ServiceBus.KeyVault.Test'
|
||||
|
||||
# Environment variables
|
||||
$skipCodeCoverage = if ([bool][Environment]::GetEnvironmentVariable('azure-service-bus-dotnet/SkipCodeCoverage')) { $true } else { $false }
|
||||
|
||||
function Build-Solution
|
||||
{
|
||||
Write-Host "Building projects"
|
||||
|
||||
# Restore solution files
|
||||
MSBuild.exe Microsoft.Azure.ServiceBus.Plugins.sln /t:restore /p:Configuration=$configuration /p:Platform=$platform /verbosity:minimal
|
||||
|
||||
# $? Returns True or False value indicating whether previous command ended with an error.
|
||||
# This is used to throw an error that will cause the AppVeyor process to fail as expected.
|
||||
if (-not $?)
|
||||
{
|
||||
throw "Package restore failed."
|
||||
}
|
||||
|
||||
# Build solution
|
||||
MSBuild.exe Microsoft.Azure.ServiceBus.Plugins.sln /p:Configuration=$configuration /p:Platform=$platform /verbosity:minimal
|
||||
|
||||
if (-not $?)
|
||||
{
|
||||
throw "Build failed."
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "Building complete."
|
||||
}
|
||||
}
|
||||
|
||||
function Run-UnitTests
|
||||
{
|
||||
if ($skipCodeCoverage)
|
||||
{
|
||||
dotnet test $testProject -f $runtime
|
||||
if (-not $?)
|
||||
{
|
||||
throw "Unit tests failed."
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Write-Host "Running unit tests."
|
||||
|
||||
if (-Not (Test-Path .\nuget.exe))
|
||||
{
|
||||
Invoke-WebRequest -Uri "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -outfile $buildFolder\nuget.exe
|
||||
}
|
||||
|
||||
$openCoverVersion = '4.6.712'
|
||||
$openCoverNuPkgOutFile = $buildFolder + 'OpenCover.' + $openCoverVersion + '.nupkg'
|
||||
|
||||
# Using a temporary version of OpenCover until a NuGet is published. https://github.com/OpenCover/opencover/issues/669
|
||||
# Once there is a new NuGet package, this if statement can be removed, and so can '-source $buildFolder' in the line after
|
||||
if (-Not (Test-Path $openCoverNuPkgOutFile)) {
|
||||
Invoke-WebRequest `
|
||||
-Uri "https://ci.appveyor.com/api/buildjobs/upad53qdyo1iv382/artifacts/main%2Fbin%2Fpackages%2Fnuget%2Fopencover%2FOpenCover.4.6.712.nupkg" `
|
||||
-OutFile $openCoverNuPkgOutFile
|
||||
}
|
||||
|
||||
& $buildFolder\nuget.exe install OpenCover -version $openCoverVersion -SolutionDirectory $buildFolder -source $buildFolder
|
||||
|
||||
$openCoverConsole = $buildFolder + 'packages/' + 'OpenCover.' + $openCoverVersion + '\tools\OpenCover.Console.exe'
|
||||
$target = '-target:C:\Program Files\dotnet\dotnet.exe'
|
||||
$targetArgs = '-targetargs: test ' + $testProject + ' -f ' + $runtime
|
||||
$filter = '-filter:+[' + $appNamespace + '*]* -[' + $testNamespace + ']*'
|
||||
$output = '-output:' + $coverageFile
|
||||
|
||||
& $openCoverConsole $target $targetArgs $filter $output '-register:user' '-oldStyle'
|
||||
|
||||
if (-not $?)
|
||||
{
|
||||
throw "Unit tests failed."
|
||||
}
|
||||
|
||||
if (-Not (Test-Path $coverageFile))
|
||||
{
|
||||
return
|
||||
}
|
||||
if ($isAppveyor)
|
||||
{
|
||||
$ENV:PATH = 'C:\\Python34;C:\\Python34\\Scripts;' + $ENV:PATH
|
||||
python -m pip install --upgrade pip
|
||||
pip install git+git://github.com/codecov/codecov-python.git
|
||||
codecov -f $coverageFile -X gcov
|
||||
}
|
||||
else
|
||||
{
|
||||
$reportGeneratorVersion = '2.5.7'
|
||||
& $buildFolder\nuget.exe install ReportGenerator -version $reportGeneratorVersion -SolutionDirectory $buildFolder
|
||||
$reportGenerator = $buildFolder + 'packages\' + 'ReportGenerator.' + $reportGeneratorVersion + '\tools\ReportGenerator.exe'
|
||||
$targetDirectory = $buildFolder + 'OpenCoverReport\'
|
||||
& $reportGenerator -reports:$coverageFile -targetdir:$targetDirectory
|
||||
}
|
||||
}
|
||||
|
||||
function CopyArtifacts
|
||||
{
|
||||
if (-Not $isAppveyor)
|
||||
{
|
||||
return
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path $artifactsFolder | Out-Null
|
||||
MSBuild.exe $appProject /t:pack /p:Configuration=$configuration /p:Platform=$platform /p:PackageOutputPath=$artifactsFolder /verbosity:minimal
|
||||
if (Test-Path $coverageFile)
|
||||
{
|
||||
Copy-Item $coverageFile $artifactsFolder
|
||||
}
|
||||
}
|
||||
|
||||
# Run the functions
|
||||
|
||||
Build-Solution
|
||||
Run-UnitTests
|
||||
CopyArtifacts
|
Двоичный файл не отображается.
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.KeyVault
|
||||
{
|
||||
internal static class KeyVaultMessageHeaders
|
||||
{
|
||||
internal const string InitializationVectorPropertyName = "KeyVault-IV";
|
||||
internal const string KeyNamePropertyName = "KeyVault-KeyName";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.KeyVault
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Core;
|
||||
using System.Security.Cryptography;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Provides Azure KeyVault functionality for Azure Service Bus.
|
||||
/// </summary>
|
||||
public class KeyVaultPlugin : ServiceBusPlugin
|
||||
{
|
||||
private readonly string secretName;
|
||||
private readonly string keyVaultEndpoint;
|
||||
private KeyVaultSecretManager secretManager;
|
||||
private byte[] initializationVector;
|
||||
private string base64InitializationVector;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name that is used to identify this plugin.
|
||||
/// </summary>
|
||||
public override string Name => "Microsoft.Azure.ServiceBus.KeyVault.KeyVaultPlugin";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of an <see cref="KeyVaultPlugin"/>.
|
||||
/// </summary>
|
||||
/// <param name="encryptionSecretName">The name of the secret used to encrypt / decrypt messages.</param>
|
||||
/// <param name="options">The <see cref="KeyVaultPluginSettings"/> used to create a new instance.</param>
|
||||
public KeyVaultPlugin(string encryptionSecretName, KeyVaultPluginSettings options)
|
||||
{
|
||||
if (string.IsNullOrEmpty(encryptionSecretName))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encryptionSecretName));
|
||||
}
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
this.secretName = encryptionSecretName;
|
||||
this.keyVaultEndpoint = options.Endpoint;
|
||||
this.secretManager = new KeyVaultSecretManager(options.Endpoint, options.ClientId, options.ClientSecret);
|
||||
this.initializationVector = KeyVaultPlugin.GenerateInitializationVector();
|
||||
this.base64InitializationVector = Convert.ToBase64String(this.initializationVector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The action performed before sending a message to Service Bus. This method will load the KeyVault key and encrypt messages.
|
||||
/// </summary>
|
||||
/// <param name="message">The <see cref="Message"/> to be encrypted.</param>
|
||||
/// <returns>The encrypted <see cref="Message"/>.</returns>
|
||||
public override async Task<Message> BeforeMessageSend(Message message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (message.UserProperties.ContainsKey(KeyVaultMessageHeaders.InitializationVectorPropertyName) || message.UserProperties.ContainsKey(KeyVaultMessageHeaders.KeyNamePropertyName))
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
var secret = await secretManager.GetHashedSecret(secretName);
|
||||
|
||||
message.UserProperties.Add(KeyVaultMessageHeaders.InitializationVectorPropertyName, base64InitializationVector);
|
||||
message.UserProperties.Add(KeyVaultMessageHeaders.KeyNamePropertyName, secretName);
|
||||
|
||||
message.Body = await KeyVaultPlugin.Encrypt(message.Body, secret, this.initializationVector);
|
||||
return message;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new KeyVaultPluginException(Resources.BeforeMessageSendException, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The action performed after receiving a message from Service Bus. This method will load the KeyVault key and decrypt messages.
|
||||
/// </summary>
|
||||
/// <param name="message">The <see cref="Message"/> to be decrypted.</param>
|
||||
/// <returns>The decrypted <see cref="Message"/>.</returns>
|
||||
public override async Task<Message> AfterMessageReceive(Message message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!message.UserProperties.ContainsKey(KeyVaultMessageHeaders.InitializationVectorPropertyName)
|
||||
|| !message.UserProperties.ContainsKey(KeyVaultMessageHeaders.KeyNamePropertyName))
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
var iVString = message.UserProperties[KeyVaultMessageHeaders.InitializationVectorPropertyName] as string;
|
||||
var iV = Convert.FromBase64String(iVString);
|
||||
var secretName = message.UserProperties[KeyVaultMessageHeaders.KeyNamePropertyName] as string;
|
||||
|
||||
// Remove properties before giving the message back
|
||||
message.UserProperties.Remove(KeyVaultMessageHeaders.InitializationVectorPropertyName);
|
||||
message.UserProperties.Remove(KeyVaultMessageHeaders.KeyNamePropertyName);
|
||||
|
||||
var secret = await secretManager.GetHashedSecret(secretName);
|
||||
|
||||
var decryptedMessage = await KeyVaultPlugin.Decrypt(message.Body, secret, iV);
|
||||
|
||||
message.Body = decryptedMessage;
|
||||
return message;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new KeyVaultPluginException(Resources.AfterMessageReceiveException, ex);
|
||||
}
|
||||
}
|
||||
|
||||
internal static byte[] GenerateInitializationVector()
|
||||
{
|
||||
byte[] initializationVector = null;
|
||||
using (var aes = Aes.Create())
|
||||
{
|
||||
aes.GenerateIV();
|
||||
initializationVector = aes.IV;
|
||||
}
|
||||
return initializationVector;
|
||||
}
|
||||
|
||||
// Taken from the examples here: https://msdn.microsoft.com/en-us/library/system.security.cryptography.aes
|
||||
internal static async Task<byte[]> Encrypt(byte[] payload, byte[] key, byte[] initializationVector)
|
||||
{
|
||||
// Create an Aes object
|
||||
// with the specified key and IV.
|
||||
using (Aes aesAlg = Aes.Create())
|
||||
{
|
||||
aesAlg.Key = key;
|
||||
aesAlg.IV = initializationVector;
|
||||
|
||||
var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
|
||||
|
||||
return await PerformCryptography(encryptor, payload);
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from the examples here: https://msdn.microsoft.com/en-us/library/system.security.cryptography.aes
|
||||
internal static async Task<byte[]> Decrypt(byte[] payload, byte[] key, byte[] initializationVector)
|
||||
{
|
||||
// Create an Aes object
|
||||
// with the specified key and IV.
|
||||
using (Aes aesAlg = Aes.Create())
|
||||
{
|
||||
aesAlg.Key = key;
|
||||
aesAlg.IV = initializationVector;
|
||||
|
||||
var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
|
||||
|
||||
return await PerformCryptography(decryptor, payload);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<byte[]> PerformCryptography(ICryptoTransform cryptoTransform, byte[] data)
|
||||
{
|
||||
// Create the streams used for encryption.
|
||||
using (var memoryStream = new MemoryStream())
|
||||
using (var cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write))
|
||||
{
|
||||
// Write all data to the memory stream.
|
||||
await cryptoStream.WriteAsync(data, 0, data.Length);
|
||||
cryptoStream.FlushFinalBlock();
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.KeyVault
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents errors that occur within the <see cref="KeyVaultPlugin"/>.
|
||||
/// </summary>
|
||||
public class KeyVaultPluginException : Exception
|
||||
{
|
||||
internal KeyVaultPluginException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.KeyVault
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// The endpoint settings used to create a new <see cref="KeyVaultPlugin"/>.
|
||||
/// </summary>
|
||||
public class KeyVaultPluginSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="KeyVaultPluginSettings"/> object and validates the settings.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The endpoint should be a guid.</param>
|
||||
/// <param name="endpoint">The endpoint should resemble: https://{keyvault-name}.vault.azure.net/</param>
|
||||
/// <param name="clientSecret">The secret should be a token.</param>
|
||||
public KeyVaultPluginSettings(string clientId, string endpoint, string clientSecret)
|
||||
{
|
||||
if (string.IsNullOrEmpty(clientId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
if (string.IsNullOrEmpty(endpoint))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoint));
|
||||
}
|
||||
if (string.IsNullOrEmpty(clientSecret))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientSecret));
|
||||
}
|
||||
|
||||
this.ClientId = clientId;
|
||||
this.Endpoint = endpoint;
|
||||
this.ClientSecret = clientSecret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the KeyVault endpoint.
|
||||
/// </summary>
|
||||
public string Endpoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the KeyVault ClientId.
|
||||
/// </summary>
|
||||
public string ClientId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the KeyVaultClientSecret.
|
||||
/// </summary>
|
||||
public string ClientSecret { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.KeyVault
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.KeyVault;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using System.Text;
|
||||
|
||||
internal class KeyVaultSecretManager
|
||||
{
|
||||
private static Dictionary<string, byte[]> secretCache;
|
||||
private string azureClientId;
|
||||
private string azureClientSecret;
|
||||
|
||||
internal string KeyVaultUrl { get; private set; }
|
||||
|
||||
internal KeyVaultSecretManager(string keyVaultUrl, string azureClientId, string azureClientSecret)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keyVaultUrl))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(keyVaultUrl));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(azureClientId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(azureClientId));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(azureClientSecret))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(azureClientSecret));
|
||||
}
|
||||
|
||||
secretCache = new Dictionary<string, byte[]>();
|
||||
this.KeyVaultUrl = keyVaultUrl;
|
||||
this.azureClientId = azureClientId;
|
||||
this.azureClientSecret = azureClientSecret;
|
||||
}
|
||||
|
||||
internal async Task<byte[]> GetHashedSecret(string secretName)
|
||||
{
|
||||
if (secretCache.ContainsKey(secretName))
|
||||
{
|
||||
return secretCache[secretName];
|
||||
}
|
||||
|
||||
var secret = await GetSecretFromKeyVault(secretName);
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
var secretAsBytes = Encoding.UTF8.GetBytes(secret);
|
||||
var hashedSecret = sha256.ComputeHash(secretAsBytes);
|
||||
secretCache.Add(secretName, hashedSecret);
|
||||
return hashedSecret;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetSecretFromKeyVault(string secretName)
|
||||
{
|
||||
using (var keyVaultClient = new KeyVaultClient(GetAccessToken))
|
||||
{
|
||||
string secret;
|
||||
try
|
||||
{
|
||||
var secretResult = await keyVaultClient.GetSecretAsync(this.KeyVaultUrl, secretName);
|
||||
secret = secretResult.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new KeyVaultPluginException(string.Format(Resources.KeyVaultKeyAcquisitionFailure, secretName, KeyVaultUrl), ex);
|
||||
}
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
// ToDo: Add support for KeyVault service side encryption/decryption
|
||||
|
||||
//var encryptedMessage = await keyVaultClient.EncryptAsync(
|
||||
// KeyVaultUrl,
|
||||
// SecretName,
|
||||
// "secretVersion",
|
||||
// JsonWebKeyEncryptionAlgorithm.RSA15,
|
||||
// message);
|
||||
}
|
||||
|
||||
private async Task<string> GetAccessToken(string authority, string resource, string scope)
|
||||
{
|
||||
var credential = new ClientCredential(this.azureClientId, this.azureClientSecret);
|
||||
|
||||
var ctx = new AuthenticationContext(new Uri(authority).AbsoluteUri, false);
|
||||
|
||||
AuthenticationResult result;
|
||||
try
|
||||
{
|
||||
result = await ctx.AcquireTokenAsync(resource, credential);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new KeyVaultPluginException(string.Format(Resources.AzureAdTokenAcquisitionFailure, KeyVaultUrl), ex);
|
||||
}
|
||||
return result.AccessToken;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Microsoft.Azure.ServiceBus KeyVault extension</Description>
|
||||
<AssemblyTitle>Microsoft.Azure.ServiceBus.KeyVault</AssemblyTitle>
|
||||
<VersionPrefix>0.0.1-preview</VersionPrefix>
|
||||
<Authors>Microsoft</Authors>
|
||||
<TargetFrameworks>net451;netstandard1.3</TargetFrameworks>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<AssemblyName>Microsoft.Azure.ServiceBus.KeyVault</AssemblyName>
|
||||
<AssemblyOriginatorKeyFile>../../build/keyfile.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
<PackageId>Microsoft.Azure.ServiceBus.KeyVault</PackageId>
|
||||
<PackageTags>Azure;Service Bus;ServiceBus;.NET;AMQP;IoT;Queue;Topic;KeyVault;Encryption;Plugin</PackageTags>
|
||||
<PackageReleaseNotes>https://github.com/Azure/azure-service-bus-dotnet-plugins/releases</PackageReleaseNotes>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/Azure/azure-service-bus-dotnet-plugins/master/service-bus.png</PackageIconUrl>
|
||||
<PackageProjectUrl>https://github.com/Azure/azure-service-bus-dotnet-plugins</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://raw.githubusercontent.com/Azure/azure-service-bus-dotnet-plugins/master/LICENSE</PackageLicenseUrl>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.3' ">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
|
||||
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.3' ">1.6.1</NetStandardImplicitPackageVersion>
|
||||
<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\Microsoft.Azure.ServiceBus.KeyVault.xml</DocumentationFile>
|
||||
<DebugType>full</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.KeyVault" Version="2.0.6" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="3.13.9" />
|
||||
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="0.0.6-preview" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Microsoft")]
|
||||
[assembly: AssemblyProduct("Microsoft.Azure.ServiceBus.KeyVault")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("a042adf0-ef65-4f87-b634-322a409f3d61")]
|
||||
|
||||
// Friend Assemblies
|
||||
[assembly: InternalsVisibleTo("Microsoft.Azure.ServiceBus.KeyVault.Test,PublicKey=" +
|
||||
"0024000004800000940000000602000000240000525341310004000001000100fdf4acac3b2244" +
|
||||
"dd8a96737e5385b31414369dc3e42f371172127252856a0650793e1f5673a16d5d78e2ac852a10" +
|
||||
"4bc51e6f018dca44fdd26a219c27cb2b263956a80620223c8e9c2f8913c3c903e1e453e9e4e840" +
|
||||
"98afdad5f4badb8c1ebe0a7b0a4b57a08454646a65886afe3e290a791ff3260099ce0edf0bdbcc" +
|
||||
"afadfeb6")]
|
|
@ -0,0 +1,102 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.KeyVault {
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Azure.ServiceBus.KeyVault.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An exception occured while trying to decrypt a message.
|
||||
/// </summary>
|
||||
public static string AfterMessageReceiveException
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResourceManager.GetString("AfterMessageReceiveException", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Could not acquire Azure AD token for KeyVault access. KeyVault: '{0}'.
|
||||
/// </summary>
|
||||
public static string AzureAdTokenAcquisitionFailure {
|
||||
get {
|
||||
return ResourceManager.GetString("AzureAdTokenAcquisitionFailure", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An exception occured while trying to encrypt a message.
|
||||
/// </summary>
|
||||
public static string BeforeMessageSendException
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResourceManager.GetString("BeforeMessageSendException", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Could not acquire KeyVault token. Token name: '{0}' KeyVault: '{1}'.
|
||||
/// </summary>
|
||||
public static string KeyVaultKeyAcquisitionFailure {
|
||||
get {
|
||||
return ResourceManager.GetString("KeyVaultKeyAcquisitionFailure", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AfterMessageReceiveException" xml:space="preserve">
|
||||
<value>An exception occured while trying to decrypt a message.</value>
|
||||
</data>
|
||||
<data name="AzureAdTokenAcquisitionFailure" xml:space="preserve">
|
||||
<value>Could not acquire Azure AD token for KeyVault access. KeyVault: '{0}'</value>
|
||||
</data>
|
||||
<data name="BeforeMessageSendException" xml:space="preserve">
|
||||
<value>An exception occured while trying to encrypt a message.</value>
|
||||
</data>
|
||||
<data name="KeyVaultKeyAcquisitionFailure" xml:space="preserve">
|
||||
<value>Could not acquire KeyVault token. Token name: '{0}' KeyVault: '{1}'</value>
|
||||
</data>
|
||||
</root>
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.KeyVault.Test
|
||||
{
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.PlatformAbstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
public class DisplayTestMethodNameAttribute : BeforeAfterTestAttribute
|
||||
{
|
||||
public override void Before(MethodInfo methodUnderTest)
|
||||
{
|
||||
TestUtility.Log($"Begin {methodUnderTest.DeclaringType}.{methodUnderTest.Name} on {PlatformServices.Default.Application.RuntimeFramework}");
|
||||
base.Before(methodUnderTest);
|
||||
}
|
||||
|
||||
public override void After(MethodInfo methodUnderTest)
|
||||
{
|
||||
TestUtility.Log($"End {methodUnderTest.DeclaringType}.{methodUnderTest.Name}");
|
||||
base.After(methodUnderTest);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.KeyVault.Test
|
||||
{
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.KeyVault;
|
||||
using Xunit;
|
||||
|
||||
public class EncryptionTests
|
||||
{
|
||||
[Fact]
|
||||
[DisplayTestMethodName]
|
||||
public async Task SmokeTest()
|
||||
{
|
||||
var payload = Encoding.UTF8.GetBytes("hello");
|
||||
var password = "password";
|
||||
|
||||
var sha256 = SHA256.Create();
|
||||
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
|
||||
|
||||
var iV = KeyVaultPlugin.GenerateInitializationVector();
|
||||
|
||||
var encryptedPayload = await KeyVaultPlugin.Encrypt(payload, hash, iV);
|
||||
|
||||
var decryptedPayload = await KeyVaultPlugin.Decrypt(encryptedPayload, hash, iV);
|
||||
|
||||
Assert.Equal(payload, decryptedPayload);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>Microsoft.Azure.ServiceBus.KeyVault.Test</AssemblyTitle>
|
||||
<TargetFrameworks>netcoreapp1.0;net46</TargetFrameworks>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<AssemblyName>Microsoft.Azure.ServiceBus.KeyVault.Test</AssemblyName>
|
||||
<AssemblyOriginatorKeyFile>../../build/keyfile.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
<PackageId>Microsoft.Azure.ServiceBus.KeyVault.Test</PackageId>
|
||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);dnxcore50;portable-net46+win8</PackageTargetFallback>
|
||||
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
|
||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||
<Description />
|
||||
<DelaySign>False</DelaySign>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Azure.ServiceBus.KeyVault\Microsoft.Azure.ServiceBus.KeyVault.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.NETCore.Platforms" Version="1.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Microsoft")]
|
||||
[assembly: AssemblyProduct("Microsoft.Azure.ServiceBus.KeyVault.Test")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("bda98e20-3a26-4a5a-9371-cb02b73e3434")]
|
||||
|
||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.KeyVault.Test
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
static class TestUtility
|
||||
{
|
||||
internal static void Log(string message)
|
||||
{
|
||||
var formattedMessage = $"{DateTime.Now.TimeOfDay}: {message}";
|
||||
Debug.WriteLine(formattedMessage);
|
||||
Console.WriteLine(formattedMessage);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче