Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract key logic + added AES encryptor #24

Merged
merged 2 commits into from
Nov 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 12 additions & 46 deletions src/Controllers/EncryptController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,17 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Serilog;
using Hamuste.KeyManagment;

namespace Hamuste.Controllers
{

public class EncryptController : Controller
{
private readonly IKubernetes mKubernetes;
private readonly IKeyVaultClient mKeyVaultClient;
private readonly IAuthorizationService mAuthorizationService;
private readonly IHttpContextAccessor mHttpContextAccessor;
private readonly string mKeyVaultName;
private readonly string mKeyType;
private readonly IKeyManagment mKeyManagment;
private readonly ILogger mAuditLogger = Log.ForContext<EncryptController>().AsAudit();
private readonly ILogger mLogger = Log.ForContext<EncryptController>();

Expand All @@ -37,18 +36,14 @@ public class EncryptController : Controller

public EncryptController(
IKubernetes kubernetes,
IKeyVaultClient keyVaultClient,
IAuthorizationService authorizationService,
IHttpContextAccessor httpContextAccessor,
IConfiguration configuration)
IKeyManagment keyManagment)
{
mKubernetes = kubernetes;
mKeyVaultClient = keyVaultClient;
mAuthorizationService = authorizationService;
mHttpContextAccessor = httpContextAccessor;

mKeyVaultName = configuration["KeyVault:Name"];
mKeyType = configuration["KeyVault:KeyType"];
mKeyManagment = keyManagment;
}

[HttpPost]
Expand All @@ -71,33 +66,15 @@ public async Task<ActionResult> Encrypt([FromBody]EncryptRequest body)
body.NamesapceName);
return BadRequest();
}

var id = $"{body.NamesapceName}:{body.SerivceAccountName}";
var hash = ComputeKeyId(id);

var keyId = $"https://{mKeyVaultName}.vault.azure.net/keys/{hash}";

try
{
await mKeyVaultClient.GetKeyAsync(keyId);
}
catch (KeyVaultErrorException e) when (e.Response.StatusCode == HttpStatusCode.NotFound)
{
mLogger.Information(
"KeyVault key was not found for Namespace {namesapce} and ServiceAccountName {serviceAccount}, creating new one.",
body.NamesapceName, body.SerivceAccountName);

await mKeyVaultClient.CreateKeyAsync($"https://{mKeyVaultName}.vault.azure.net", hash, mKeyType, 2048);
}

var encryptionResult = await mKeyVaultClient.EncryptAsync(keyId, "RSA-OAEP", Encoding.UTF8.GetBytes(body.Data));

var encryptedData = await mKeyManagment.Encrypt(body.Data, $"{body.NamesapceName}:{body.SerivceAccountName}");

mAuditLogger.Information("Encryption request succeeded, SourceIP: {sourceIp}, ServiceAccount: {serviceAccount}, Namesacpe: {namespace}",
Request.HttpContext.Connection.RemoteIpAddress,
body.SerivceAccountName,
body.NamesapceName);

return Content(Convert.ToBase64String(encryptionResult.Result));
return Content(encryptedData);
}

[HttpPost]
Expand All @@ -123,20 +100,16 @@ public async Task<ActionResult> Decrypt([FromBody] DecryptRequest body)
Request.HttpContext.Connection.RemoteIpAddress,
id);

var hash = ComputeKeyId(id);

var keyId = $"https://{mKeyVaultName}.vault.azure.net/keys/{hash}";
try
try
{
var encryptionResult =
await mKeyVaultClient.DecryptAsync(keyId, "RSA-OAEP", Convert.FromBase64String(body.EncryptedData));
var data = await mKeyManagment.Decrypt(body.EncryptedData, id);

mAuditLogger.Information("Decryption request succeeded, SourceIP: {sourceIp}, ServiceAccount user Name: {sa}",
Request.HttpContext.Connection.RemoteIpAddress,
id);
return Content(Encoding.UTF8.GetString(encryptionResult.Result));
return Content(data);
}
catch (KeyVaultErrorException e)
catch (DecryptionFailureException e)
{
mLogger.Warning(e, "Decryption request failed, ServiceAccount: {sa}",
Request.HttpContext.Connection.RemoteIpAddress,
Expand All @@ -145,13 +118,6 @@ public async Task<ActionResult> Decrypt([FromBody] DecryptRequest body)
}
}

private string ComputeKeyId(string serviceUserName)
{
return
WebEncoders.Base64UrlEncode(
SHA256.Create().ComputeHash(
Encoding.UTF8.GetBytes(serviceUserName)))
.Replace("_", "-");
}

}
}
35 changes: 35 additions & 0 deletions src/KeyManagment/AESKeyManagment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;

namespace Hamuste.KeyManagment
{
// inspired by: https://stackoverflow.com/a/43936866/4792970
public class AESKeyManagment : IKeyManagment
{
private string mKey;
private IDataProtector mDataProtector;

public AESKeyManagment(string key, IDataProtectionProvider dataProtectionProvider)
{
mDataProtector = dataProtectionProvider.CreateProtector(key);
}
public Task<string> Decrypt(string encryptedData, string serviceAccountId)
{
var protector = mDataProtector.CreateProtector(serviceAccountId);
return Task.FromResult(
Encoding.UTF8.GetString(protector.Unprotect(Convert.FromBase64String(encryptedData))));
}

public Task<string> Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true)
{
var protector = mDataProtector.CreateProtector(serviceAccountId);
return Task.FromResult(
Convert.ToBase64String(protector.Protect(Encoding.UTF8.GetBytes(data))));
}
}
}
86 changes: 86 additions & 0 deletions src/KeyManagment/AzureKeyVaultKeyManagment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.KeyVault.Models;
using Microsoft.Extensions.Configuration;
using Serilog;

namespace Hamuste.KeyManagment
{
public class AzureKeyVaultKeyManagment : IKeyManagment
{
private readonly IKeyVaultClient mKeyVaultClient;
private readonly string mKeyVaultName;
private readonly string mKeyType;
private readonly short mKeyLength;
private readonly ILogger mLogger = Log.ForContext<AzureKeyVaultKeyManagment>();

public AzureKeyVaultKeyManagment(IKeyVaultClient keyVaultClient,
IConfiguration configuration)
{
mKeyVaultClient = keyVaultClient;

mKeyVaultName = configuration["KeyVault:Name"];
mKeyType = configuration["KeyVault:KeyType"];

if (!Int16.TryParse(configuration["KeyVault:KeyLength"], out mKeyLength)){
throw new Exception($"Expected key lenght int, got {configuration["KeyVault:KeyLength"]}");
}
}

public async Task<string> Decrypt(string encryptedData, string serviceAccountId)
{
var hash = ComputeKeyId(serviceAccountId);

var keyId = $"https://{mKeyVaultName}.vault.azure.net/keys/{hash}";
try
{
var encryptionResult =
await mKeyVaultClient.DecryptAsync(keyId, "RSA-OAEP", Convert.FromBase64String(encryptedData));

return Encoding.UTF8.GetString(encryptionResult.Result);
}
catch (KeyVaultErrorException e)
{
throw new DecryptionFailureException("KeyVault decription failed", e);
}
}

public async Task<string> Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true)
{
var hash = ComputeKeyId(serviceAccountId);

var keyId = $"https://{mKeyVaultName}.vault.azure.net/keys/{hash}";

try
{
await mKeyVaultClient.GetKeyAsync(keyId);
}
catch (KeyVaultErrorException e) when (e.Response.StatusCode == HttpStatusCode.NotFound && createKeyIfMissing)
{
mLogger.Information(
"KeyVault key was not found for service account id {serviceAccount}, creating new one.",
serviceAccountId);

await mKeyVaultClient.CreateKeyAsync($"https://{mKeyVaultName}.vault.azure.net", hash, mKeyType, mKeyLength);
}

var encryptionResult = await mKeyVaultClient.EncryptAsync(keyId, "RSA-OAEP", Encoding.UTF8.GetBytes(data));

return Convert.ToBase64String(encryptionResult.Result);
}

private string ComputeKeyId(string serviceUserName)
{
return
WebEncoders.Base64UrlEncode(
SHA256.Create().ComputeHash(
Encoding.UTF8.GetBytes(serviceUserName)))
.Replace("_", "-");
}
}
}
19 changes: 19 additions & 0 deletions src/KeyManagment/IKeyManagment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Threading.Tasks;

namespace Hamuste.KeyManagment
{
public interface IKeyManagment
{
Task<string> Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true);
Task<string> Decrypt(string encryptedData, string serviceAccountId);
}

public class DecryptionFailureException : Exception
{
public DecryptionFailureException(string reason, Exception innerException) : base(reason, innerException)
{

}
}
}
31 changes: 23 additions & 8 deletions src/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Serilog;
using System.Linq;
using Microsoft.AspNetCore.Http;

using Microsoft.AspNetCore.Http;
using Hamuste.KeyManagment;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;

namespace Hamuste
{
public class Startup {
Expand Down Expand Up @@ -55,16 +59,28 @@ public void ConfigureServices (IServiceCollection services) {
config = string.IsNullOrEmpty(Configuration["Kubernetes:ProxyUrl"])
? KubernetesClientConfiguration.InClusterConfig()
: new KubernetesClientConfiguration {Host = Configuration["Kubernetes:ProxyUrl"]};


return new Kubernetes(config);
});

services.AddSingleton<IKeyVaultClient>(s =>
{
return new KeyVaultClient(GetToken);
services.AddDataProtection();

services.AddSingleton<IKeyManagment>(s => {
var provider = Configuration.GetValue<string>("KeyManagment:Provider");
if (provider == "AzureKeyVault"){
return new AzureKeyVaultKeyManagment(s.GetService<IKeyVaultClient>(), Configuration);
}
else if (provider == "AESKey")
{
var key = Configuration.GetValue<string>("KeyManagment:AES:Key");
return new AESKeyManagment(key, s.GetService<IDataProtectionProvider>());
}
else {
throw new InvalidOperationException($"Unsupported provider type: {provider}");
}
});

services.AddSingleton<IKeyVaultClient>(_ => new KeyVaultClient(GetToken));

services.AddAuthentication().AddScheme<KubernetesAuthenticationOptions, KubernetesAuthenticationHandler>("kubernetes", null);

services.AddAuthorization(options => {
Expand All @@ -80,7 +96,6 @@ public void ConfigureServices (IServiceCollection services) {

public async Task<string> GetToken(string authority, string resource, string scope)
{
Console.WriteLine("Requesting a token!");
var clientId = Configuration["ActiveDirectory:ClientId"];
var clientSecret = Configuration["ActiveDirectory:ClientSecret"];

Expand Down
9 changes: 8 additions & 1 deletion src/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
},
"KeyVault": {
"Name": "k8spoc",
"KeyType": "RSA"
"KeyType": "RSA",
"KeyLength": "2048"
},
"KeyManagment": {
"Provider": "AESKey",
"AES": {
"Key": "123456"
}
}
}
3 changes: 3 additions & 0 deletions src/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
},
"KeyManagment": {
"Provider": "AzureKeyVault"
}
}