This repository has been archived by the owner on Feb 16, 2024. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds initial plugin solution Adds KeyVault plugin / project
- Loading branch information
1 parent
086a2c0
commit 23d8586
Showing
18 changed files
with
987 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Binary file not shown.
11 changes: 11 additions & 0 deletions
11
src/Microsoft.Azure.ServiceBus.KeyVault/KeyVaultMessageHeaders.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"; | ||
} | ||
} |
171 changes: 171 additions & 0 deletions
171
src/Microsoft.Azure.ServiceBus.KeyVault/KeyVaultPlugin.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/Microsoft.Azure.ServiceBus.KeyVault/KeyVaultPluginException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
{ | ||
} | ||
} | ||
} |
Oops, something went wrong.