Skip to content

Commit

Permalink
Merge pull request #120 from Soluto/aws-kms
Browse files Browse the repository at this point in the history
Aws kms
  • Loading branch information
shaikatz authored Mar 7, 2019
2 parents c93679d + 55e3a79 commit 973d1bc
Show file tree
Hide file tree
Showing 15 changed files with 261 additions and 53 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,11 @@ Kamus has 3 components:
The encrypt and decrypt APIs handle encryption and decryption requests.
The KMS is a wrapper for various cryptographic solutions. Currently supported:
* AES - uses one key for all secrets
* Azure KeyVault - creates one key per service account.
* Google Cloud KMS - creates one key per service account.
* AWS KMS, Azure KeyVault, Google Cloud KMS - creates one key per service account.

We look forward to add support for other cloud encryption backends.


We look forward to add support for other cloud solutions, like AWS KMS.
If you're interested in such a feature, please let us know.
We would like help with testing it out.
Consult the [installation guide](docs/install.md) for more details on how to deploy Kamus using the relevant KMS.

### Utilities
Expand Down
32 changes: 31 additions & 1 deletion docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ helm upgrade --install kamus soluto/kamus --set keyManager.AES.key=$key
```

### Azure KeyVault KMS
Using [Azure KeyVault](https://azure.microsoft.com/en-us/services/key-vault/) as the key managment solution is the secure solution when running a cluster on Azure.
Using [Azure KeyVault](https://azure.microsoft.com/en-us/services/key-vault/) as the key management solution is the most secure solution when running a cluster on Azure.
Azure documentation is far from perfect, so I'm going to reffer to a lot of different guides because there is no one guide documenting the required process.

Start by creating a KeyVault instance.
Expand Down Expand Up @@ -100,3 +100,33 @@ And use the following command to deploy kamus:
```
helm upgrade --install kamus soluto/kamus -f values.yaml --set-string keyManagement.googleKms.credentials="$(cat credentials.json | base64)"
```

### AWS KMS
Using [AWS KMS](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html) as the key management solution is the most secure solution when running a cluster on AWS Cloud.
The required permissions for the IAM role/user for Kamus to work properly are KMS permissions for Encrypt, Decrypt, and GenerateDataKey.

There are 2 options to authentication with the KMS:

1. Kamus by default will try to use the regular AWS SDK discovery mechanism, if your cluster in AWS you need to map IAM role to kamus POD by using one of the community tools, for example [kiam](https://github.com/uswitch/kiam).
2. Provide user access key and secret with KMS access.

Typical values.yaml for AWS :
```yaml
keyManagement:
provider: AwsKms
```
If you want to pass user access key and secret to Kamus deploy use the following values.yaml command:
```yaml
keyManagement:
provider: AwsKms
awsKms:
region: <>
key: <>
secret: <>
```
You can also provide `cmkPrefix` values to give the custerom master keys that Kamus creates better visibility, if not specific the keys alias will be called `kamus-<GUID>`.
And now deploy Kamus using the following helm command:
```
helm upgrade --install kamus soluto/kamus -f <path/to/values.yaml>
```

44 changes: 33 additions & 11 deletions src/decrypt-api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@
using System.Linq;
using Kamus.KeyManagement;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
using Google.Apis.Auth.OAuth2;
using Google.Apis.CloudKMS.v1;
using Google.Apis.Services;
using System.IO;
using System.Reflection;

using Google.Apis.Auth.OAuth2;
using Google.Apis.CloudKMS.v1;
using Google.Apis.Services;
using System.IO;
using System.Reflection;
using Amazon;
using Amazon.KeyManagementService;

namespace Kamus
{
public class Startup {
Expand Down Expand Up @@ -73,6 +72,8 @@ public void ConfigureServices (IServiceCollection services) {
var provider = Configuration.GetValue<string>("KeyManagement:Provider");
switch (provider)
{
case "AwsKms":
return GetAwsKeyManagement(s.GetRequiredService<ILogger>());
case "GoogleKms":
return GetGoogleCloudKeyManagment();
case "AzureKeyVault":
Expand Down Expand Up @@ -147,8 +148,8 @@ public void Configure (IApplicationBuilder app, IHostingEnvironment env) {
app.UseAuthentication();

app.UseMvc ();
}

}

private IKeyManagement GetGoogleCloudKeyManagment()
{
var location = Configuration.GetValue<string>("KeyManagement:GoogleKms:Location");
Expand Down Expand Up @@ -180,5 +181,26 @@ private IKeyManagement GetGoogleCloudKeyManagment()
location,
protectionLevel);
}

private IKeyManagement GetAwsKeyManagement(ILogger logger)
{
AmazonKeyManagementServiceClient kmsService;
var region = Configuration.GetValue<string>("KeyManagement:AwsKms:Region");
var awsKey = Configuration.GetValue<string>("KeyManagement:AwsKms:Key");
var awsSecret = Configuration.GetValue<string>("KeyManagement:AwsKms:Secret");
var cmkPrefix = Configuration.GetValue<string>("KeyManagement:AwsKms:CmkPrefix");

if (string.IsNullOrEmpty(region) || string.IsNullOrEmpty(awsKey) || string.IsNullOrEmpty(awsSecret))
{
logger.Information("AwsKms credentials were not provided, using default AWS SDK credentials discovery");
kmsService = new AmazonKeyManagementServiceClient();
}
else
{
kmsService = new AmazonKeyManagementServiceClient(awsKey, awsSecret, RegionEndpoint.GetBySystemName(region));
}

return new AwsKeyManagement(kmsService, new SymmetricKeyManagement(), cmkPrefix);
}
}
}
3 changes: 2 additions & 1 deletion src/decrypt-api/decrypt-api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<Version>0.1.2.0</Version>
<Version>0.2.0.0</Version>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
Expand All @@ -13,6 +13,7 @@
<ProjectReference Include="..\key-managment\key-managment.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.KeyManagementService" Version="3.3.7.21" />
<PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="App.Metrics.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="App.Metrics.Formatters.Prometheus" Version="2.0.0" />
Expand Down
45 changes: 35 additions & 10 deletions src/encrypt-api/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.IO;
using System.Reflection;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.CloudKMS.v1;
using Google.Apis.Services;
using Amazon;
using Amazon.KeyManagementService;
using Google.Apis.Auth.OAuth2;
using Google.Apis.CloudKMS.v1;
using Google.Apis.Services;
using Kamus.KeyManagement;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
Expand Down Expand Up @@ -35,8 +37,8 @@ public Startup(IHostingEnvironment env)
.AddJsonFile("secrets/appsettings.secrets.json", optional: true)
.AddEnvironmentVariables();

Configuration = builder.Build();

Configuration = builder.Build();

var version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
Console.WriteLine($"Kamus Encryptor API {version} starting");
}
Expand All @@ -57,7 +59,9 @@ public void ConfigureServices (IServiceCollection services) {
{
var provider = Configuration.GetValue<string>("KeyManagement:Provider");
switch (provider)
{
{
case "AwsKms":
return GetAwsKeyManagement(s.GetRequiredService<ILogger>());
case "GoogleKms":
return GetGoogleCloudKeyManagment();
case "AzureKeyVault":
Expand Down Expand Up @@ -126,7 +130,7 @@ public void Configure (IApplicationBuilder app, IHostingEnvironment env) {
app.UseMvc ();
}

private IKeyManagement GetGoogleCloudKeyManagment()
private IKeyManagement GetGoogleCloudKeyManagment()
{
var location = Configuration.GetValue<string>("KeyManagement:GoogleKms:Location");
var keyRingName = Configuration.GetValue<string>("KeyManagement:GoogleKms:KeyRingName");
Expand Down Expand Up @@ -155,7 +159,28 @@ private IKeyManagement GetGoogleCloudKeyManagment()
serviceAccountCredential.ProjectId,
keyRingName,
location,
protectionLevel);
protectionLevel);
}

private IKeyManagement GetAwsKeyManagement(ILogger logger)
{
AmazonKeyManagementServiceClient kmsService;
var region = Configuration.GetValue<string>("KeyManagement:AwsKms:Region");
var awsKey = Configuration.GetValue<string>("KeyManagement:AwsKms:Key");
var awsSecret = Configuration.GetValue<string>("KeyManagement:AwsKms:Secret");
var cmkPrefix = Configuration.GetValue<string>("KeyManagement:AwsKms:CmkPrefix");

if (string.IsNullOrEmpty(region) || string.IsNullOrEmpty(awsKey) || string.IsNullOrEmpty(awsSecret))
{
logger.Information("AwsKms credentials were not provided, using default AWS SDK credentials discovery");
kmsService = new AmazonKeyManagementServiceClient();
}
else
{
kmsService = new AmazonKeyManagementServiceClient(awsKey, awsSecret, RegionEndpoint.GetBySystemName(region));
}

return new AwsKeyManagement(kmsService, new SymmetricKeyManagement(), cmkPrefix);
}
}
}
3 changes: 2 additions & 1 deletion src/encrypt-api/encrypt-api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<Version>0.1.2.0</Version>
<Version>0.2.0.0</Version>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
Expand All @@ -13,6 +13,7 @@
<ProjectReference Include="..\key-managment\key-managment.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.KeyManagementService" Version="3.3.7.21" />
<PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="App.Metrics.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="App.Metrics.Formatters.Prometheus" Version="2.0.0" />
Expand Down
84 changes: 84 additions & 0 deletions src/key-managment/AwsKeyManagement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Amazon.KeyManagementService;
using Amazon.KeyManagementService.Model;

namespace Kamus.KeyManagement
{
public class AwsKeyManagement : IKeyManagement
{
private readonly IAmazonKeyManagementService mAmazonKeyManagementService;
private readonly SymmetricKeyManagement mSymmetricKeyManagement;
private readonly string mCmkPrefix;

public AwsKeyManagement(IAmazonKeyManagementService amazonKeyManagementService, SymmetricKeyManagement symmetricKeyManagement, string cmkPrefix = "")
{
mAmazonKeyManagementService = amazonKeyManagementService;
mSymmetricKeyManagement = symmetricKeyManagement;
mCmkPrefix = cmkPrefix;
}

public async Task<string> Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true)
{
var cmkPrefix = string.IsNullOrEmpty(mCmkPrefix) ? "" : $"{mCmkPrefix}-";
var masterKeyAlias = $"alias/{cmkPrefix}kamus/{KeyIdCreator.Create(serviceAccountId)}";
var (dataKey, encryptedDataKey) = await GenerateEncryptionKey(masterKeyAlias);
mSymmetricKeyManagement.SetEncryptionKey(Convert.ToBase64String(dataKey.ToArray()));
var encryptedData = await mSymmetricKeyManagement.Encrypt(data, serviceAccountId);

return "env" + "$" + encryptedDataKey + "$" + encryptedData;

}

public async Task<string> Decrypt(string encryptedData, string serviceAccountId)
{
var encryptedDataKey = encryptedData.Split('$')[1];
var actualEncryptedData = encryptedData.Split('$')[2];

var decryptionResult = await mAmazonKeyManagementService.DecryptAsync(new DecryptRequest
{
CiphertextBlob = new MemoryStream(Convert.FromBase64String(encryptedDataKey)),
});

var dataKey = ConvertMemoryStreamToBase64String(decryptionResult.Plaintext);

mSymmetricKeyManagement.SetEncryptionKey(dataKey);
return await mSymmetricKeyManagement.Decrypt(actualEncryptedData, serviceAccountId);
}

private static string ConvertMemoryStreamToBase64String(MemoryStream ms)
{
return Convert.ToBase64String(ms.ToArray());
}

private async Task<(MemoryStream dataKey, string encryptedDataKey)> GenerateEncryptionKey(string keyAlias)
{
GenerateDataKeyResponse generateKeyResponse = null;
try
{
generateKeyResponse = await mAmazonKeyManagementService.GenerateDataKeyAsync(new GenerateDataKeyRequest { KeyId = keyAlias, KeySpec = "AES_256"});

}
catch (NotFoundException)
{
await GenerateMasterKey(keyAlias);
generateKeyResponse = await mAmazonKeyManagementService.GenerateDataKeyAsync(new GenerateDataKeyRequest { KeyId = keyAlias, KeySpec = "AES_256"});
}

if (generateKeyResponse.HttpStatusCode != HttpStatusCode.OK)
{
throw new Exception($"Couldn't generate encryption key for {keyAlias}");
}

return (generateKeyResponse.Plaintext, ConvertMemoryStreamToBase64String(generateKeyResponse.CiphertextBlob));
}

private async Task GenerateMasterKey(string keyAlias)
{
var createKeyResponse = await mAmazonKeyManagementService.CreateKeyAsync(new CreateKeyRequest());
await mAmazonKeyManagementService.CreateAliasAsync(keyAlias, createKeyResponse.KeyMetadata.KeyId);
}
}
}
13 changes: 2 additions & 11 deletions src/key-managment/AzureKeyVaultKeyManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public AzureKeyVaultKeyManagement(IKeyVaultClient keyVaultClient,

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

var keyId = $"https://{mKeyVaultName}.vault.azure.net/keys/{hash}";
try
Expand All @@ -52,7 +52,7 @@ public async Task<string> Decrypt(string encryptedData, string serviceAccountId)

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

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

Expand All @@ -73,14 +73,5 @@ public async Task<string> Encrypt(string data, string serviceAccountId, bool cre

return Convert.ToBase64String(encryptionResult.Result);
}

private string ComputeKeyId(string serviceUserName)
{
return
WebEncoders.Base64UrlEncode(
SHA256.Create().ComputeHash(
Encoding.UTF8.GetBytes(serviceUserName)))
.Replace("_", "-");
}
}
}
13 changes: 2 additions & 11 deletions src/key-managment/GoogleCloudKeyManagment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public GoogleCloudKeyManagment(

public async Task<string> Decrypt(string encryptedData, string serviceAccountId)
{
var safeId = ComputeKeyId(serviceAccountId);
var safeId = KeyIdCreator.Create(serviceAccountId);
var cryptoKeys = mKmsService.Projects.Locations.KeyRings.CryptoKeys;
var keyringId = $"projects/{mProjectName}/locations/{mKeyringLocation}/keyRings/{mKeyringName}";
var keyId = $"{keyringId}/cryptoKeys/{safeId}";
Expand All @@ -50,7 +50,7 @@ public async Task<string> Decrypt(string encryptedData, string serviceAccountId)

public async Task<string> Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true)
{
var safeId = ComputeKeyId(serviceAccountId);
var safeId = KeyIdCreator.Create(serviceAccountId);
var cryptoKeys = mKmsService.Projects.Locations.KeyRings.CryptoKeys;
var keyringId = $"projects/{mProjectName}/locations/{mKeyringLocation}/keyRings/{mKeyringName}";
var keyId = $"{keyringId}/cryptoKeys/{safeId}";
Expand Down Expand Up @@ -81,14 +81,5 @@ public async Task<string> Encrypt(string data, string serviceAccountId, bool cre

return encryted.Ciphertext;
}

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

0 comments on commit 973d1bc

Please sign in to comment.