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

[WIP] Add support for GCP KMS #66

Merged
merged 6 commits into from
Jan 8, 2019
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
12 changes: 2 additions & 10 deletions ci/codefresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,16 @@ steps:
title: Push Encryptor Api - lastest
candidate: ${{BuildingEncryptorDockerImage}}
tags:
- encryptor-latest
- encryptor-${{CF_BRANCH_TAG_NORMALIZED}}
credentials:
username: ${{DOCKERHUB_REGISTRY_USERNAME}}
password: ${{DOCKERHUB_REGISTRY_PASSWORD}}
when:
branch:
only:
- master
PushDecryptorImage:
type: push
title: Push Decryptor Api - lastest
when:
branch:
only:
- master
candidate: ${{BuildingDecryptorDockerImage}}
tags:
- decryptor-latest
- decryptor-${{CF_BRANCH_TAG_NORMALIZED}}
credentials:
username: ${{DOCKERHUB_REGISTRY_USERNAME}}
password: ${{DOCKERHUB_REGISTRY_PASSWORD}}
40 changes: 39 additions & 1 deletion src/decrypt-api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
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;

namespace Kamus
{
public class Startup {
Expand Down Expand Up @@ -67,6 +71,8 @@ public void ConfigureServices (IServiceCollection services) {
var provider = Configuration.GetValue<string>("KeyManagement:Provider");
switch (provider)
{
case "GoogleKms":
return GetGoogleCloudKeyManagment();
case "AzureKeyVault":
return new EnvelopeEncryptionDecorator(
new AzureKeyVaultKeyManagement(s.GetService<IKeyVaultClient>(), Configuration),
Expand Down Expand Up @@ -139,6 +145,38 @@ public void Configure (IApplicationBuilder app, IHostingEnvironment env) {
app.UseAuthentication();

app.UseMvc ();
}

private IKeyManagement GetGoogleCloudKeyManagment()
{
var location = Configuration.GetValue<string>("KeyManagement:GoogleKms:Location");
var keyRingName = Configuration.GetValue<string>("KeyManagement:GoogleKms:KeyRingName");
var protectionLevel = Configuration.GetValue<string>("KeyManagement:GoogleKms:ProtectionLevel");
var credentialsPath = Configuration.GetValue<string>("KeyManagement:GoogleKms:CredentialsPath");

var serviceAccountCredential = ServiceAccountCredential.FromServiceAccountData(File.OpenRead(credentialsPath));
var credentials = GoogleCredential.FromServiceAccountCredential(serviceAccountCredential);
if (credentials.IsCreateScopedRequired)
{
credentials = credentials.CreateScoped(new[]
{
CloudKMSService.Scope.CloudPlatform
});
}

var kmsService = new CloudKMSService(new BaseClientService.Initializer
{
HttpClientInitializer = credentials,
GZipEnabled = true
});


return new GoogleCloudKeyManagment(
kmsService,
serviceAccountCredential.ProjectId,
keyRingName,
location,
protectionLevel);
}
}
}
3 changes: 0 additions & 3 deletions src/encrypt-api/Controllers/EncryptController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ public class EncryptController : Controller
private readonly IKeyManagement mKeyManagement;
private readonly ILogger mAuditLogger = Log.ForContext<EncryptController>().AsAudit();
private readonly ILogger mLogger = Log.ForContext<EncryptController>();

//see: https://github.com/kubernetes/kubernetes/blob/d5803e596fc8aba17aa8c74a96aff9c73bb0f1da/staging/src/k8s.io/apiserver/pkg/authentication/serviceaccount/util.go#L27
private const string ServiceAccountUsernamePrefix = "system:serviceaccount:";

public EncryptController(IKeyManagement keyManagement)
{
Expand Down
40 changes: 39 additions & 1 deletion src/encrypt-api/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System;
using System.IO;
using System.Threading.Tasks;
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 @@ -51,7 +55,9 @@ public void ConfigureServices (IServiceCollection services) {
{
var provider = Configuration.GetValue<string>("KeyManagement:Provider");
switch (provider)
{
{
case "GoogleKms":
return GetGoogleCloudKeyManagment();
case "AzureKeyVault":
return new EnvelopeEncryptionDecorator(
new AzureKeyVaultKeyManagement(s.GetService<IKeyVaultClient>(), Configuration),
Expand Down Expand Up @@ -117,5 +123,37 @@ public void Configure (IApplicationBuilder app, IHostingEnvironment env) {

app.UseMvc ();
}

private IKeyManagement GetGoogleCloudKeyManagment()
{
var location = Configuration.GetValue<string>("KeyManagement:GoogleKms:Location");
var keyRingName = Configuration.GetValue<string>("KeyManagement:GoogleKms:KeyRingName");
var protectionLevel = Configuration.GetValue<string>("KeyManagement:GoogleKms:ProtectionLevel");
var credentialsPath = Configuration.GetValue<string>("KeyManagement:GoogleKms:CredentialsPath");

var serviceAccountCredential = ServiceAccountCredential.FromServiceAccountData(File.OpenRead(credentialsPath));
var credentials = GoogleCredential.FromServiceAccountCredential(serviceAccountCredential);
if (credentials.IsCreateScopedRequired)
{
credentials = credentials.CreateScoped(new[]
{
CloudKMSService.Scope.CloudPlatform
});
}

var kmsService = new CloudKMSService(new BaseClientService.Initializer
{
HttpClientInitializer = credentials,
GZipEnabled = true
});


return new GoogleCloudKeyManagment(
kmsService,
serviceAccountCredential.ProjectId,
keyRingName,
location,
protectionLevel);
}
}
}
94 changes: 94 additions & 0 deletions src/key-managment/GoogleCloudKeyManagment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Google;
using Google.Apis.CloudKMS.v1;
using Google.Apis.CloudKMS.v1.Data;
using Microsoft.AspNetCore.WebUtilities;

namespace Kamus.KeyManagement
{
public class GoogleCloudKeyManagment : IKeyManagement
{
private readonly CloudKMSService mKmsService;
private readonly string mProjectName;
private readonly string mKeyringName;
private readonly string mKeyringLocation;
private readonly string mProtectionLevel;

public GoogleCloudKeyManagment(
CloudKMSService kmsService,
string projectName,
string keyringName,
string keyringLocation,
string protectionLevel)
{
mKmsService = kmsService;
mProjectName = projectName;
mKeyringName = keyringName;
mKeyringLocation = keyringLocation;
mProtectionLevel = protectionLevel;
}


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

var result = await cryptoKeys.Decrypt(new DecryptRequest
{
Ciphertext = encryptedData
}, keyId).ExecuteAsync();

return result.Plaintext;
}

public async Task<string> Encrypt(string data, string serviceAccountId, bool createKeyIfMissing = true)
{
var safeId = ComputeKeyId(serviceAccountId);
var cryptoKeys = mKmsService.Projects.Locations.KeyRings.CryptoKeys;
var keyringId = $"projects/{mProjectName}/locations/{mKeyringLocation}/keyRings/{mKeyringName}";
var keyId = $"{keyringId}/cryptoKeys/{safeId}";
try
{
await cryptoKeys.Get(keyId).ExecuteAsync();
} catch (GoogleApiException e) when (e.HttpStatusCode == HttpStatusCode.NotFound && createKeyIfMissing)
{
//todo: handle key rotation - currently set to never expired
var key = new CryptoKey
{
Purpose = "ENCRYPT_DECRYPT",
VersionTemplate = new CryptoKeyVersionTemplate
{
ProtectionLevel = mProtectionLevel
}
};

var request = cryptoKeys.Create(key, keyringId);
request.CryptoKeyId = safeId;
await request.ExecuteAsync();
}

var encryted = await cryptoKeys.Encrypt(new EncryptRequest
{
Plaintext = data
}, keyId).ExecuteAsync();

return encryted.Ciphertext;
}

private string ComputeKeyId(string serviceUserName)
{
return
WebEncoders.Base64UrlEncode(
SHA256.Create().ComputeHash(
Encoding.UTF8.GetBytes(serviceUserName)))
.Replace("_", "-");
}
}
}
1 change: 1 addition & 0 deletions src/key-managment/key-managment.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Apis.CloudKMS.v1" Version="1.36.1.1443" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.1.1" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
Expand Down
71 changes: 71 additions & 0 deletions tests/integration/GoogleCloudKeyManagment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.CloudKMS.v1;
using Google.Apis.Services;
using Kamus.KeyManagement;
using Microsoft.Extensions.Configuration;
using Xunit;

namespace integration
{
public class GoogleCloudKeyManagmentTests
{
private readonly IKeyManagement mGoogleCloudKeyManagement;
private readonly CloudKMSService mCloudKmsService;
private readonly IConfiguration mConfiguration;

public GoogleCloudKeyManagmentTests()
{
mConfiguration = new ConfigurationBuilder()
.AddJsonFile("settings.json")
.AddEnvironmentVariables().Build();

var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(mConfiguration.GetValue<string>("KeyManagment:GoogleKms:Credentials"));
writer.Flush();
stream.Position = 0;
var serviceAccountCredential = ServiceAccountCredential.FromServiceAccountData(stream);
var credentials = GoogleCredential.FromServiceAccountCredential(serviceAccountCredential);
if (credentials.IsCreateScopedRequired)
{
credentials = credentials.CreateScoped(new[]
{
CloudKMSService.Scope.CloudPlatform
});
}

mCloudKmsService = new CloudKMSService(new BaseClientService.Initializer
{
HttpClientInitializer = credentials,
GZipEnabled = true
});
var location = mConfiguration.GetValue<string>("KeyManagment:GoogleKms:Location");
var keyRingName = mConfiguration.GetValue<string>("KeyManagment:GoogleKms:KeyRingName");
var protectionLevel = mConfiguration.GetValue<string>("KeyManagment:GoogleKms:ProtectionLevel");

mGoogleCloudKeyManagement = new GoogleCloudKeyManagment(
mCloudKmsService,
serviceAccountCredential.ProjectId,
keyRingName,
location,
protectionLevel);
}



[Fact]
public async Task TestFullFlow()
{
var sa = "sa:namespace";
var data = "data";
var encrypted = await mGoogleCloudKeyManagement.Encrypt(data, sa);
var decrypted = await mGoogleCloudKeyManagement.Decrypt(encrypted, sa);

Assert.Equal(data, decrypted);

}
}
}
1 change: 1 addition & 0 deletions tests/integration/integration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<ProjectReference Include="..\..\src\key-managment\key-managment.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Google.Apis.CloudKMS.v1" Version="1.36.1.1443" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.2" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="4.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
Expand Down