This is an automated email from the ASF dual-hosted git repository. quantranhong1999 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 24c23e76d64a054f70d4c9664b7824b33cf66742 Author: Benoit TELLIER <[email protected]> AuthorDate: Tue Jul 28 23:11:07 2026 +0200 [ENHANCEMENT] Webadmin: default to auto-generate creds --- CHANGELOG.md | 4 ++ .../servers/pages/distributed/run/run-docker.adoc | 3 +- .../servers/pages/postgres/run/run-docker.adoc | 3 +- .../servers/partials/configure/webadmin.adoc | 20 ++++++ .../docker-configuration/webadmin.properties | 10 ++- .../sample-configuration/webadmin.properties | 7 +++ .../docker-configuration/webadmin.properties | 10 ++- .../sample-configuration/webadmin.properties | 7 +++ .../docker-configuration/webadmin.properties | 10 ++- .../sample-configuration/webadmin.properties | 7 +++ .../docker-configuration/webadmin.properties | 10 ++- .../sample-configuration/webadmin.properties | 7 +++ .../docker-configuration/webadmin.properties | 10 ++- .../sample-configuration/webadmin.properties | 7 +++ .../docker-configuration/webadmin.properties | 10 ++- .../sample-configuration/webadmin.properties | 7 +++ .../src/test/resources/webadmin.properties | 5 +- .../sample-configuration/webadmin.properties | 7 +++ .../src/test/resources/webadmin.properties | 5 +- .../james/modules/server/WebAdminServerModule.java | 34 +++++++++- .../modules/server/WebAdminServerModuleTest.java | 72 ++++++++++++++++++++++ .../src/test/resources/webadmin.properties | 5 +- .../src/test/resources/webadmin.properties | 5 +- .../src/test/resources/webadmin.properties | 5 +- .../webadmin/authentication/PasswordGenerator.java | 45 ++++++++++++++ 25 files changed, 300 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37c4fcb17a..2fddc16c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) - JAMES-4193 Correct BoringSSL TLS 1.3 cipher suite sanitizing that led to disabled TLS 1.3 (#3029) - JAMES-4201 Implement granular password access control in webadmin (`password.readonly` and `password.nodelete`) + - Webadmin `password.generate`: generate a random password upon start up when none is configured, and log it. + **Breaking change**: this defaults to `true`, thus webadmin is no longer unauthenticated out of the box, existing + deployments included. Set `password.generate=false` in `webadmin.properties` to opt back into an unauthenticated + webadmin, or configure `password` to pin a stable secret. - JAMES-4171 Allow configuring a strong distinction between submission and MX ports - JAMES-4158 Allow IMAP to specify per-port administrators - JAMES-4183 AllowedUnauthenticatedSender mail hook, using PROXY protocol to inject source IP diff --git a/docs/modules/servers/pages/distributed/run/run-docker.adoc b/docs/modules/servers/pages/distributed/run/run-docker.adoc index 2c22c0ab78..edae590a75 100644 --- a/docs/modules/servers/pages/distributed/run/run-docker.adoc +++ b/docs/modules/servers/pages/distributed/run/run-docker.adoc @@ -82,7 +82,8 @@ Where : - HOSTNAME: is the hostname you want to give to your James container. This DNS entry will be used to send mail to your James server. -Webadmin port binding is restricted to loopback as users are not authenticated by default on webadmin server. Thus you should avoid exposing it in production. +Webadmin port binding is restricted to loopback. Webadmin is protected by a password randomly generated upon +each start up and written in the logs (see `password.generate`), yet you should avoid exposing it in production. Note that the above example assumes `127.0.0.1` is your loopback interface for convenience but you should change it if this is not the case on your machine. If you want to pass additional options to the underlying java command, you can configure a _JAVA_TOOL_OPTIONS_ env variable, for example add: diff --git a/docs/modules/servers/pages/postgres/run/run-docker.adoc b/docs/modules/servers/pages/postgres/run/run-docker.adoc index f6bd9d8f1f..bd56b4f144 100644 --- a/docs/modules/servers/pages/postgres/run/run-docker.adoc +++ b/docs/modules/servers/pages/postgres/run/run-docker.adoc @@ -79,7 +79,8 @@ Where : - HOSTNAME: is the hostname you want to give to your James container. This DNS entry will be used to send mail to your James server. -Webadmin port binding is restricted to loopback as users are not authenticated by default on webadmin server. Thus you should avoid exposing it in production. +Webadmin port binding is restricted to loopback. Webadmin is protected by a password randomly generated upon +each start up and written in the logs (see `password.generate`), yet you should avoid exposing it in production. Note that the above example assumes `127.0.0.1` is your loopback interface for convenience but you should change it if this is not the case on your machine. If you want to pass additional options to the underlying java command, you can configure a _JAVA_TOOL_OPTIONS_ env variable, for example add: diff --git a/docs/modules/servers/partials/configure/webadmin.adoc b/docs/modules/servers/partials/configure/webadmin.adoc index e2566a59c3..c0f7f2a6da 100644 --- a/docs/modules/servers/partials/configure/webadmin.adoc +++ b/docs/modules/servers/partials/configure/webadmin.adoc @@ -39,6 +39,26 @@ to get some examples and hints. | cors.origin | Specify ths CORS origin (default: null) +| password.generate +| Generates a random password upon start up when no `password` is configured, allowing a secure setup without +hardcoded credentials (default: true). As such, WebAdmin is never unauthenticated unless explicitly asked for. + +.... +password.generate=false +.... + +The generated password is written in the logs upon start up: + +.... +WARN No WebAdmin password had been configured: a random one had been generated for this run. [...] +Generated WebAdmin password: 8Kj2mXqT4vZ... +.... + +Beware: the generated password changes upon each restart, and is exposed to whoever can read the logs. Configure +`password` explicitly for setups needing a stable secret, or set `password.generate=false` to opt back into an +unauthenticated WebAdmin. This option is ignored when `password` is configured, when `jwt.enabled` is true, and when +WebAdmin is disabled. + | password | Uses a configured static value for authentication. It relies on the Password header. It supports several passwords, configured as a comma separated list. diff --git a/server/apps/distributed-app/docker-configuration/webadmin.properties b/server/apps/distributed-app/docker-configuration/webadmin.properties index 5d72d99b74..42c276ec18 100644 --- a/server/apps/distributed-app/docker-configuration/webadmin.properties +++ b/server/apps/distributed-app/docker-configuration/webadmin.properties @@ -51,4 +51,12 @@ https.enabled=false # List of fully qualified class names that should be exposed over webadmin # in addition to your product default routes. Routes needs to be located # within the classpath or in the ./extensions-jars folder. -#extensions.routes= \ No newline at end of file +#extensions.routes= + +# Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true diff --git a/server/apps/distributed-app/sample-configuration/webadmin.properties b/server/apps/distributed-app/sample-configuration/webadmin.properties index 7376656fce..27bc60395c 100644 --- a/server/apps/distributed-app/sample-configuration/webadmin.properties +++ b/server/apps/distributed-app/sample-configuration/webadmin.properties @@ -55,6 +55,13 @@ https.enabled=false #extensions.routes= # Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true + # Configure one or more passwords (comma separated) for WebAdmin authentication #password=secret1,secret2 diff --git a/server/apps/distributed-pop3-app/docker-configuration/webadmin.properties b/server/apps/distributed-pop3-app/docker-configuration/webadmin.properties index 5d72d99b74..42c276ec18 100644 --- a/server/apps/distributed-pop3-app/docker-configuration/webadmin.properties +++ b/server/apps/distributed-pop3-app/docker-configuration/webadmin.properties @@ -51,4 +51,12 @@ https.enabled=false # List of fully qualified class names that should be exposed over webadmin # in addition to your product default routes. Routes needs to be located # within the classpath or in the ./extensions-jars folder. -#extensions.routes= \ No newline at end of file +#extensions.routes= + +# Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true diff --git a/server/apps/distributed-pop3-app/sample-configuration/webadmin.properties b/server/apps/distributed-pop3-app/sample-configuration/webadmin.properties index 7376656fce..27bc60395c 100644 --- a/server/apps/distributed-pop3-app/sample-configuration/webadmin.properties +++ b/server/apps/distributed-pop3-app/sample-configuration/webadmin.properties @@ -55,6 +55,13 @@ https.enabled=false #extensions.routes= # Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true + # Configure one or more passwords (comma separated) for WebAdmin authentication #password=secret1,secret2 diff --git a/server/apps/jpa-app/docker-configuration/webadmin.properties b/server/apps/jpa-app/docker-configuration/webadmin.properties index 5d72d99b74..42c276ec18 100644 --- a/server/apps/jpa-app/docker-configuration/webadmin.properties +++ b/server/apps/jpa-app/docker-configuration/webadmin.properties @@ -51,4 +51,12 @@ https.enabled=false # List of fully qualified class names that should be exposed over webadmin # in addition to your product default routes. Routes needs to be located # within the classpath or in the ./extensions-jars folder. -#extensions.routes= \ No newline at end of file +#extensions.routes= + +# Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true diff --git a/server/apps/jpa-app/sample-configuration/webadmin.properties b/server/apps/jpa-app/sample-configuration/webadmin.properties index e21c89b0bc..c2a336b1e0 100644 --- a/server/apps/jpa-app/sample-configuration/webadmin.properties +++ b/server/apps/jpa-app/sample-configuration/webadmin.properties @@ -49,6 +49,13 @@ https.enabled=false #extensions.routes= # Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true + # Configure one or more passwords (comma separated) for WebAdmin authentication #password=secret1,secret2 diff --git a/server/apps/jpa-smtp-app/docker-configuration/webadmin.properties b/server/apps/jpa-smtp-app/docker-configuration/webadmin.properties index 5d72d99b74..42c276ec18 100644 --- a/server/apps/jpa-smtp-app/docker-configuration/webadmin.properties +++ b/server/apps/jpa-smtp-app/docker-configuration/webadmin.properties @@ -51,4 +51,12 @@ https.enabled=false # List of fully qualified class names that should be exposed over webadmin # in addition to your product default routes. Routes needs to be located # within the classpath or in the ./extensions-jars folder. -#extensions.routes= \ No newline at end of file +#extensions.routes= + +# Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true diff --git a/server/apps/jpa-smtp-app/sample-configuration/webadmin.properties b/server/apps/jpa-smtp-app/sample-configuration/webadmin.properties index e21c89b0bc..c2a336b1e0 100644 --- a/server/apps/jpa-smtp-app/sample-configuration/webadmin.properties +++ b/server/apps/jpa-smtp-app/sample-configuration/webadmin.properties @@ -49,6 +49,13 @@ https.enabled=false #extensions.routes= # Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true + # Configure one or more passwords (comma separated) for WebAdmin authentication #password=secret1,secret2 diff --git a/server/apps/memory-app/docker-configuration/webadmin.properties b/server/apps/memory-app/docker-configuration/webadmin.properties index 5d72d99b74..42c276ec18 100644 --- a/server/apps/memory-app/docker-configuration/webadmin.properties +++ b/server/apps/memory-app/docker-configuration/webadmin.properties @@ -51,4 +51,12 @@ https.enabled=false # List of fully qualified class names that should be exposed over webadmin # in addition to your product default routes. Routes needs to be located # within the classpath or in the ./extensions-jars folder. -#extensions.routes= \ No newline at end of file +#extensions.routes= + +# Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true diff --git a/server/apps/memory-app/sample-configuration/webadmin.properties b/server/apps/memory-app/sample-configuration/webadmin.properties index a9eb36a813..a451aca7b8 100644 --- a/server/apps/memory-app/sample-configuration/webadmin.properties +++ b/server/apps/memory-app/sample-configuration/webadmin.properties @@ -56,6 +56,13 @@ https.enabled=false #extensions.routes= # Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true + # Configure one or more passwords (comma separated) for WebAdmin authentication #password=secret1,secret2 diff --git a/server/apps/postgres-app/docker-configuration/webadmin.properties b/server/apps/postgres-app/docker-configuration/webadmin.properties index 5d72d99b74..42c276ec18 100644 --- a/server/apps/postgres-app/docker-configuration/webadmin.properties +++ b/server/apps/postgres-app/docker-configuration/webadmin.properties @@ -51,4 +51,12 @@ https.enabled=false # List of fully qualified class names that should be exposed over webadmin # in addition to your product default routes. Routes needs to be located # within the classpath or in the ./extensions-jars folder. -#extensions.routes= \ No newline at end of file +#extensions.routes= + +# Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true diff --git a/server/apps/postgres-app/sample-configuration/webadmin.properties b/server/apps/postgres-app/sample-configuration/webadmin.properties index e21c89b0bc..c2a336b1e0 100644 --- a/server/apps/postgres-app/sample-configuration/webadmin.properties +++ b/server/apps/postgres-app/sample-configuration/webadmin.properties @@ -49,6 +49,13 @@ https.enabled=false #extensions.routes= # Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true + # Configure one or more passwords (comma separated) for WebAdmin authentication #password=secret1,secret2 diff --git a/server/apps/postgres-app/src/test/resources/webadmin.properties b/server/apps/postgres-app/src/test/resources/webadmin.properties index 3386a14238..435136da30 100644 --- a/server/apps/postgres-app/src/test/resources/webadmin.properties +++ b/server/apps/postgres-app/src/test/resources/webadmin.properties @@ -22,4 +22,7 @@ enabled=true port=0 -host=127.0.0.1 \ No newline at end of file +host=127.0.0.1 + +# These tests exercise WebAdmin routes without authentication +password.generate=false diff --git a/server/apps/scaling-pulsar-smtp/sample-configuration/webadmin.properties b/server/apps/scaling-pulsar-smtp/sample-configuration/webadmin.properties index 5a494e0e59..986133dfa5 100644 --- a/server/apps/scaling-pulsar-smtp/sample-configuration/webadmin.properties +++ b/server/apps/scaling-pulsar-smtp/sample-configuration/webadmin.properties @@ -49,6 +49,13 @@ https.enabled=false #extensions.routes= # Password authentication settings + +# Generate a random password upon start up when no password is configured below. +# The generated password is written in the logs, and changes upon each restart. +# Set it to false to run WebAdmin without any authentication. +# Defaults to true +password.generate=true + # Configure one or more passwords (comma separated) for WebAdmin authentication #password=secret1,secret2 diff --git a/server/apps/webadmin-cli/src/test/resources/webadmin.properties b/server/apps/webadmin-cli/src/test/resources/webadmin.properties index 3386a14238..435136da30 100644 --- a/server/apps/webadmin-cli/src/test/resources/webadmin.properties +++ b/server/apps/webadmin-cli/src/test/resources/webadmin.properties @@ -22,4 +22,7 @@ enabled=true port=0 -host=127.0.0.1 \ No newline at end of file +host=127.0.0.1 + +# These tests exercise WebAdmin routes without authentication +password.generate=false diff --git a/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/WebAdminServerModule.java b/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/WebAdminServerModule.java index 000e6c9901..228fced7bc 100644 --- a/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/WebAdminServerModule.java +++ b/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/WebAdminServerModule.java @@ -60,6 +60,7 @@ import org.apache.james.webadmin.authentication.AuthenticationFilter; import org.apache.james.webadmin.authentication.JwtFilter; import org.apache.james.webadmin.authentication.NoAuthenticationFilter; import org.apache.james.webadmin.authentication.PasswordFilter; +import org.apache.james.webadmin.authentication.PasswordGenerator; import org.apache.james.webadmin.dto.DTOModuleInjections; import org.apache.james.webadmin.mdc.RequestLogger; import org.apache.james.webadmin.utils.JsonTransformer; @@ -83,6 +84,7 @@ public class WebAdminServerModule extends AbstractModule { private static final boolean DEFAULT_JWT_DISABLED = false; private static final boolean DEFAULT_DISABLED = false; + private static final boolean DEFAULT_PASSWORD_GENERATION_ENABLED = true; private static final String DEFAULT_NO_CORS_ORIGIN = null; private static final boolean DEFAULT_CORS_DISABLED = false; private static final String DEFAULT_NO_KEYSTORE = null; @@ -142,9 +144,10 @@ public class WebAdminServerModule extends AbstractModule { Configuration configurationFile = propertiesProvider.getConfiguration("webadmin"); List<String> additionalRoutes = additionalRoutes(configurationFile); + boolean webAdminEnabled = configurationFile.getBoolean("enabled", DEFAULT_DISABLED); return WebAdminConfiguration.builder() - .enable(configurationFile.getBoolean("enabled", DEFAULT_DISABLED)) + .enable(webAdminEnabled) .port(port(configurationFile)) .tls(readHttpsConfiguration(configurationFile)) .enableCORS(configurationFile.getBoolean("cors.enable", DEFAULT_CORS_DISABLED)) @@ -155,7 +158,7 @@ public class WebAdminServerModule extends AbstractModule { Optional.ofNullable(configurationFile.getString("jwt.publickeypem.url", null)))) .maxThreadCount(Optional.ofNullable(configurationFile.getInteger("maxThreadCount", null))) .minThreadCount(Optional.ofNullable(configurationFile.getInteger("minThreadCount", null))) - .password(Optional.ofNullable(configurationFile.getString("password", null))) + .password(password(configurationFile, webAdminEnabled)) .readOnlyPassword(Optional.ofNullable(configurationFile.getString("password.readonly", null))) .noDeletePassword(Optional.ofNullable(configurationFile.getString("password.nodelete", null))) .build(); @@ -174,6 +177,33 @@ public class WebAdminServerModule extends AbstractModule { return new FixedPortSupplier(portNumber); } + @VisibleForTesting + Optional<String> password(Configuration configurationFile, boolean webAdminEnabled) { + Optional<String> configuredPassword = Optional.ofNullable(configurationFile.getString("password", null)); + + if (configuredPassword.isPresent()) { + return configuredPassword; + } + if (shouldGeneratePassword(configurationFile, webAdminEnabled)) { + return Optional.of(generateAndLogPassword()); + } + return Optional.empty(); + } + + private boolean shouldGeneratePassword(Configuration configurationFile, boolean webAdminEnabled) { + return configurationFile.getBoolean("password.generate", DEFAULT_PASSWORD_GENERATION_ENABLED) + && webAdminEnabled + && !configurationFile.getBoolean("jwt.enabled", DEFAULT_JWT_DISABLED); + } + + private String generateAndLogPassword() { + String password = PasswordGenerator.generate(); + LOGGER.warn("No WebAdmin password had been configured: a random one had been generated for this run. " + + "Supply it within the `Password` header of your WebAdmin requests, or pin it with the `password` entry " + + "of webadmin.properties. Generated WebAdmin password: {}", password); + return password; + } + @VisibleForTesting ImmutableList<String> additionalRoutes(Configuration configurationFile) { return ImmutableList.copyOf(configurationFile.getStringArray("extensions.routes")); diff --git a/server/container/guice/protocols/webadmin/src/test/java/org/apache/james/modules/server/WebAdminServerModuleTest.java b/server/container/guice/protocols/webadmin/src/test/java/org/apache/james/modules/server/WebAdminServerModuleTest.java index 7d81f0b423..bbe213d2ed 100644 --- a/server/container/guice/protocols/webadmin/src/test/java/org/apache/james/modules/server/WebAdminServerModuleTest.java +++ b/server/container/guice/protocols/webadmin/src/test/java/org/apache/james/modules/server/WebAdminServerModuleTest.java @@ -21,11 +21,18 @@ package org.apache.james.modules.server; import static org.assertj.core.api.Assertions.assertThat; +import java.util.Optional; + import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.james.utils.PropertiesProvider; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class WebAdminServerModuleTest { + private static final boolean WEBADMIN_ENABLED = true; + private static final boolean WEBADMIN_DISABLED = false; + @Test void shouldReturnEmptyWhenNoField() throws Exception { Configuration configuration = getConfiguration("webadmin-none"); @@ -61,4 +68,69 @@ class WebAdminServerModuleTest { private Configuration getConfiguration(String name) throws Exception { return PropertiesProvider.forTesting().getConfiguration(name); } + + @Nested + class PasswordGeneration { + @Test + void passwordShouldBeGeneratedByDefault() { + assertThat(new WebAdminServerModule().password(new PropertiesConfiguration(), WEBADMIN_ENABLED)) + .isNotEmpty(); + } + + @Test + void passwordShouldBeEmptyWhenGenerationIsDisabled() { + assertThat(new WebAdminServerModule().password(configuration("password.generate", false), WEBADMIN_ENABLED)) + .isEmpty(); + } + + @Test + void passwordShouldBeGeneratedWhenGenerationIsEnabled() { + assertThat(new WebAdminServerModule().password(configuration("password.generate", true), WEBADMIN_ENABLED)) + .isNotEmpty(); + } + + @Test + void generatedPasswordShouldNotContainThePasswordSeparator() { + assertThat(new WebAdminServerModule().password(configuration("password.generate", true), WEBADMIN_ENABLED)) + .hasValueSatisfying(password -> assertThat(password).doesNotContain(",")); + } + + @Test + void generatedPasswordsShouldBeRandom() { + Optional<String> firstPassword = new WebAdminServerModule().password(configuration("password.generate", true), WEBADMIN_ENABLED); + Optional<String> secondPassword = new WebAdminServerModule().password(configuration("password.generate", true), WEBADMIN_ENABLED); + + assertThat(firstPassword).isNotEqualTo(secondPassword); + } + + @Test + void configuredPasswordShouldTakePrecedenceOverGeneration() { + PropertiesConfiguration configuration = configuration("password.generate", true); + configuration.addProperty("password", "secret"); + + assertThat(new WebAdminServerModule().password(configuration, WEBADMIN_ENABLED)) + .contains("secret"); + } + + @Test + void passwordShouldNotBeGeneratedWhenWebAdminIsDisabled() { + assertThat(new WebAdminServerModule().password(configuration("password.generate", true), WEBADMIN_DISABLED)) + .isEmpty(); + } + + @Test + void passwordShouldNotBeGeneratedWhenJwtIsEnabled() { + PropertiesConfiguration configuration = configuration("password.generate", true); + configuration.addProperty("jwt.enabled", true); + + assertThat(new WebAdminServerModule().password(configuration, WEBADMIN_ENABLED)) + .isEmpty(); + } + + private PropertiesConfiguration configuration(String key, Object value) { + PropertiesConfiguration configuration = new PropertiesConfiguration(); + configuration.addProperty(key, value); + return configuration; + } + } } \ No newline at end of file diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/resources/webadmin.properties b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/resources/webadmin.properties index 78a176aabd..6602aa0f0a 100644 --- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/resources/webadmin.properties +++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/resources/webadmin.properties @@ -24,4 +24,7 @@ enabled=true port=0 host=127.0.0.1 -extensions.routes=org.apache.james.webadmin.dropwizard.MetricsRoutes \ No newline at end of file +extensions.routes=org.apache.james.webadmin.dropwizard.MetricsRoutes + +# These tests exercise WebAdmin routes without authentication +password.generate=false diff --git a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/resources/webadmin.properties b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/resources/webadmin.properties index 78a176aabd..6602aa0f0a 100644 --- a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/resources/webadmin.properties +++ b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/resources/webadmin.properties @@ -24,4 +24,7 @@ enabled=true port=0 host=127.0.0.1 -extensions.routes=org.apache.james.webadmin.dropwizard.MetricsRoutes \ No newline at end of file +extensions.routes=org.apache.james.webadmin.dropwizard.MetricsRoutes + +# These tests exercise WebAdmin routes without authentication +password.generate=false diff --git a/server/protocols/webadmin-integration-test/postgres-webadmin-integration-test/src/test/resources/webadmin.properties b/server/protocols/webadmin-integration-test/postgres-webadmin-integration-test/src/test/resources/webadmin.properties index 78a176aabd..6602aa0f0a 100644 --- a/server/protocols/webadmin-integration-test/postgres-webadmin-integration-test/src/test/resources/webadmin.properties +++ b/server/protocols/webadmin-integration-test/postgres-webadmin-integration-test/src/test/resources/webadmin.properties @@ -24,4 +24,7 @@ enabled=true port=0 host=127.0.0.1 -extensions.routes=org.apache.james.webadmin.dropwizard.MetricsRoutes \ No newline at end of file +extensions.routes=org.apache.james.webadmin.dropwizard.MetricsRoutes + +# These tests exercise WebAdmin routes without authentication +password.generate=false diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/authentication/PasswordGenerator.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/authentication/PasswordGenerator.java new file mode 100644 index 0000000000..da032ba61f --- /dev/null +++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/authentication/PasswordGenerator.java @@ -0,0 +1,45 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.webadmin.authentication; + +import java.security.SecureRandom; +import java.util.Base64; + +/** + * Generates ephemeral WebAdmin passwords, enabling a secure setup without hardcoded credentials. + * + * The base64 URL alphabet is used as it excludes the comma, which {@link PasswordFilter} relies on + * as a password separator. + */ +public class PasswordGenerator { + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding(); + private static final int ENTROPY_BYTE_COUNT = 32; + + public static String generate() { + byte[] entropy = new byte[ENTROPY_BYTE_COUNT]; + SECURE_RANDOM.nextBytes(entropy); + return ENCODER.encodeToString(entropy); + } + + private PasswordGenerator() { + + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
