-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Add support for Vault #2897
Add support for Vault #2897
Conversation
The windows build is failing due to docker:
What is the configuration of the windows machine that runs the ci. Does it have docker installed? |
extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java
Outdated
Show resolved
Hide resolved
I do like what you have done, but the general issue I'm seeing is that we have a lot of functionality included here that exists in other extensions such as rest proxies, json binding, and the vault service itself would really have to be a separate extension that provides access to the vault features we would want to support. We'll discuss this and come back with how to proceed. |
I agree with the general issue you are stating. I had pointed this out in the poc back in april and in paragraph "Future improvements to be discussed" above. The question to me was to either go bigger first, possibly drawing more dependencies on other contributors or teams (like I had on agroal with AG-116) hence delaying the feature, or go smaller, with the perspective of having to do some rework at some point, but bringing value more quickly. Obviously I chose the later (but preferably I would have liked to go big and fast), because I believe there is already a fair amount of value in bringing vault's support with respect to db connection configuration, as this is by far the primary use case for storing secrets. The compromise I found was to not expose anything to the user beside the configuration parameters, making all I have done an implementation detail. The idea was that as soon as there would be a vault extension available, we could delegate a fair amount of logic to this new extension, and remove code from the agroal extension. The only visible effect for the user would be then if vault's configuration properties were to be changed. The discussion is really about time to market vs doing the right thing. If we can do both, I am all for it. |
The only difference I see with the existing integration tests that already use testcontainers is that I am using the docker java api, as opposed to relying on the fabric8 docker-maven-plugin, which is useful for me because I have plenty of docker exec to do for vault. Since I saw a reference to testcontainers in the bom, I assumed it was already used in quarkus, but I do not see any other reference in the code. And tests that are running the docker-maven-plugin are all from the integration-tests module, which are disabled (the jpa-X tests). So may be you are right there is no docker?? |
@cescoffier do you know if the Windows CI machine has docker installed? |
@starksm64 I reworked the code base, and came up with a vault extension, and an integration inside agroal. The extension can be used at 2 levels:
As suggested in your review, the VaultProxy is now based on resteasy+jsonb. TLS is handled consistently through a trust store. For instance for k8s (default):
and for userpass:
So that reading secrets is as easy as:
I have also developed the agroal/vault integration, which works both with static secrets and dynamic db credentials. Everything is done through configuration:
The agroal integration provides a simple cache that will return the previously fetched secret for some time period, or whenever vault is unavailable. I upgraded my test application https://github.com/vsevel/vaultapp and got it to deploy and run in k8s. That's for the good news. For the bad news, the biggest disappointment came from the fact that there is an indirect dependency between quarkus-smallrye-rest-client-deployment and quarkus-agroal-deployment through quarkus-smallrye-fault-tolerance-deployment --> quarkus-smallrye-context-propagation-deployment --> quarkus-hibernate-orm-panache-deployment --> quarkus-hibernate-orm-deployment If the rest->agroal dependency cannot be amended, one solution would be to rely on an indirection (eg: config property resolver) as discussed in the google groups thread, which would make it easier to integrate in any other consumer as a side benefit. But this is above my knowledge at this point. I have left the code in agroal commented-out to support discussions if needed. Beside this showstopper, I have left a few TODO in the code, and I had to disable the vault integration test on windows to make the build pass. I hope this PR can be of value for discussions around vault's support in quarkus. |
As it turned out, the dependency cycle was only for RestClientFallbackTest. |
I completed support for a vault config source. For instance you can write in vault: Define the following policy & role in vault:
Then all you need in application.properties is:
As a side benefit, url can be expressed naturally as quarkus.vault.url, and the rest client property io.quarkus.vault.VaultProxy/mp-rest/url will be transparently provisionned. properties can also be used from the app through ConfigProvider.getConfig() or injection (ie. @ConfigProperty), like any other property. properties coming from vault will be cached in the vault config source for some cache-period duration. I refactored agroal's code to replace the use of SimplePassword by a new class ConfigPropertyPassword that draws the password value from the configuration. Another benefit is that for that use case we do not need the dependency of agroal on the vault extension module (since we get the password from the configuration). The vault config source still honors token extension and renewal (ttl and max-ttl). Since the config source is initialized before any modules are available (including the rest client), I had to rely on a simple apache httpclient for the few rest calls that are done from the vault config source. The other features have been preserved (VaultService and VaultProxy rest client). Beside the usual integration tests, a full example is provided in: |
@vsevel can you please resolve the |
done. thanks for your interest. |
@vsevel thanks; I do plan to experiment with your PR and I'm positive it will make it into the master eventually as the vault concept has its niche. Sorry it will take a bit of time (so more conflicts may need to be resolved going forward :-) ) since I've just returned from PTO and have to deal with some other pending issues. |
on a different subject, I saw that the kubernetes client extension was using the okhttp http client library. may be that is a better fit than apache httpclient for the VaultConfigSource. I will experiment with it at some point. |
extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultDynamicDbSecretService.java
Outdated
Show resolved
Hide resolved
extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultBaseProxy.java
Outdated
Show resolved
Hide resolved
extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultConfigSource.java
Outdated
Show resolved
Hide resolved
extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultProxy.java
Outdated
Show resolved
Hide resolved
extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultProxyImpl.java
Outdated
Show resolved
Hide resolved
extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultServerRuntimeConfig.java
Outdated
Show resolved
Hide resolved
extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultServiceImpl.java
Outdated
Show resolved
Hide resolved
extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultServiceImpl.java
Outdated
Show resolved
Hide resolved
extensions/vault/runtime/src/main/java/io/quarkus/vault/VaultService.java
Outdated
Show resolved
Hide resolved
extensions/vault/runtime/src/main/java/io/quarkus/vault/proxy/dto/VaultCredsData.java
Outdated
Show resolved
Hide resolved
@vsevel I've commented on the source as well, you have a rest client dependency and Mp Rest Client proxy but I'm not seeing it being used, don't understand why the client HTTP invocations are coded directly with Apache Http Client. (and the Kub cllient code should probably have avoided bringing one more rest client library as well) |
the Mp Rest Client proxy is used in agroal (see VaultDynamicPassword), and client applications that would want to access the vault directly from their business logic. like I said before, if the vault config source could use the rest client, that would simplify a lot. to be honest, I was wondering if this PR was too ambitious. and I was thinking that may be it should focus on the config source use case, which is already bringing a lot of value (like the zookeeper config source). and providing a VaultProxy and upper abstraction VaultService could come later. |
@vsevel thanks for the clarifications so far. After looking more carefully at the code and the docs, I honestly think
As such I propose to drop What do you think ? |
in reply to your last comment
VaultAuthService, VaultDynamicDbSecretService is internal impl stuff. I renamed those into XXXManager to not get them confused with VaultService, which is public api.
even if we do not make VaultProxy available to the app developer, we still need it as an implementation detail, as VaultService needs it. one way to approach it is to only document VaultService (from an app developer's perspective) and leave the rest as an impl detail (even if most of the classes are public).
that would be awesome summaryHere is a summary of things I believe we need to nail down:
|
@vsevel thanks... So the 1st issue, before anything else, which I'd like to get resolved is VaultProxy vs VaultService. This is why my proposal is to offer a REST client proxy (note does not mean it has to be implemented with MP RESTClient), whatever it is called, say
Next, once the users start having more involved requirements, Lets start from supporting the most main stream use case for the users to appreciate the vault faster, and they will drive it further. Thoughts ? |
@sberyozkin hello. no problem in putting the focus on VaultService. But it is not clear to me what you want to do with VaultProxy. drop it? it is not possible as it is being used by VaultService. It is just a rest interface. |
Hi @vsevel, right, I suggest to drop the proxy class, and MP REST Client won't give the flexibility required to support the under the hood login/token acquisition/etc which are needed to have a higher level secret acquisition completed. So the idea is to have the Apache HTTP Client code replaced with the JAX-RS client API since it is offered by Quarkus natively and move it into
and this is it, VaultServiceProducer will create it, Agroal will use, the config source will use it, user code will use it. Inside we have the control required to do some smart things the Vault supports to ensure the secrets can be rotated, etc. Does it make sense ? Thanks |
@vsevel thanks; it was a known issue already resolved for 0.24.0. |
@vsevel Hi Vincent, I'm a bit concerned about you keeping growing the PR, I mean it is a good thing, but I'm worried it can just delay the merge as the PR is large. We've done a lot of review work with yourself, and you are now going to finalize with @gsmet, so please wait a bit and then start applying small PRs against the master :-) Hope you agree, thanks |
hello @sberyozkin you are right. as the PR moved from one milestone to another, I thought I had time to implement the approle authentication, which is probably more useful than userpass, if the app+vault is running outside k8s. and I also refactored the config for authentication and tls (moved properties in dedicated classes, rather than having everything at the root level). I thought this was better to do it now, than doing breaking changes later. plus I was able to drop some properties as well (authentication_type is now inferred, and the ca cert is also defaulted when running in k8s). this should improve the developer experience. btw I have the call with guillaume on thursday. so I will know more. I will do some more testing in the meantime. the last build failed with an error that seemed to affect other builds as well thanks for your benevolent eye |
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 added 2 minor additional comments following our discussion this morning. Can you take care of them and then squash everything?
But I have a bigger issue that I didn't think of during our discussions: we don't want to mandate Docker when running the tests. So somehow these tests should be disabled and we should have a -Dtest-vault
to enable them if they need Docker to be there. See what we did in the Elasticsearch IT.
I got that because I wasn't able to get the tests to run.
I have:
Caused by: java.lang.IllegalStateException: Can not connect to Ryuk
for one
And:
Caused by: org.testcontainers.containers.ContainerLaunchException: Timed out waiting for container port to open (localhost ports: [8200] should be listening)
at org.testcontainers.containers.wait.strategy.HostPortWaitStrategy.waitUntilReady(HostPortWaitStrategy.java:47)
at org.testcontainers.containers.wait.strategy.AbstractWaitStrategy.waitUntilReady(AbstractWaitStrategy.java:35)
at org.testcontainers.containers.wait.HostPortWaitStrategy.waitUntilReady(HostPortWaitStrategy.java:23)
at org.testcontainers.containers.wait.strategy.AbstractWaitStrategy.waitUntilReady(AbstractWaitStrategy.java:35)
at org.testcontainers.containers.GenericContainer.waitUntilContainerStarted(GenericContainer.java:675)
at org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:332)
for the other.
The first one, I have no clue but maybe it's related to the second one?
The second one might be due to the fact that sudo is needed to start a Docker container on my box. But it would be nice to be able to run the tests somehow.
Full test report:
[INFO] Running io.quarkus.vault.VaultAppRoleITCase
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 36.364 s <<< FAILURE! - in io.quarkus.vault.VaultAppRoleITCase
[ERROR] io.quarkus.vault.VaultAppRoleITCase Time elapsed: 36.361 s <<< ERROR!
java.lang.RuntimeException: Unable to start Quarkus test resource io.quarkus.vault.test.VaultTestLifecycleManager@39a2bb97
at io.quarkus.test.common.TestResourceManager.start(TestResourceManager.java:45)
at io.quarkus.test.QuarkusUnitTest.beforeAll(QuarkusUnitTest.java:205)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeBeforeAllCallbacks$7(ClassBasedTestDescriptor.java:355)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeBeforeAllCallbacks(ClassBasedTestDescriptor.java:355)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:189)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:77)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:132)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:142)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:117)
at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:383)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:344)
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:125)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:417)
Caused by: java.lang.IllegalStateException: Can not connect to Ryuk
at org.testcontainers.utility.ResourceReaper.start(ResourceReaper.java:149)
at org.testcontainers.DockerClientFactory.client(DockerClientFactory.java:131)
at org.testcontainers.containers.GenericContainer.<init>(GenericContainer.java:175)
at org.testcontainers.containers.JdbcDatabaseContainer.<init>(JdbcDatabaseContainer.java:36)
at org.testcontainers.containers.PostgreSQLContainer.<init>(PostgreSQLContainer.java:32)
at org.testcontainers.containers.PostgreSQLContainer.<init>(PostgreSQLContainer.java:28)
at io.quarkus.vault.test.VaultTestExtension.start(VaultTestExtension.java:120)
at io.quarkus.vault.test.VaultTestLifecycleManager.start(VaultTestLifecycleManager.java:32)
at io.quarkus.test.common.TestResourceManager.start(TestResourceManager.java:43)
... 36 more
[INFO] Running io.quarkus.vault.VaultITCase
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 87.583 s <<< FAILURE! - in io.quarkus.vault.VaultITCase
[ERROR] io.quarkus.vault.VaultITCase Time elapsed: 87.583 s <<< ERROR!
java.lang.RuntimeException: Unable to start Quarkus test resource io.quarkus.vault.test.VaultTestLifecycleManager@1dd6d4b7
at io.quarkus.test.common.TestResourceManager.start(TestResourceManager.java:45)
at io.quarkus.test.QuarkusUnitTest.beforeAll(QuarkusUnitTest.java:205)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeBeforeAllCallbacks$7(ClassBasedTestDescriptor.java:355)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeBeforeAllCallbacks(ClassBasedTestDescriptor.java:355)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:189)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:77)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:132)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:142)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:117)
at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:383)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:344)
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:125)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:417)
Caused by: org.testcontainers.containers.ContainerLaunchException: Container startup failed
at org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:290)
at org.testcontainers.containers.GenericContainer.start(GenericContainer.java:272)
at io.quarkus.vault.test.VaultTestExtension.start(VaultTestExtension.java:153)
at io.quarkus.vault.test.VaultTestLifecycleManager.start(VaultTestLifecycleManager.java:32)
at io.quarkus.test.common.TestResourceManager.start(TestResourceManager.java:43)
... 36 more
Caused by: org.rnorth.ducttape.RetryCountExceededException: Retry limit hit with exception
at org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess(Unreliables.java:88)
at org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:283)
... 40 more
Caused by: org.testcontainers.containers.ContainerLaunchException: Could not create/start container
at org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:350)
at org.testcontainers.containers.GenericContainer.lambda$doStart$0(GenericContainer.java:285)
at org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess(Unreliables.java:81)
... 41 more
Caused by: org.testcontainers.containers.ContainerLaunchException: Timed out waiting for container port to open (localhost ports: [8200] should be listening)
at org.testcontainers.containers.wait.strategy.HostPortWaitStrategy.waitUntilReady(HostPortWaitStrategy.java:47)
at org.testcontainers.containers.wait.strategy.AbstractWaitStrategy.waitUntilReady(AbstractWaitStrategy.java:35)
at org.testcontainers.containers.wait.HostPortWaitStrategy.waitUntilReady(HostPortWaitStrategy.java:23)
at org.testcontainers.containers.wait.strategy.AbstractWaitStrategy.waitUntilReady(AbstractWaitStrategy.java:35)
at org.testcontainers.containers.GenericContainer.waitUntilContainerStarted(GenericContainer.java:675)
at org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:332)
... 43 more
hello @gsmet I did add the
ryuk is a resource reaper used by testcontainers to cleanup containers, even when tests fail.
testcontainers knows vault has started by doing a test on vault's port (8200) I will track progress on the CI cache issue, and trigger a CI build as soon as possible. once I get there I will do a final squash. |
hello @sberyozkin & @gsmet |
Oh yes please, hope I can start enjoying this extension ASAP. |
@vsevel @sberyozkin and merged! Thanks @vsevel for your hard work and your patience. I will add a small follow-up commit to add a new metadata file we added recently. |
fantastic! thanks so much to both of you. you too had a lot of patience, and a pragmatic mindset that made it possible. it would have been easy for you @gsmet to take any of the current limitations (IT not running on windows because of the lack of docker installed, testcontainers usage not consistent with other ITs, rewritten config object creation in the vault config source, okhttpclient instead of jax-rs client), or pretend you were too busy (and that would have been true) and say that this was an interesting idea, but is not ready for prime time. and it would have been easy for you @sberyozkin to just let it go after so many back and forth discussions - how many did we have in the end?? your comments and guidance were decisive to transform an interesting poc into a product feature. obviously quarkus does not stop here for me. I have a few follow-ups lined up:
I try to monitor conversations on the google group (but not every day). and I am almost never on zulip. so if anything pops up for me, do not hesitate to reach out. I do not plan to disappear. looking forward for more work with the great quarkus community. |
@vsevel really good to see this one in. Looking forward to see the vault power. Also congratulations. |
@rsvoboda hello,
Those objects are mapped to XXXAuth objects, such as VaultKubernetesAuth, VaultAppRoleAuth, VaultUserPassAuth (those are the only supported auth methods in the extension at the moment). then inside those objects we find several attributes plus a these are json dtos internal to the extension. it might have been less confusing if the top level object had been named VaultKubernetesLoginResponse, containing a VaultKubernetesAuth for instance. but again those are technical classes, not public api. |
@vsevel thank you for details |
This pull request provides support for getting credentials out of hashicorp vault directly from quarkus when using the agroal connection pool.
The initial discussion is here:
https://groups.google.com/d/msg/quarkus-dev/Izy6KyqNtrk/xyDSIBFfBQAJ
I believe the main points have been addressed.
I have developed a sample application that demonstrates its use:
https://github.com/vsevel/vaultapp
From an application perspective, it is as easy as:
depending on the situation, vault has to be configured differently. Please refer to the vaultapp example application, or the VaultTestExtension that supports IT tests.
This PR honors edge cases such as:
This level of functionality has been made possible by https://issues.jboss.org/browse/AG-116 that was developed in agroal for that use case, plus the max-lifetime feature that forces a connection to be recycled on a regular basis (and allows to go through vault's renewal logic without implementing some kind of timer mechanism in VaultPassword). To benefit from this feature, I had to expose it through a new property maxLifetime in DataSourceRuntimeConfig.
It means that you can use static passwords in vault and revoke the client token, or dynamic credentials and revoke the lease, and force client applications to go through the secret acquisition logic, without any downtime.
Each datasource will have its own VaultPassword (VaultPassword instances are not shared across multiple datasources, which is not an issue because they are lightweight objects).
We support using secrets on the default datasource, and also named datasources.
2 auth mechanisms are supported:
The kubernetes auth is configured by default, and leverages the standard jwt token located at:
/var/run/secrets/kubernetes.io/serviceaccount/token
Even if k8s is the primary target, it is working perfectly as a standalone container, or even as a good old java program running outside docker.
Other auth mechanisms (see https://www.vaultproject.io/docs/auth/index.html) can be easily added in VaultAuthService.
see VaultAuthService
TLS is supported, and active by default. A tls-skip-verify mode has been added as well. And it is possible also to deactivate tls all together (dev mode).
see VaultHttpClientFactory
If running in k8s we can leverage automatically the cacert bundle located here:
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
For all properties there are sensible defaults, to ease deployment in k8s in particular.
see VaultServerRuntimeConfig
For static passwords, I support the kv engine version 1 and 2 (with versioning).
see https://www.vaultproject.io/docs/secrets/kv/index.html
There is a very simple cache for fetched secrets, with a cache-period that can be set (default=10mins). The cache gets used in the usual situation where we are within the cache period, but also if we get an http exception or a forbidden exception when we attempt to contact the vault. In that case the application will be allowed to use the last known value.
The http client allows to set the read timeout (default is 500ms) and the connect timeout (default is 5000ms).
Most of the logging is done in debug, and there is a log-confidentiality-level property that can be set to print out confidential informations in dev mode.
I have created integration tests that leverage testcontainers to start and configure a complete vault+postgres system.
see VaultTestExtension and Vault*ITCase classes.
I just saw that the windows build was failing with an error on getting the testcontainers lib to work:
Not sure about that.
The integration test was by far the most challenging to write, and that is why I spent most of my efforts on it. I will complete the testing side with unit tests very shortly.
I have done quite a bit of manual testing as well using the example application vaultapp.
Future improvements to be discussed:
instance to be shared inside quarkus, plus would open up some other use cases (eg certificates mgmt) by leveraging the other secret engines.
The only minor glitch I encountered was that for some reasons I could not get the org.testcontainers.postgresql artifact to use version ${test-containers.version}, but instead had to hardcode it in the pom. Something that is probably easy to figure out.
I am hoping you can see the value of this PR, given that:
Please reach out if you think this PR has the potential to make its way into the product.
Regards,
Vincent
Fixes #2764