From 3a8c4323a6c2bc20bf409c12d60ee95fbd7636ee Mon Sep 17 00:00:00 2001 From: omerlh Date: Sun, 6 Jan 2019 23:55:28 +0200 Subject: [PATCH 1/6] encryption working --- src/key-managment/GoogleCloudKeyManagment.cs | 77 ++++++++++++++++++++ src/key-managment/key-managment.csproj | 1 + 2 files changed, 78 insertions(+) create mode 100644 src/key-managment/GoogleCloudKeyManagment.cs diff --git a/src/key-managment/GoogleCloudKeyManagment.cs b/src/key-managment/GoogleCloudKeyManagment.cs new file mode 100644 index 000000000..e24785f1a --- /dev/null +++ b/src/key-managment/GoogleCloudKeyManagment.cs @@ -0,0 +1,77 @@ +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; + + public GoogleCloudKeyManagment( + CloudKMSService kmsService, + string projectName, + string keyringName, + string keyringLocation) + { + mKmsService = kmsService; + mProjectName = projectName; + mKeyringName = keyringName; + mKeyringLocation = keyringLocation; + } + + + public Task Decrypt(string encryptedData, string serviceAccountId) + { + throw new NotImplementedException(); + } + + public async Task 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 + { + var key = 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" + }; + + 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("_", "-"); + } + } +} diff --git a/src/key-managment/key-managment.csproj b/src/key-managment/key-managment.csproj index fa96103af..2ad6f9588 100644 --- a/src/key-managment/key-managment.csproj +++ b/src/key-managment/key-managment.csproj @@ -6,6 +6,7 @@ + From e7067eabdaf37acee407f2fe4e3ccb6d763edf5d Mon Sep 17 00:00:00 2001 From: omerlh Date: Mon, 7 Jan 2019 08:37:22 +0200 Subject: [PATCH 2/6] now deceryption is working + add support for protection level --- src/key-managment/GoogleCloudKeyManagment.cs | 29 ++++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/key-managment/GoogleCloudKeyManagment.cs b/src/key-managment/GoogleCloudKeyManagment.cs index e24785f1a..f7d7da6a8 100644 --- a/src/key-managment/GoogleCloudKeyManagment.cs +++ b/src/key-managment/GoogleCloudKeyManagment.cs @@ -16,40 +16,57 @@ public class GoogleCloudKeyManagment : IKeyManagement 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 keyringLocation, + string protectionLevel) { mKmsService = kmsService; mProjectName = projectName; mKeyringName = keyringName; mKeyringLocation = keyringLocation; + mProtectionLevel = protectionLevel; } - public Task Decrypt(string encryptedData, string serviceAccountId) + public async Task Decrypt(string encryptedData, string serviceAccountId) { - throw new NotImplementedException(); + 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 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 keyringId = $"projects/{mProjectName}/locations/{mKeyringLocation}/keyRings/{mKeyringName}"; var keyId = $"{keyringId}/cryptoKeys/{safeId}"; try { - var key = await cryptoKeys.Get(keyId).ExecuteAsync(); + 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" + Purpose = "ENCRYPT_DECRYPT", + VersionTemplate = new CryptoKeyVersionTemplate + { + ProtectionLevel = mProtectionLevel + } }; var request = cryptoKeys.Create(key, keyringId); From 9b7c94140d472ad8a4a34c694e914848b66e9f95 Mon Sep 17 00:00:00 2001 From: omerlh Date: Mon, 7 Jan 2019 08:37:39 +0200 Subject: [PATCH 3/6] added tests for google KMS --- tests/integration/GoogleCloudKeyManagment.cs | 72 ++++++++++++++++++++ tests/integration/integration.csproj | 1 + 2 files changed, 73 insertions(+) create mode 100644 tests/integration/GoogleCloudKeyManagment.cs diff --git a/tests/integration/GoogleCloudKeyManagment.cs b/tests/integration/GoogleCloudKeyManagment.cs new file mode 100644 index 000000000..ddd165b5a --- /dev/null +++ b/tests/integration/GoogleCloudKeyManagment.cs @@ -0,0 +1,72 @@ +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 string mKeyringId; + 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("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("KeyManagment:GoogleKms:Location"); + var keyRingName = mConfiguration.GetValue("KeyManagment:GoogleKms:KeyRingName"); + var protectionLevel = mConfiguration.GetValue("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); + + } + } +} diff --git a/tests/integration/integration.csproj b/tests/integration/integration.csproj index 0ae0ae1bb..3aaa89cb2 100644 --- a/tests/integration/integration.csproj +++ b/tests/integration/integration.csproj @@ -7,6 +7,7 @@ + From 85ffa3e26cd7182527977a9f5a1d22237a5e72ea Mon Sep 17 00:00:00 2001 From: omerlh Date: Mon, 7 Jan 2019 09:38:26 +0200 Subject: [PATCH 4/6] added support for KMS in the API --- src/decrypt-api/Startup.cs | 40 ++++++++++++++++++- .../Controllers/EncryptController.cs | 3 -- src/encrypt-api/Startup.cs | 40 ++++++++++++++++++- tests/integration/GoogleCloudKeyManagment.cs | 1 - 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/src/decrypt-api/Startup.cs b/src/decrypt-api/Startup.cs index ade23ac5d..ac305eaf9 100644 --- a/src/decrypt-api/Startup.cs +++ b/src/decrypt-api/Startup.cs @@ -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 { @@ -67,6 +71,8 @@ public void ConfigureServices (IServiceCollection services) { var provider = Configuration.GetValue("KeyManagement:Provider"); switch (provider) { + case "GoogleKms": + return GetGoogleCloudKeyManagment(); case "AzureKeyVault": return new EnvelopeEncryptionDecorator( new AzureKeyVaultKeyManagement(s.GetService(), Configuration), @@ -139,6 +145,38 @@ public void Configure (IApplicationBuilder app, IHostingEnvironment env) { app.UseAuthentication(); app.UseMvc (); + } + + private IKeyManagement GetGoogleCloudKeyManagment() + { + var location = Configuration.GetValue("KeyManagement:GoogleKms:Location"); + var keyRingName = Configuration.GetValue("KeyManagement:GoogleKms:KeyRingName"); + var protectionLevel = Configuration.GetValue("KeyManagement:GoogleKms:ProtectionLevel"); + var credentialsPath = Configuration.GetValue("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); } } } \ No newline at end of file diff --git a/src/encrypt-api/Controllers/EncryptController.cs b/src/encrypt-api/Controllers/EncryptController.cs index fa41a2051..a87f49294 100644 --- a/src/encrypt-api/Controllers/EncryptController.cs +++ b/src/encrypt-api/Controllers/EncryptController.cs @@ -18,9 +18,6 @@ public class EncryptController : Controller private readonly IKeyManagement mKeyManagement; private readonly ILogger mAuditLogger = Log.ForContext().AsAudit(); private readonly ILogger mLogger = Log.ForContext(); - - //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) { diff --git a/src/encrypt-api/Startup.cs b/src/encrypt-api/Startup.cs index eea3a4a29..59702d6ac 100644 --- a/src/encrypt-api/Startup.cs +++ b/src/encrypt-api/Startup.cs @@ -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; @@ -51,7 +55,9 @@ public void ConfigureServices (IServiceCollection services) { { var provider = Configuration.GetValue("KeyManagement:Provider"); switch (provider) - { + { + case "GoogleKms": + return GetGoogleCloudKeyManagment(); case "AzureKeyVault": return new EnvelopeEncryptionDecorator( new AzureKeyVaultKeyManagement(s.GetService(), Configuration), @@ -117,5 +123,37 @@ public void Configure (IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc (); } + + private IKeyManagement GetGoogleCloudKeyManagment() + { + var location = Configuration.GetValue("KeyManagement:GoogleKms:Location"); + var keyRingName = Configuration.GetValue("KeyManagement:GoogleKms:KeyRingName"); + var protectionLevel = Configuration.GetValue("KeyManagement:GoogleKms:ProtectionLevel"); + var credentialsPath = Configuration.GetValue("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); + } } } \ No newline at end of file diff --git a/tests/integration/GoogleCloudKeyManagment.cs b/tests/integration/GoogleCloudKeyManagment.cs index ddd165b5a..c815a64ce 100644 --- a/tests/integration/GoogleCloudKeyManagment.cs +++ b/tests/integration/GoogleCloudKeyManagment.cs @@ -13,7 +13,6 @@ namespace integration public class GoogleCloudKeyManagmentTests { private readonly IKeyManagement mGoogleCloudKeyManagement; - private readonly string mKeyringId; private readonly CloudKMSService mCloudKmsService; private readonly IConfiguration mConfiguration; From e0b11168f8074dbf8cb8561771c8c2381eb467ff Mon Sep 17 00:00:00 2001 From: omerlh Date: Mon, 7 Jan 2019 09:39:49 +0200 Subject: [PATCH 5/6] push images temporary from branch --- ci/codefresh.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ci/codefresh.yml b/ci/codefresh.yml index 9856094dc..50440805f 100644 --- a/ci/codefresh.yml +++ b/ci/codefresh.yml @@ -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}} \ No newline at end of file From 387d32b3720eb7f80972a75154a3ce7bf4013474 Mon Sep 17 00:00:00 2001 From: omerlh Date: Mon, 7 Jan 2019 09:44:54 +0200 Subject: [PATCH 6/6] fix image tag --- ci/codefresh.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/codefresh.yml b/ci/codefresh.yml index 50440805f..c40786421 100644 --- a/ci/codefresh.yml +++ b/ci/codefresh.yml @@ -67,7 +67,7 @@ steps: title: Push Encryptor Api - lastest candidate: ${{BuildingEncryptorDockerImage}} tags: - - encryptor-${{CF_BRANCH_TAG_NORMALIZED}}- + - encryptor-${{CF_BRANCH_TAG_NORMALIZED}} credentials: username: ${{DOCKERHUB_REGISTRY_USERNAME}} password: ${{DOCKERHUB_REGISTRY_PASSWORD}} @@ -76,7 +76,7 @@ steps: title: Push Decryptor Api - lastest candidate: ${{BuildingDecryptorDockerImage}} tags: - - decryptor-${{CF_BRANCH_TAG_NORMALIZED}}- + - decryptor-${{CF_BRANCH_TAG_NORMALIZED}} credentials: username: ${{DOCKERHUB_REGISTRY_USERNAME}} password: ${{DOCKERHUB_REGISTRY_PASSWORD}} \ No newline at end of file