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

added support for binary data in kamus secret #248

Merged
merged 44 commits into from
Sep 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8beb5ed
added support for binary data in kamus secret
Jul 10, 2019
d52a126
documentaion
Jul 10, 2019
d5ef1b8
fix the tests
Jul 10, 2019
acc14a5
fix the tests build
Jul 10, 2019
3a6c748
try to fix the test
Jul 10, 2019
9a7a9f7
fix the tests
Jul 10, 2019
eb7ef35
rename to encodedData
Jul 14, 2019
8857909
refactor - add v1alpha2 which is compatible with k8s secrets
Jul 24, 2019
711f805
update docs
Jul 24, 2019
fd1ddde
fix the build
Jul 24, 2019
23479fa
try to fix the tests
Jul 25, 2019
a78de1d
fix the tests
Jul 29, 2019
4a07f88
store only v2
Aug 1, 2019
044f613
try to fix the tests
Aug 1, 2019
cf88bc4
try to fix the tests
Aug 4, 2019
23dcb54
clean up
Aug 4, 2019
0ba3ba6
try to fix the build
Aug 8, 2019
f7f471d
added support for conversation webhook
Aug 12, 2019
a83e92e
revert test changes
Aug 12, 2019
5fc0701
try to fix the build
Aug 12, 2019
ae6ccae
remove encodedData
Aug 12, 2019
a0fe6a7
force https for the controller
Aug 12, 2019
65151c3
temporary - just make it work
Aug 12, 2019
00be2b2
fix the tests
Aug 13, 2019
a8cb8c0
ugly patch :praying af
Aug 13, 2019
26ed6ff
try to fix the build
Aug 13, 2019
0395f06
no judgment
Aug 13, 2019
1c9b152
enable CustomResourceWebhookConversion
Aug 13, 2019
b4392d3
try to fix the build
Aug 13, 2019
b8e723d
maybe :shrug
Aug 13, 2019
6028c23
fix the build
Aug 13, 2019
97dcfe1
try again
Aug 14, 2019
bd81c55
pleaseeee
Aug 14, 2019
6c8f091
added logging
Sep 1, 2019
4c41106
clarify docs
Sep 1, 2019
6826ab1
remove certificate from the dockerfile
Sep 4, 2019
03cb102
this time create the certificate for the proper name
Sep 4, 2019
fea90fc
Update src/crd-controller/Startup.cs
omerlh Sep 5, 2019
bc02b15
Update tests/crd-controller/deployment.yaml
omerlh Sep 5, 2019
c53ac81
Update src/crd-controller/Models/V1Alpha2/KamusSecret.cs
omerlh Sep 5, 2019
9eb09c5
fix CR comments
Sep 8, 2019
fe5aa7d
try to fix the build
Sep 9, 2019
73c7234
add comments
Sep 9, 2019
8f8249a
version bump
Sep 9, 2019
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
2 changes: 2 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ commands:
- docker_api_cache_key-{{ .Revision }}
- run:
name: install
environment:
kubernetesVersion: parameters.kubernetesVersion
command: |
tests/crd-controller/run-tests.sh << parameters.kubernetesVersion >>
no_output_timeout: 3600
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ ENV PROJECT_NAME_ENV=$PROJECT_NAME
RUN addgroup dotnet && \
adduser -D -G dotnet -h /home/dotnet dotnet && \
apk add --update --no-cahce libc6-compat

USER dotnet
WORKDIR /home/dotnet/app
ENV ASPNETCORE_URLS=http://+:9999
COPY --from=build-env /app/$PROJECT_NAME/obj/Docker/publish .

ENTRYPOINT dotnet $PROJECT_NAME_ENV.dll
Binary file added certificate.pfx
Binary file not shown.
17 changes: 15 additions & 2 deletions site/content/docs/user/crd.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Using KamusSecret allows to use Kamus with applications that requires native Kub

## Usage
KamusSecret works very similary to regular secret encryption flow with Kamus.
The encrypted data is represented in a format that is identical to regular [Kubernetes Secrets].
Kamus will create an identical secret with the decrypted content.

To encrypt the data, start by deciding to which namespace and which service account you're encrypting it.
The service account does not have to exist or used by the pod consuming the secret.
It just used for expressing who can consume this encrypted secret.
Expand All @@ -27,14 +30,16 @@ kamus-cli encrypt
```
Now that you have the data encrypted, create a KamusSecret object, using the following manifest:
```
apiVersion: "soluto.com/v1alpha1"
apiVersion: "soluto.com/v1alpha2"
kind: KamusSecret
metadata:
name: my-tls-secret //This will be the name of the secret
namespace: default //The secret and KamusSecret live in this namespace
type: TlsSecret //The type of the secret that will be created
data: //Put here all the encrypted data, that will be stored (decrypted) on the secret data
stringData: //Put here all the encrypted data, that will be stored (decrypted) on the secret data
key: J9NYLzTC/O44DvlCEZ+LfQ==:Cc9O5zQzFOyxwTD5ZHseqg==
data: //Put here base64 encoded data (usually, binary data like private keys in der format) encrypted (e.g. encrypt the value after base64 encoding it)
key2: J9NYLzTC/O44DvlCEZ+LfQ==:Cc9O5zQzFOyxwTD5ZHseqg==
serviceAccount: some-sa //The service account used for encrypting the data
```
And finally, create the KamusSecret using:
Expand All @@ -49,8 +54,16 @@ default-token-m6whl kubernetes.io/service-account-token
my-tls-secret TlsSecret 1 5s
```

## Migrating from previous version
To migrate from `v1alpha1` to `v1alpha2` all you need to do is:

* Change the key `data` to `stringData`
* Change the `apiVersion` to `"soluto.com/v1alpha2"`

## Known limitation
This is the alpha release of this feature, so not all functionality is supported.
The current known issues:

* There is no validation - so if you forgot to add mandatory keys to the KamusSecret objects, it will not be created properly.

[kubernetes secrets]: (https://kubernetes.io/docs/concepts/configuration/secret/)
112 changes: 112 additions & 0 deletions src/crd-controller/Controllers/ConversionWebhookController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Linq;
using CustomResourceDescriptorController.Models;
using k8s.Models;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using Serilog;

namespace CustomResourceDescriptorController.Controllers
{
public class ConversionWebhookController : Controller
{
private readonly ILogger mLogger = Log.ForContext<ConversionWebhookController>();

[HttpPost]
[Route("/api/v1/conversion-webhook")]
public ActionResult<ConversionReview> Convert([FromBody]ConversionReview conversionReview)
{
ConversionReviewResponse response;

mLogger.Information("Received conversion request");
try
{
response = new ConversionReviewResponse
{
UID = conversionReview.Request.UID,
ConvertedObjects = conversionReview.Request.Objects.Select(o => Convert(o, conversionReview.Request.DesiredAPIVersion)).ToArray(),
Result = new V1Status
{
Status = "Success"
}
};
}
catch (Exception e)
{
mLogger.Error(e, "Coversation failed");
response = new ConversionReviewResponse
{
UID = conversionReview.Request.UID,
Result = new V1Status
{
Status = "Failure",
Message = "Conversation failed, check logs for more details"
}
};
}

return new ConversionReview
{
Kind = conversionReview.Kind,
ApiVersion = conversionReview.ApiVersion,
Response = response
};
}

private object Convert(JObject source, string desiredApiVersion)
{
var apiVersion = source.Value<string>("apiVersion");

mLogger.Information("Starting to convert from {apiVersion} to {desirediVersion}", apiVersion, desiredApiVersion);

switch (desiredApiVersion)
{
case "soluto.com/v1alpha1":
switch (apiVersion)
{
case "soluto.com/v1alpha2":
var sourceKamusSecret = source.ToObject<Models.V1Alpha2.KamusSecret>();
return new Models.V1Alpha1.KamusSecret
{
Data = sourceKamusSecret.StringData,
ServiceAccount = sourceKamusSecret.ServiceAccount,
Metadata = sourceKamusSecret.Metadata,
Kind = "KamusSecret",
Type = sourceKamusSecret.Type,
ApiVersion = desiredApiVersion
};

default:
throw new InvalidOperationException($"Unsupported conversation from {apiVersion} to {desiredApiVersion}");
}


case "soluto.com/v1alpha2":

switch (apiVersion)
{
case "soluto.com/v1alpha1":
var sourceKamusSecret = source.ToObject<Models.V1Alpha1.KamusSecret>();
return new Models.V1Alpha2.KamusSecret
{
StringData = sourceKamusSecret.Data,
ServiceAccount = sourceKamusSecret.ServiceAccount,
Metadata = sourceKamusSecret.Metadata,
Kind = "KamusSecret",
Type = sourceKamusSecret.Type,
ApiVersion = desiredApiVersion
};

default:
throw new InvalidOperationException($"Unsupported conversation from {apiVersion} to {desiredApiVersion}");
}

default:
throw new InvalidOperationException($"Unsupported conversation from {apiVersion} to {desiredApiVersion}");
}

}
}


}
180 changes: 180 additions & 0 deletions src/crd-controller/HostedServices/V1Alpha1Controller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using CustomResourceDescriptorController.Models.V1Alpha1;
using k8s;
using k8s.Models;
using Kamus.KeyManagement;
using CustomResourceDescriptorController.Extensions;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.Extensions.Hosting;
using Serilog;
using CustomResourceDescriptorController.utils;

namespace CustomResourceDescriptorController.HostedServices
{
public class V1Alpha1Controller : IHostedService
{
private readonly IKubernetes mKubernetes;
private readonly IKeyManagement mKeyManagement;
private IDisposable mSubscription;
private readonly ILogger mAuditLogger = Log.ForContext<V1Alpha1Controller>().AsAudit();
private readonly ILogger mLogger = Log.ForContext<V1Alpha1Controller>();

public V1Alpha1Controller(IKubernetes kubernetes, IKeyManagement keyManagement)
{
this.mKubernetes = kubernetes;
this.mKeyManagement = keyManagement;
}

public Task StopAsync(CancellationToken cancellationToken)
{
if (mSubscription != null)
{
mSubscription.Dispose();
}
return Task.CompletedTask;
}

public Task StartAsync(CancellationToken token)
{
mSubscription =
mKubernetes.ObserveClusterCustomObject<KamusSecret>(
"soluto.com",
"v1alpha1",
"kamussecrets",
token)
.SelectMany(x =>
Observable.FromAsync(async () => await HandleEvent(x.Item1, x.Item2))
)
.Subscribe(
onNext: t => { },
onError: e =>
{
mLogger.Error(e, "Unexpected error occured while watching KamusSecret events");
Environment.Exit(1);
},
onCompleted: () =>
{
mLogger.Information("Watching KamusSecret events completed, terminating process");
Environment.Exit(0);
});

mLogger.Information("Starting watch for KamusSecret V1Alpha1 events");

return Task.CompletedTask;
}

private async Task HandleEvent(WatchEventType @event, KamusSecret kamusSecret)
{
try
{
mLogger.Information("Handling event of type {type}. KamusSecret {name} in namespace {namespace}",
@event.ToString(),
kamusSecret.Metadata.Name,
kamusSecret.Metadata.NamespaceProperty ?? "default");

switch (@event)
{
case WatchEventType.Added:
await HandleAdd(kamusSecret);
return;

case WatchEventType.Deleted:
await HandleDelete(kamusSecret);
return;

case WatchEventType.Modified:
await HandleModify(kamusSecret);
return;
default:
mLogger.Warning(
"Event of type {type} is not supported. KamusSecret {name} in namespace {namespace}",
@event.ToString(),
kamusSecret.Metadata.Name,
kamusSecret.Metadata.NamespaceProperty ?? "default");
return;

}
}
catch (Exception e)
{
mLogger.Error(e,
"Error while handling KamusSecret event of type {eventType}, for KamusSecret {name} on namespace {namespace}",
@event.ToString(),
kamusSecret.Metadata.Name,
kamusSecret.Metadata.NamespaceProperty ?? "default");
}
}

private async Task<V1Secret> CreateSecret(KamusSecret kamusSecret)
{
var @namespace = kamusSecret.Metadata.NamespaceProperty ?? "default";
var serviceAccount = kamusSecret.ServiceAccount;
var id = $"{@namespace}:{serviceAccount}";

mLogger.Debug("Starting decrypting KamusSecret items. KamusSecret {name} in namespace {namespace}",
kamusSecret.Metadata.Name,
@namespace);

Action<Exception, string> errorHandler = (e, key) => mLogger.Error(e,
"Failed to decrypt KamusSecret key {key}. KamusSecret {name} in namespace {namespace}",
key,
kamusSecret.Metadata.Name,
@namespace);

var decryptedStrings = await mKeyManagement.DecryptItems(kamusSecret.Data, id, errorHandler, x => x);

mLogger.Debug("KamusSecret items decrypted successfully. KamusSecret {name} in namespace {namespace}",
kamusSecret.Metadata.Name,
@namespace);

return new V1Secret
{
Metadata = new V1ObjectMeta
{
Name = kamusSecret.Metadata.Name,
NamespaceProperty = @namespace
},
Type = kamusSecret.Type,
StringData = decryptedStrings
};
}

private async Task HandleAdd(KamusSecret kamusSecret, bool isUpdate = false)
{
var secret = await CreateSecret(kamusSecret);
var createdSecret =
await mKubernetes.CreateNamespacedSecretAsync(secret, secret.Metadata.NamespaceProperty);

mAuditLogger.Information("Created a secret from KamusSecret {name} in namespace {namespace} successfully.",
kamusSecret.Metadata.Name,
secret.Metadata.NamespaceProperty);
}

private async Task HandleModify(KamusSecret kamusSecret)
{
var secret = await CreateSecret(kamusSecret);
var secretPatch = new JsonPatchDocument<V1Secret>();
secretPatch.Replace(e => e.StringData, secret.StringData);
var createdSecret = await mKubernetes.PatchNamespacedSecretAsync(
new V1Patch(secretPatch),
kamusSecret.Metadata.Name,
secret.Metadata.NamespaceProperty
);

mAuditLogger.Information("Updated a secret from KamusSecret {name} in namespace {namespace} successfully.",
kamusSecret.Metadata.Name,
secret.Metadata.NamespaceProperty);
}

private async Task HandleDelete(KamusSecret kamusSecret)
{
var @namespace = kamusSecret.Metadata.NamespaceProperty ?? "default";

await mKubernetes.DeleteNamespacedSecretAsync(kamusSecret.Metadata.Name, @namespace);
}
}
}
Loading