Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

Commit

Permalink
KeyVault plugin (#1)
Browse files Browse the repository at this point in the history
Adds initial plugin solution
Adds KeyVault plugin / project
  • Loading branch information
jtaubensee authored and nemakam committed Jun 2, 2017
1 parent 086a2c0 commit 23d8586
Show file tree
Hide file tree
Showing 18 changed files with 987 additions and 0 deletions.
36 changes: 36 additions & 0 deletions Microsoft.Azure.ServiceBus.Plugins.sln
@@ -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
21 changes: 21 additions & 0 deletions appveyor.yml
@@ -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
130 changes: 130 additions & 0 deletions build/build.ps1
@@ -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 added build/keyfile.snk
Binary file not shown.
11 changes: 11 additions & 0 deletions src/Microsoft.Azure.ServiceBus.KeyVault/KeyVaultMessageHeaders.cs
@@ -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 src/Microsoft.Azure.ServiceBus.KeyVault/KeyVaultPlugin.cs
@@ -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 src/Microsoft.Azure.ServiceBus.KeyVault/KeyVaultPluginException.cs
@@ -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 comments on commit 23d8586

Please sign in to comment.