-
Notifications
You must be signed in to change notification settings - Fork 48
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
Hashicorp Vault KMS implementation and kubernetes-example #749
Conversation
16f7a18
to
505c780
Compare
137c735
to
2ebe5ac
Compare
There's a demo of this working on Kube https://asciinema.org/a/623785 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Keith!
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Outdated
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Outdated
Show resolved
Hide resolved
...hicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsService.java
Outdated
Show resolved
Hide resolved
...hicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsService.java
Outdated
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Outdated
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Outdated
Show resolved
Hide resolved
int numAuthBits, | ||
byte[] iv, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICS, Vault does not include an immutable key id in the edek itself, but I'm inclined to include the key name here anyway. That's because I think it's better to push the bookkeeping to keep the kek id and edek together into the KMSes. AWS does this anyway, so why not make use of that? And for those KMSes which do provide an immutable key id alongside mutable key aliases it makes for a stronger contract for our Kms
abstraction to force the Kms
implementation to resolve it up front.
Coming back to Vault... it seems the only kind of key identifier it supports is a name. It also supports a delete key operation. So name is not immutable in time and space, and therefore the following is possible:
- Create key
foo
. - Generate data key
bar
. - Use data key
- Delete
foo
- You now cannot decrypt your data, but at least it's obvious why: You should get a 4xx on Vault's decrypt operation.
- Create a new
foo
. - What happens if we try to decrypt the encrypted
bar
usingfoo
now? Does it fail? Does the failure have an sensible error?
I think we need to:
- document that this is a known behaviour when using vault. We simply have to rely on users not deleting keys.
- document how it will appear to users at step 5 and step 7.
Another thing that vault lets you do is export and import keys. This means that the key with a given id could disappear for a time (export, delete, import). Or effectively get renamed (export, delete, import under a different name).
(One thing we could do to detect this situation is use the read-key operation to capture the keys
dict. We expect the keys
at encryption time to be a submap of the keys
at decryption time. We could use that fact to be able to say with certainty "Your kek has been deleted and recreated". I don't think we actually want to do this though).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I argee, the documentation is a good idea. I'll add it.
It looks like Vault associates a timestamp with each key. That would give an alternative way to detect the deleted/recreated case. I'll log the ideas I don't think we need to do them right away.
this is a key that's been rotated once
{
"request_id": "5c5584fc-2f74-438b-7dfd-cc9f2eb66864",
"lease_id": "",
"lease_duration": 0,
"renewable": false,
"data": {
"allow_plaintext_backup": false,
"auto_rotate_period": 0,
"deletion_allowed": false,
"derived": false,
"exportable": false,
"imported_key": false,
"keys": {
"1": 1701799313,
"2": 1701799318
},
"latest_version": 2,
"min_available_version": 0,
"min_decryption_version": 1,
"min_encryption_version": 0,
"name": "mykey",
"supports_decryption": true,
"supports_derivation": true,
"supports_encryption": true,
"supports_signing": false,
"type": "aes256-gcm96"
},
"warnings": null
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I raised this as #784
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Outdated
Show resolved
Hide resolved
08391b9
to
80305f0
Compare
There's still some issues I want to address, which I'll get to tomorrow morning. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM thanks @k-wall
We should probably add a Readme (a skelaton at least in this PR). To document how to configure Kroxylicious for use with Vault.
...shicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultEdekSerde.java
Outdated
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Outdated
Show resolved
Hide resolved
- name: kroxylicious | ||
image: quay.io/kroxylicious/kroxylicious-developer:0.4.0-SNAPSHOT | ||
imagePullPolicy: Always | ||
args: ["--config", "/opt/kroxylicious/config/config.yaml"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a sensible default path should we update the default (in a new PR)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose we could update the Dockerfile to assume that path, but that would require users (Docker user or Kubernetes user) to mount the config file in the same place. I'm not sure that's too helpful.
64748ad
to
9f04384
Compare
…xample to run it up. Signed-off-by: kwall <kwall@apache.org>
...ashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultResponse.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
...hicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/JsonBodyHandler.java
Outdated
Show resolved
Hide resolved
...shicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultEdekSerde.java
Outdated
Show resolved
Hide resolved
|
||
@Override | ||
public void serialize(VaultEdek edek, @NonNull ByteBuffer buffer) { | ||
var keyRefBuf = edek.kekRef().getBytes(StandardCharsets.UTF_8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDEKs get serialized in the encrypted record and may be deserialized by a different version of the Vault KMS provider. So I wonder if it's worth expending a byte on a version number, just so we've got wiggle room in the future to change the serialization schema.
You could also argue for a magic number to detect when we're trying to deserialize a EDEK that wasn't written by the vault KMS (e.g. some other KMS).
We don't have to decide either of these right now, but we should at least open an issue and have the discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added Javadoc to the VaultEdekSerde class and added a TODO to consider adding a version byte.
...shicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultEdekSerde.java
Outdated
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Outdated
Show resolved
Hide resolved
.POST(HttpRequest.BodyPublishers.noBody()) | ||
.build(); | ||
|
||
return vaultClient.sendAsync(request, statusHandler(kekRef, new JsonBodyHandler<VaultResponse<DataKeyData>>(new TypeReference<>() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What thread does this sendAsync complete on? Because IIUC that details leaks to the caller of generateDekPair
, who might need to volatile/synchronized etc to make their code threadsafe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the HTTP client by default would have been given a dedicated cached thread pool (another resource we should look at sharing across channels in future), so I imagine it will be completed from that pool, needs checking though.
The caller of generateDekPair
is already working with a CompletionStage
isn't the default assumption that any thread could be completing it and clients should code accordingly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could javadoc it and leave it up to the caller to handle switching to a different executor if required? Or I guess you could change the Kms apis so the client could optionally provide an executor they want the completion to happen on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What thread does this sendAsync complete on?
A HTTP pool thread will complete the future when the HTTP response is returned. IIUC that means that actions chained to that future will execute in that same thread, unless explicit provision is made. So in our case that means the thenApply
action in InBandKeyManager#makeKeyContext
will run on the HTTP thread. AFAICS that code looks thread safe (it uses only objects that are finals and returns a new created object via a completion stage).
If the apply in InBandKeyManager#makeKeyContext
ever needed to rely on mutable state, then it would be up to it achieve thread safety. Using thenApplyAsync
would be an option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a sentence to the Javadoc of the Kms
to say that the thread on which stages complete is undefined. That way at least the contract is explicit.
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Outdated
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to merge this and address the TODO comments in a separate PRs
...shicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultEdekSerde.java
Outdated
Show resolved
Hide resolved
Serde.putUnsignedByte(buffer, keyRefBuf.length); | ||
buffer.put(keyRefBuf); | ||
buffer.put(edek.edek()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interacting with the buffer both directly and through Serde
feels a bit odd to me. I wonder if we should have Serde.putByteArray
or Serder.copyArray
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That code now uses some Kafka Common utility method to do the unsigned varint work. I'm not sure that addresses your point. I suspect we'll want to refactor this further as we work on #767.
Hoping this is good enough for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eventually we might want a separate module just for the Serde
abstraction (which is not really KMS specific) and utility methods for interacting with ByteBuffer
, but like Keith I'm inclined to defer that for now.
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Show resolved
Hide resolved
...der-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
Show resolved
Hide resolved
…xylicious/kms/provider/hashicorp/vault/VaultEdekSerde.java Co-authored-by: Sam Barker <sam@quadrocket.co.uk> Signed-off-by: Keith Wall <kwall@redhat.com>
Co-authored-by: Tom Bentley <tombentley@users.noreply.github.com> Signed-off-by: Keith Wall <kwall@redhat.com>
bdc91d1
to
1dec9d6
Compare
@tombentley @robobario @SamBarker I hope I've addressed the review comments to your satisfaction. In some cases where I think deferring would be appropriate, I've raise a separate issue. I'm hoping this will be good enough to merge. Indeed, it you want to merge during your working day, go right ahead and squash/merge it. |
Kudos, SonarCloud Quality Gate passed! |
Type of change
Select the type of your PR
Description
kubernetes-examples/topicencryption
ready to illustrate the topic encryption. It deploys Hashicorp using the Helm Chart in a mode suitable for development (i.e. auto-unsealed).Additional Context
Why are you making this pull request?
Checklist
Please go through this checklist and make sure all applicable tasks have been done