Repository: kroxylicious/kroxylicious (java)

Issue:
Securely handle the HashiCorp Vault Token in Kroxylicious configuration
When connecting to HashiCorp Vault, the client must pass a Vault Token (a secret) in order to authenticate.  Currently the token is obtained from the Kroxylicious configuration where it is stored in-line as a string.  This approach would raise red flags during a security audit.   We need a secure mechanism(s) to inject the secret.

In a Kubernetes environment, utilising a Kubernetes Secret to is an obvious choice.

Pull request description:
Fix #782: Use PasswordProvider for the HashiCorp Vault Token
### Type of change

_Select the type of your PR_

- Enhancement / new feature

### Description

Fix #782.

why: Security audits are likely to flag the fact that the password is inline as a fail. Now we have the PasswordProvider object as part of the API, this filter can take advantage of it too.)

### Additional Context

_Why are you making this pull request?_

### Checklist

_Please go through this checklist and make sure all applicable tasks have been done_

- [x] Write tests
- [x] Make sure all tests pass
- [ ] Review performance test results. Ensure that any degradations to performance numbers are understood and justified.
- [ ] Make sure all Sonar-Lint warnings are addressed or are justifiably ignored.
- [x] Update documentation
- [x] Reference relevant issue(s) and close them after merging
- [x] For user facing changes, update CHANGELOG.md (remember to include changes affecting the API of the test artefacts too).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd6714aa..a782b5a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ Please enumerate **all user-facing** changes using format `<githib issue/pr numb
 
 ## 0.5.0
 
+
+* [#782](https://github.com/kroxylicious/kroxylicious/issues/782): Securely handle the HashiCorp Vault Token in Kroxylicious configuration
 * [#973](https://github.com/kroxylicious/kroxylicious/pull/973): Remove deprecated CompositeFilter and it's documentation
 * [#935](https://github.com/kroxylicious/kroxylicious/pull/935): Enable user to configure alternative source of keys for vault KMS client
 * [#787](https://github.com/kroxylicious/kroxylicious/issues/787): Initial documentation for the envelope-encryption feature.
@@ -26,9 +28,13 @@ Please enumerate **all user-facing** changes using format `<githib issue/pr numb
   adjust your dependencies as your adopt this release.
 * `io.kroxylicious:kroxylicious-filter-test-support` now contains RecordTestUtils for creating example `Record`, `RecordBatch` and `MemoryRecords`. It also contains 
   assertj assertions for those same classes to enable us to write fluent assertions, accessible via `io.kroxylicious.test.assertj.KafkaAssertions`.
-* The configuration for VaultKMS service has changed.  Instead of the `vaultUrl` config key, the provider now
-  requires `vaultTransitEngineUrl`.  This must provide the complete path to the Transit Engine on the
-  HashiCorp Vault instance (e.g. https://myvault:8200/v1/transit or https://myvault:8200/v1/mynamespace/transit).  
+* The configuration for VaultKMS service has changed.
+  * Instead of the `vaultUrl` config key, the provider now requires `vaultTransitEngineUrl`.  This must provide the
+    complete path to the Transit Engine on the HashiCorp Vault instance (e.g. https://myvault:8200/v1/transit or
+    https://myvault:8200/v1/mynamespace/transit).
+  * The `vaultToken` field now requires a `PasswordProvider` object rather than inline text value.   You may pass the
+    token from a file (filename specified by a `passwordFile` field) or inline (`password` field).  The latter is not
+    recommended in production environments.
 * The deprecated CompositeFilter interface has been removed.
 
 ## 0.4.1
diff --git a/docs/available-filters/envelope-encryption/hashicorp-vault/service.adoc b/docs/available-filters/envelope-encryption/hashicorp-vault/service.adoc
index 16bcdcb7..ee3a8844 100644
--- a/docs/available-filters/envelope-encryption/hashicorp-vault/service.adoc
+++ b/docs/available-filters/envelope-encryption/hashicorp-vault/service.adoc
@@ -10,12 +10,14 @@ kms: VaultKmsService                                          # <1>
 kmsConfig:
   vaultTransitEngineUrl: <vault transit engine service url>   # <2>
   tls:                                                        # <3>
-  vaultToken: <vault token>                                   # <4>
+  vaultToken:                                                 # <4>
+    passwordFile: /opt/vault/token
+
 ----
 <1> Name of the KMS provider. This must be `VaultKmsService`.
 <2> link:setup.adoc#_vault_transit_engine_url[Vault Transit Engine URL] including the protocol part, i.e. `https:` or `http:`
 <3> (Optional) TLS trust configuration.
-<4> Vault Token
+<4> File containing the Vault Token
 
 For TLS trust and TLS client authentication configuration, the filter accepts the same TLS parameters as link:../../deploying.adoc#_upstream_tls[Upstream TLS]
 except the `PEM` store type is currently https://github.com/kroxylicious/kroxylicious/issues/933[not supported].
diff --git a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/FilePassword.java b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/FilePassword.java
similarity index 93%
rename from kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/FilePassword.java
rename to kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/FilePassword.java
index 0185d502..82d55704 100644
--- a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/FilePassword.java
+++ b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/FilePassword.java
@@ -4,7 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.proxy.config.tls;
+package io.kroxylicious.proxy.config.secret;
 
 import java.io.BufferedReader;
 import java.io.FileReader;
@@ -13,7 +13,6 @@ import java.io.UncheckedIOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Objects;
 
-import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 import edu.umd.cs.findbugs.annotations.NonNull;
@@ -27,7 +26,6 @@ import edu.umd.cs.findbugs.annotations.NonNull;
  */
 public record FilePassword(@JsonProperty(required = true) String passwordFile) implements PasswordProvider {
 
-    @JsonCreator
     public FilePassword {
         Objects.requireNonNull(passwordFile);
     }
diff --git a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/FilePasswordFilePath.java b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/FilePasswordFilePath.java
similarity index 91%
rename from kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/FilePasswordFilePath.java
rename to kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/FilePasswordFilePath.java
index 6c7e24b3..a5d19330 100644
--- a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/FilePasswordFilePath.java
+++ b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/FilePasswordFilePath.java
@@ -4,7 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.proxy.config.tls;
+package io.kroxylicious.proxy.config.secret;
 
 import java.util.Objects;
 
@@ -21,7 +21,7 @@ import org.slf4j.LoggerFactory;
  * @deprecated use FilePassword instead
  *
  * TODO: Once https://github.com/FasterXML/jackson-databind/pull/4335 is released (2.17), replace FilePasswordFilePath
- * with a @JsonAlias("filePath) annotation on the FilePassword#passwordFile formal parameter.
+ * with a @JsonAlias("filePath") annotation on the FilePassword#passwordFile formal parameter.
  */
 @Deprecated(since = "0.5.0", forRemoval = true)
 @SuppressWarnings({ "java:S5738", "java:S1133" }) // java:S5738 warns of the use and need to remove deprecated classes.
diff --git a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/InlinePassword.java b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/InlinePassword.java
similarity index 82%
rename from kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/InlinePassword.java
rename to kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/InlinePassword.java
index 7fbed781..99c15e2c 100644
--- a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/InlinePassword.java
+++ b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/InlinePassword.java
@@ -4,9 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.proxy.config.tls;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
+package io.kroxylicious.proxy.config.secret;
 
 /**
  * A password expressed directly in the model, in plain text.
@@ -14,8 +12,6 @@ import com.fasterxml.jackson.annotation.JsonCreator;
  * @param password the password
  */
 public record InlinePassword(String password) implements PasswordProvider {
-    @JsonCreator
-    public InlinePassword {}
 
     @Override
     public String getProvidedPassword() {
diff --git a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/PasswordProvider.java b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/PasswordProvider.java
similarity index 93%
rename from kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/PasswordProvider.java
rename to kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/PasswordProvider.java
index bf632169..3f396876 100644
--- a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/PasswordProvider.java
+++ b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/secret/PasswordProvider.java
@@ -4,7 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.proxy.config.tls;
+package io.kroxylicious.proxy.config.secret;
 
 import com.fasterxml.jackson.annotation.JsonSubTypes;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
diff --git a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/KeyPair.java b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/KeyPair.java
index b6463f07..8866b3f7 100644
--- a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/KeyPair.java
+++ b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/KeyPair.java
@@ -10,6 +10,8 @@ import java.util.Objects;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
+
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
 /**
diff --git a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/KeyStore.java b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/KeyStore.java
index 03ed4d22..6b587557 100644
--- a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/KeyStore.java
+++ b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/KeyStore.java
@@ -10,6 +10,8 @@ import java.util.Objects;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
+
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
 /**
diff --git a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/TrustStore.java b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/TrustStore.java
index 3b333c3e..101eb0a7 100644
--- a/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/TrustStore.java
+++ b/kroxylicious-api/src/main/java/io/kroxylicious/proxy/config/tls/TrustStore.java
@@ -11,6 +11,8 @@ import java.util.Objects;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
+
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
 /**
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
index 5d513894..5304af61 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKms.java
@@ -44,11 +44,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 
 /**
  * An implementation of the KMS interface backed by a remote instance of HashiCorp Vault (v1).
- * <br/>
- * <h2>TODO</h2>
- * <ul>
- *    <li>Securely pass vault token</li>
- * </ul>
  */
 public class VaultKms implements Kms<String, VaultEdek> {
 
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsService.java b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsService.java
index eca4db81..eddd266e 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsService.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsService.java
@@ -23,7 +23,7 @@ public class VaultKmsService implements KmsService<Config, String, VaultEdek> {
     @NonNull
     @Override
     public VaultKms buildKms(Config options) {
-        return new VaultKms(options.vaultTransitEngineUrl(), options.vaultToken(), Duration.ofSeconds(20), options.sslContext());
+        return new VaultKms(options.vaultTransitEngineUrl(), options.vaultToken().getProvidedPassword(), Duration.ofSeconds(20), options.sslContext());
     }
 
 }
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/Config.java b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/Config.java
index c37d244f..fa9ae241 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/Config.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/Config.java
@@ -14,6 +14,7 @@ import javax.net.ssl.SSLContext;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
 import io.kroxylicious.proxy.config.tls.Tls;
 
 import edu.umd.cs.findbugs.annotations.NonNull;
@@ -21,11 +22,11 @@ import edu.umd.cs.findbugs.annotations.NonNull;
 /**
  * Configuration for the Vault KMS service.
  * @param vaultTransitEngineUrl URL of the Vault Transit Engine e.g. {@code https://myhashicorpvault:8200/v1/transit}
- * @param vaultToken Vault token.
+ * @param vaultToken the password provider that will provide the Vault token.
  */
 public record Config(
                      @JsonProperty(value = "vaultTransitEngineUrl", required = true) URI vaultTransitEngineUrl,
-                     @JsonProperty(required = true) String vaultToken,
+                     @JsonProperty(required = true) PasswordProvider vaultToken,
                      Tls tls) {
     public Config {
         Objects.requireNonNull(vaultTransitEngineUrl);
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTls.java b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTls.java
index 2a419ec5..d7b2be54 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTls.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTls.java
@@ -22,11 +22,11 @@ import javax.net.ssl.X509TrustManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
 import io.kroxylicious.proxy.config.tls.InsecureTls;
 import io.kroxylicious.proxy.config.tls.KeyPair;
 import io.kroxylicious.proxy.config.tls.KeyProvider;
 import io.kroxylicious.proxy.config.tls.KeyProviderVisitor;
-import io.kroxylicious.proxy.config.tls.PasswordProvider;
 import io.kroxylicious.proxy.config.tls.Tls;
 import io.kroxylicious.proxy.config.tls.TrustProvider;
 import io.kroxylicious.proxy.config.tls.TrustProviderVisitor;
diff --git a/kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/config/tls/NettyKeyProvider.java b/kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/config/tls/NettyKeyProvider.java
index d8b62f31..f4108970 100644
--- a/kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/config/tls/NettyKeyProvider.java
+++ b/kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/config/tls/NettyKeyProvider.java
@@ -16,6 +16,8 @@ import javax.net.ssl.KeyManagerFactory;
 
 import io.netty.handler.ssl.SslContextBuilder;
 
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
+
 public class NettyKeyProvider {
 
     interface SslContextBuilderA {
diff --git a/kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/config/tls/NettyTrustProvider.java b/kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/config/tls/NettyTrustProvider.java
index 62f49a2f..29256a99 100644
--- a/kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/config/tls/NettyTrustProvider.java
+++ b/kroxylicious-runtime/src/main/java/io/kroxylicious/proxy/config/tls/NettyTrustProvider.java
@@ -17,6 +17,8 @@ import javax.net.ssl.TrustManagerFactory;
 import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
 
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
+
 public class NettyTrustProvider {
 
     private final TrustProvider trustProvider;
diff --git a/kubernetes-examples/envelope-encryption/base/kroxylicious/kroxylicious-config.yaml b/kubernetes-examples/envelope-encryption/base/kroxylicious/kroxylicious-config.yaml
index a2eec9ad..7666c2f4 100644
--- a/kubernetes-examples/envelope-encryption/base/kroxylicious/kroxylicious-config.yaml
+++ b/kubernetes-examples/envelope-encryption/base/kroxylicious/kroxylicious-config.yaml
@@ -35,7 +35,8 @@ data:
         kms: VaultKmsService
         kmsConfig:
           vaultTransitEngineUrl: http://vault.vault.svc.cluster.local:8200/v1/transit
-          vaultToken: myroottoken
+          vaultToken:
+            password: myroottoken
         selector: TemplateKekSelector
         selectorConfig:
           template: "${topicName}"


Test patch:
diff --git a/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/FilePasswordTest.java b/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/secret/FilePasswordTest.java
similarity index 98%
rename from kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/FilePasswordTest.java
rename to kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/secret/FilePasswordTest.java
index 139b2f09..ea69b3b9 100644
--- a/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/FilePasswordTest.java
+++ b/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/secret/FilePasswordTest.java
@@ -4,7 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.proxy.config.tls;
+package io.kroxylicious.proxy.config.secret;
 
 import java.io.File;
 import java.io.FileNotFoundException;
diff --git a/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/InlinePasswordTest.java b/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/secret/InlinePasswordTest.java
similarity index 79%
rename from kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/InlinePasswordTest.java
rename to kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/secret/InlinePasswordTest.java
index 09705388..2bbc28c7 100644
--- a/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/InlinePasswordTest.java
+++ b/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/secret/InlinePasswordTest.java
@@ -4,7 +4,7 @@
  * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
  */
 
-package io.kroxylicious.proxy.config.tls;
+package io.kroxylicious.proxy.config.secret;
 
 import org.junit.jupiter.api.Test;
 
@@ -13,13 +13,13 @@ import static org.assertj.core.api.Assertions.assertThat;
 class InlinePasswordTest {
 
     @Test
-    public void testInlinePassword() {
+    void inlinePassword() {
         InlinePassword inline = new InlinePassword("pazz");
         assertThat(inline.getProvidedPassword()).isEqualTo("pazz");
     }
 
     @Test
-    public void testInlinePasswordToStringDoesNotExposePassword() {
+    void inlinePasswordToStringDoesNotExposePassword() {
         InlinePassword inline = new InlinePassword("shouldNotBeExposed");
         assertThat(inline.toString()).doesNotContain("shouldNotBeExposed");
     }
diff --git a/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/TlsParseTest.java b/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/TlsParseTest.java
index 1dd03c65..986a32e8 100644
--- a/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/TlsParseTest.java
+++ b/kroxylicious-api/src/test/java/io/kroxylicious/proxy/config/tls/TlsParseTest.java
@@ -15,6 +15,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.exc.MismatchedInputException;
 import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
 
+import io.kroxylicious.proxy.config.secret.FilePassword;
+import io.kroxylicious.proxy.config.secret.FilePasswordFilePath;
+import io.kroxylicious.proxy.config.secret.InlinePassword;
+
 import static org.assertj.core.api.Assertions.assertThat;
 
 class TlsParseTest {
diff --git a/kroxylicious-integration-test-support/src/main/java/io/kroxylicious/proxy/config/BuilderConfig.java b/kroxylicious-integration-test-support/src/main/java/io/kroxylicious/proxy/config/BuilderConfig.java
index bb5f8b03..e6e36f7a 100644
--- a/kroxylicious-integration-test-support/src/main/java/io/kroxylicious/proxy/config/BuilderConfig.java
+++ b/kroxylicious-integration-test-support/src/main/java/io/kroxylicious/proxy/config/BuilderConfig.java
@@ -18,8 +18,8 @@ import io.sundr.builder.annotations.ExternalBuildables;
         "io.kroxylicious.proxy.config.admin.AdminHttpConfiguration",
         "io.kroxylicious.proxy.config.admin.EndpointsConfiguration",
         "io.kroxylicious.proxy.config.admin.PrometheusMetricsConfig",
-        "io.kroxylicious.proxy.config.tls.FilePassword",
-        "io.kroxylicious.proxy.config.tls.InlinePassword",
+        "io.kroxylicious.proxy.config.secret.FilePassword",
+        "io.kroxylicious.proxy.config.secret.InlinePassword",
         "io.kroxylicious.proxy.config.tls.Tls",
         "io.kroxylicious.proxy.config.tls.TrustStore",
         "io.kroxylicious.proxy.config.tls.InsecureTls",
diff --git a/kroxylicious-integration-tests/src/test/java/io/kroxylicious/proxy/TlsIT.java b/kroxylicious-integration-tests/src/test/java/io/kroxylicious/proxy/TlsIT.java
index 759fd028..703525b0 100644
--- a/kroxylicious-integration-tests/src/test/java/io/kroxylicious/proxy/TlsIT.java
+++ b/kroxylicious-integration-tests/src/test/java/io/kroxylicious/proxy/TlsIT.java
@@ -42,9 +42,9 @@ import io.kroxylicious.proxy.config.ClusterNetworkAddressConfigProviderDefinitio
 import io.kroxylicious.proxy.config.ClusterNetworkAddressConfigProviderDefinitionBuilder;
 import io.kroxylicious.proxy.config.ConfigurationBuilder;
 import io.kroxylicious.proxy.config.VirtualClusterBuilder;
-import io.kroxylicious.proxy.config.tls.FilePassword;
-import io.kroxylicious.proxy.config.tls.InlinePassword;
-import io.kroxylicious.proxy.config.tls.PasswordProvider;
+import io.kroxylicious.proxy.config.secret.FilePassword;
+import io.kroxylicious.proxy.config.secret.InlinePassword;
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
 import io.kroxylicious.proxy.internal.clusternetworkaddressconfigprovider.PortPerBrokerClusterNetworkAddressConfigProvider;
 import io.kroxylicious.proxy.service.HostPort;
 import io.kroxylicious.testing.kafka.api.KafkaCluster;
diff --git a/kroxylicious-kms-provider-hashicorp-vault-test-support/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/AbstractVaultTestKmsFacade.java b/kroxylicious-kms-provider-hashicorp-vault-test-support/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/AbstractVaultTestKmsFacade.java
index d94274fb..acc8934f 100644
--- a/kroxylicious-kms-provider-hashicorp-vault-test-support/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/AbstractVaultTestKmsFacade.java
+++ b/kroxylicious-kms-provider-hashicorp-vault-test-support/src/main/java/io/kroxylicious/kms/provider/hashicorp/vault/AbstractVaultTestKmsFacade.java
@@ -14,6 +14,7 @@ import java.util.Set;
 
 import io.kroxylicious.kms.provider.hashicorp.vault.config.Config;
 import io.kroxylicious.kms.service.TestKmsFacade;
+import io.kroxylicious.proxy.config.secret.InlinePassword;
 
 import edu.umd.cs.findbugs.annotations.NonNull;
 
@@ -62,7 +63,7 @@ public abstract class AbstractVaultTestKmsFacade implements TestKmsFacade<Config
 
     @Override
     public final Config getKmsServiceConfig() {
-        return new Config(getVaultTransitEngineUrl(), kmsVaultToken, null);
+        return new Config(getVaultTransitEngineUrl(), new InlinePassword(kmsVaultToken), null);
     }
 
     @Override
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsIT.java b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsIT.java
index f7b5c7aa..56fc9458 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsIT.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsIT.java
@@ -21,6 +21,7 @@ import io.kroxylicious.kms.provider.hashicorp.vault.config.Config;
 import io.kroxylicious.kms.service.DekPair;
 import io.kroxylicious.kms.service.UnknownAliasException;
 import io.kroxylicious.kms.service.UnknownKeyException;
+import io.kroxylicious.proxy.config.secret.InlinePassword;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assumptions.assumeThat;
@@ -42,7 +43,7 @@ class VaultKmsIT {
     void beforeEach() {
         assumeThat(DockerClientFactory.instance().isDockerAvailable()).withFailMessage("docker unavailable").isTrue();
         vaultContainer = TestVault.start();
-        var config = new Config(vaultContainer.getEndpoint(), vaultContainer.rootToken(), null);
+        var config = new Config(vaultContainer.getEndpoint(), new InlinePassword(vaultContainer.rootToken()), null);
         service = new VaultKmsService().buildKms(config);
     }
 
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsTlsIT.java b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsTlsIT.java
index 16dd5656..b054085b 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsTlsIT.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/VaultKmsTlsIT.java
@@ -22,8 +22,8 @@ import org.testcontainers.DockerClientFactory;
 
 import io.kroxylicious.kms.provider.hashicorp.vault.VaultResponse.ReadKeyData;
 import io.kroxylicious.kms.provider.hashicorp.vault.config.Config;
-import io.kroxylicious.proxy.config.tls.FilePassword;
-import io.kroxylicious.proxy.config.tls.InlinePassword;
+import io.kroxylicious.proxy.config.secret.FilePassword;
+import io.kroxylicious.proxy.config.secret.InlinePassword;
 import io.kroxylicious.proxy.config.tls.InsecureTls;
 import io.kroxylicious.proxy.config.tls.KeyStore;
 import io.kroxylicious.proxy.config.tls.Tls;
@@ -156,7 +156,7 @@ class VaultKmsTlsIT {
 
     @NonNull
     private static Config vaultConfig(Tls tls, URI endpoint) {
-        return new Config(endpoint, VAULT_TOKEN, tls);
+        return new Config(endpoint, new InlinePassword(VAULT_TOKEN), tls);
     }
 
     @NonNull
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/ConfigParseTest.java b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/ConfigParseTest.java
index 8322ef7f..3a35b19e 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/ConfigParseTest.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/ConfigParseTest.java
@@ -8,6 +8,7 @@ package io.kroxylicious.kms.provider.hashicorp.vault.config;
 
 import java.io.IOException;
 import java.net.URI;
+import java.nio.file.Files;
 
 import javax.net.ssl.SSLContext;
 
@@ -18,33 +19,56 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.exc.MismatchedInputException;
 import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
 
+import io.kroxylicious.proxy.config.secret.InlinePassword;
 import io.kroxylicious.proxy.config.tls.InsecureTls;
 import io.kroxylicious.proxy.config.tls.Tls;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 class ConfigParseTest {
-    ObjectMapper mapper = new ObjectMapper();
+    private final ObjectMapper MAPPER = new ObjectMapper();
 
     @Test
-    void testVaultUrlAndToken() throws IOException {
+    void vaultUrlAndInlineToken() throws IOException {
         String json = """
                 {
                     "vaultTransitEngineUrl": "http://vault",
-                    "vaultToken": "token"
+                    "vaultToken": { "password" : "token" }
                 }
                 """;
         Config config = readConfig(json);
-        assertThat(config.vaultToken()).isEqualTo("token");
+        assertThat(config.vaultToken().getProvidedPassword()).isEqualTo("token");
         assertThat(config.vaultTransitEngineUrl()).isEqualTo(URI.create("http://vault"));
     }
 
     @Test
-    void testVaultUrlRequired() {
+    void tokenFromPasswordFile() throws IOException {
+        var tmp = Files.createTempFile("password", "txt");
+        tmp.toFile().deleteOnExit();
+        Files.writeString(tmp, "token");
+
+        try {
+            String json = """
+                    {
+                        "vaultTransitEngineUrl": "http://vault",
+                        "vaultToken": { "passwordFile" : "%s" }
+                    }
+                    """.formatted(tmp);
+            Config config = readConfig(json);
+            assertThat(config.vaultToken().getProvidedPassword()).isEqualTo("token");
+            assertThat(config.vaultTransitEngineUrl()).isEqualTo(URI.create("http://vault"));
+        }
+        finally {
+            var unused = tmp.toFile().delete();
+        }
+    }
+
+    @Test
+    void vaultUrlRequired() {
         Assertions.assertThatThrownBy(() -> {
             String json = """
                     {
-                        "vaultToken": "token"
+                        "vaultToken": { "password" : "token" }
                     }
                     """;
             readConfig(json);
@@ -52,12 +76,12 @@ class ConfigParseTest {
     }
 
     @Test
-    void testVaultUrlShouldNotBeNull() {
+    void vaultUrlShouldNotBeNull() {
         Assertions.assertThatThrownBy(() -> {
             String json = """
                     {
                         "vaultTransitEngineUrl": null,
-                        "vaultToken": "token"
+                        "vaultToken": { "password" : "token" }
                     }
                     """;
             readConfig(json);
@@ -65,7 +89,7 @@ class ConfigParseTest {
     }
 
     @Test
-    void testVaultTokenRequired() {
+    void vaultTokenRequired() {
         Assertions.assertThatThrownBy(() -> {
             String json = """
                     {
@@ -77,7 +101,7 @@ class ConfigParseTest {
     }
 
     @Test
-    void testVaultTokenShouldNotBeNull() {
+    void vaultTokenShouldNotBeNull() {
         Assertions.assertThatThrownBy(() -> {
             String json = """
                     {
@@ -90,11 +114,11 @@ class ConfigParseTest {
     }
 
     @Test
-    void testEmptyTls() throws Exception {
+    void emptyTls() throws Exception {
         String json = """
                 {
                     "vaultTransitEngineUrl": "https://vault",
-                    "vaultToken": "token",
+                    "vaultToken": { "password" : "token" },
                     "tls": {}
                 }
                 """;
@@ -105,11 +129,11 @@ class ConfigParseTest {
     }
 
     @Test
-    void testMissingTls() throws Exception {
+    void missingTls() throws Exception {
         String json = """
                 {
                     "vaultTransitEngineUrl": "https://vault",
-                    "vaultToken": "token"
+                    "vaultToken": { "password" : "token" }
                 }
                 """;
         Config config = readConfig(json);
@@ -123,7 +147,7 @@ class ConfigParseTest {
         String json = """
                 {
                     "vaultTransitEngineUrl": "https://vault",
-                    "vaultToken": "token",
+                    "vaultToken": { "password" : "token" },
                     "tls": {
                         "trust": {
                             "insecure": true
@@ -132,12 +156,12 @@ class ConfigParseTest {
                 }
                 """;
         Config config = readConfig(json);
-        Config expected = new Config(URI.create("https://vault"), "token", new Tls(null, new InsecureTls(true)));
+        Config expected = new Config(URI.create("https://vault"), new InlinePassword("token"), new Tls(null, new InsecureTls(true)));
         assertThat(config).isEqualTo(expected);
     }
 
     private Config readConfig(String json) throws IOException {
-        return mapper.reader().readValue(json, Config.class);
+        return MAPPER.reader().readValue(json, Config.class);
     }
 
 }
diff --git a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTlsTest.java b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTlsTest.java
index 92fa4bcc..52c53535 100644
--- a/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTlsTest.java
+++ b/kroxylicious-kms-provider-hashicorp-vault/src/test/java/io/kroxylicious/kms/provider/hashicorp/vault/config/JdkTlsTest.java
@@ -21,7 +21,7 @@ import javax.net.ssl.X509TrustManager;
 import org.junit.jupiter.api.Test;
 
 import io.kroxylicious.kms.provider.hashicorp.vault.CertificateGenerator;
-import io.kroxylicious.proxy.config.tls.InlinePassword;
+import io.kroxylicious.proxy.config.secret.InlinePassword;
 import io.kroxylicious.proxy.config.tls.InsecureTls;
 import io.kroxylicious.proxy.config.tls.KeyPair;
 import io.kroxylicious.proxy.config.tls.KeyStore;
diff --git a/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/ConfigParserTest.java b/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/ConfigParserTest.java
index 2f4968de..32a1ac33 100644
--- a/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/ConfigParserTest.java
+++ b/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/ConfigParserTest.java
@@ -28,8 +28,8 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import com.flipkart.zjsonpatch.JsonDiff;
 
 import io.kroxylicious.proxy.config.admin.AdminHttpConfiguration;
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
 import io.kroxylicious.proxy.config.tls.KeyStore;
-import io.kroxylicious.proxy.config.tls.PasswordProvider;
 import io.kroxylicious.proxy.config.tls.Tls;
 import io.kroxylicious.proxy.filter.FilterFactory;
 import io.kroxylicious.proxy.internal.clusternetworkaddressconfigprovider.PortPerBrokerClusterNetworkAddressConfigProvider.PortPerBrokerClusterNetworkAddressConfigProviderConfig;
diff --git a/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/NettyKeyProviderTest.java b/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/NettyKeyProviderTest.java
index bae5c82b..80e5c95f 100644
--- a/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/NettyKeyProviderTest.java
+++ b/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/NettyKeyProviderTest.java
@@ -21,6 +21,9 @@ import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.junit.jupiter.params.provider.ValueSource;
 
+import io.kroxylicious.proxy.config.secret.InlinePassword;
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
+
 import static io.kroxylicious.proxy.config.tls.TlsTestConstants.BADPASS;
 import static io.kroxylicious.proxy.config.tls.TlsTestConstants.JKS;
 import static io.kroxylicious.proxy.config.tls.TlsTestConstants.KEYPASS;
diff --git a/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/NettyTrustProviderTest.java b/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/NettyTrustProviderTest.java
index bfaa49ca..85e789ac 100644
--- a/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/NettyTrustProviderTest.java
+++ b/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/NettyTrustProviderTest.java
@@ -17,6 +17,8 @@ import org.junit.jupiter.params.provider.MethodSource;
 
 import io.netty.handler.ssl.SslContextBuilder;
 
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
+
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
 
diff --git a/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/TlsTestConstants.java b/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/TlsTestConstants.java
index f126f2d1..cf724a74 100644
--- a/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/TlsTestConstants.java
+++ b/kroxylicious-runtime/src/test/java/io/kroxylicious/proxy/config/tls/TlsTestConstants.java
@@ -6,6 +6,11 @@
 
 package io.kroxylicious.proxy.config.tls;
 
+import io.kroxylicious.proxy.config.secret.FilePassword;
+import io.kroxylicious.proxy.config.secret.FilePasswordFilePath;
+import io.kroxylicious.proxy.config.secret.InlinePassword;
+import io.kroxylicious.proxy.config.secret.PasswordProvider;
+
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class TlsTestConstants {
diff --git a/kroxylicious-systemtests/pom.xml b/kroxylicious-systemtests/pom.xml
index 75567bdc..e18362da 100644
--- a/kroxylicious-systemtests/pom.xml
+++ b/kroxylicious-systemtests/pom.xml
@@ -98,6 +98,10 @@
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.kroxylicious</groupId>
+            <artifactId>kroxylicious-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>io.kroxylicious</groupId>
             <artifactId>kroxylicious-kms-test-support</artifactId>
diff --git a/kroxylicious-systemtests/src/main/java/io/kroxylicious/systemtests/templates/kroxylicious/KroxyliciousConfigMapTemplates.java b/kroxylicious-systemtests/src/main/java/io/kroxylicious/systemtests/templates/kroxylicious/KroxyliciousConfigMapTemplates.java
index 04387c3d..fc8841c8 100644
--- a/kroxylicious-systemtests/src/main/java/io/kroxylicious/systemtests/templates/kroxylicious/KroxyliciousConfigMapTemplates.java
+++ b/kroxylicious-systemtests/src/main/java/io/kroxylicious/systemtests/templates/kroxylicious/KroxyliciousConfigMapTemplates.java
@@ -14,7 +14,10 @@ import io.kroxylicious.systemtests.Constants;
 /**
  * The type Kroxylicious config templates.
  */
-public class KroxyliciousConfigMapTemplates {
+public final class KroxyliciousConfigMapTemplates {
+
+    private KroxyliciousConfigMapTemplates() {
+    }
 
     private static ConfigMapBuilder baseKroxyliciousConfig(String namespaceName) {
         return new ConfigMapBuilder()
@@ -73,7 +76,8 @@ public class KroxyliciousConfigMapTemplates {
                     kms: VaultKmsService
                     kmsConfig:
                       vaultTransitEngineUrl: %VAULT_TRANSIT_ENGINE_URL%
-                      vaultToken: %VAULT_TOKEN%
+                      vaultToken:
+                        password: %VAULT_TOKEN%
                     selector: TemplateKekSelector
                     selectorConfig:
                       template: "${topicName}"
@@ -81,7 +85,7 @@ public class KroxyliciousConfigMapTemplates {
                 .replace("%NAMESPACE%", Constants.KAFKA_DEFAULT_NAMESPACE)
                 .replace("%CLUSTER_NAME%", clusterName)
                 .replace("%KROXY_SERVICE_NAME%", Constants.KROXY_SERVICE_NAME)
-                .replace("%VAULT_TOKEN%", config.vaultToken())
+                .replace("%VAULT_TOKEN%", config.vaultToken().getProvidedPassword())
                 .replace("%VAULT_TRANSIT_ENGINE_URL%", config.vaultTransitEngineUrl().toString())
                 .replace("%TOPIC_NAME%", topicName);
     }