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]

Reply via email to