You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/02/21 01:15:30 UTC

[james-project] 02/08: fixup! JAMES-3881 Set a JMX password

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 919e518672f7dc3ed61ac434c88c8bf69c7a75c9
Author: Tung Tran <vt...@linagora.com>
AuthorDate: Tue Feb 14 06:58:02 2023 +0700

    fixup! JAMES-3881 Set a JMX password
---
 .../cli/JmxSecurityServerIntegrationTest.java      | 115 +++++++++++++++++++++
 .../main/java/org/apache/james/cli/ServerCmd.java  |  24 ++++-
 .../apache/james/cli/probe/impl/JmxConnection.java |  16 +++
 .../java/org/apache/james/cli/ServerCmdTest.java   |  59 +++++++++++
 .../docs/modules/ROOT/pages/configure/jmx.adoc     |  34 ++++++
 .../org/apache/james/TemporaryJamesServer.java     |   2 +-
 .../org/apache/james/modules/server/JMXServer.java |  39 ++++---
 .../james/modules/server/JmxConfiguration.java     |   8 +-
 8 files changed, 277 insertions(+), 20 deletions(-)

diff --git a/server/apps/cli-integration-tests/src/test/java/org/apache/james/cli/JmxSecurityServerIntegrationTest.java b/server/apps/cli-integration-tests/src/test/java/org/apache/james/cli/JmxSecurityServerIntegrationTest.java
new file mode 100644
index 0000000000..089016684e
--- /dev/null
+++ b/server/apps/cli-integration-tests/src/test/java/org/apache/james/cli/JmxSecurityServerIntegrationTest.java
@@ -0,0 +1,115 @@
+/****************************************************************
+ * 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.cli;
+
+import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.TemporaryJamesServer;
+import org.apache.james.cli.util.OutputCapture;
+import org.apache.james.data.UsersRepositoryModuleChooser;
+import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
+import org.apache.james.modules.data.MemoryUsersRepositoryModule;
+import org.apache.james.modules.server.JMXServerModule;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import com.google.common.collect.ImmutableList;
+
+class JmxSecurityServerIntegrationTest {
+
+    private static final List<String> BASE_CONFIGURATION_FILE_NAMES = ImmutableList.of("dnsservice.xml",
+        "dnsservice.xml",
+        "imapserver.xml",
+        "jwt_publickey",
+        "lmtpserver.xml",
+        "mailetcontainer.xml",
+        "mailrepositorystore.xml",
+        "managesieveserver.xml",
+        "pop3server.xml",
+        "smtpserver.xml");
+
+    private GuiceJamesServer jamesServer;
+
+    @BeforeEach
+    void beforeEach(@TempDir Path workingPath) throws Exception {
+        TemporaryJamesServer temporaryJamesServer = new TemporaryJamesServer(workingPath.toFile(), BASE_CONFIGURATION_FILE_NAMES);
+        writeFile(workingPath + "/conf/jmx.properties", "jmx.address=127.0.0.1\n" +
+            "jmx.port=9999\n");
+        writeFile(workingPath + "/conf/jmxremote.password", "james-admin pass1\n");
+        writeFile(workingPath + "/conf/jmxremote.access", "james-admin readwrite\n");
+
+        jamesServer = temporaryJamesServer.getJamesServer()
+            .combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE)
+            .combineWith(new UsersRepositoryModuleChooser(new MemoryUsersRepositoryModule())
+                .chooseModules(UsersRepositoryModuleChooser.Implementation.DEFAULT))
+            .overrideWith(new JMXServerModule(),
+                binder -> binder.bind(ListeningMessageSearchIndex.class).toInstance(mock(ListeningMessageSearchIndex.class)));
+        jamesServer.start();
+
+    }
+
+    @AfterEach
+    void afterEach() {
+        if (jamesServer != null && jamesServer.isStarted()) {
+            jamesServer.stop();
+        }
+    }
+
+    @Test
+    void jamesCliShouldFailWhenNotGiveAuthCredential() throws Exception {
+        OutputCapture outputCapture = new OutputCapture();
+
+        assertThatThrownBy(() -> ServerCmd.executeAndOutputToStream(new String[]{"-h", "127.0.0.1", "-p", "9999", "listdomains"}, outputCapture.getPrintStream()))
+            .isInstanceOf(SecurityException.class)
+            .hasMessageContaining("Authentication failed! Credentials required");
+    }
+
+    @Test
+    void jamesCliShouldWorkWhenGiveAuthCredential() throws Exception {
+        OutputCapture outputCapture = new OutputCapture();
+        ServerCmd.executeAndOutputToStream(new String[]{"-h", "127.0.0.1", "-p", "9999", "-username", "james-admin", "-password", "pass1",
+            "listdomains"}, outputCapture.getPrintStream());
+
+        assertThat(outputCapture.getContent()).contains("localhost");
+    }
+
+    private void writeFile(String fileNamePath, String data) {
+        File passwordFile = new File(fileNamePath);
+        try (OutputStream outputStream = new FileOutputStream(passwordFile)) {
+            IOUtils.write(data, outputStream, StandardCharsets.UTF_8);
+        } catch (IOException ignored) {
+        }
+    }
+}
diff --git a/server/apps/cli/src/main/java/org/apache/james/cli/ServerCmd.java b/server/apps/cli/src/main/java/org/apache/james/cli/ServerCmd.java
index bb42cb5650..77b3aa1496 100644
--- a/server/apps/cli/src/main/java/org/apache/james/cli/ServerCmd.java
+++ b/server/apps/cli/src/main/java/org/apache/james/cli/ServerCmd.java
@@ -18,13 +18,16 @@
  ****************************************************************/
 package org.apache.james.cli;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
+import java.util.StringTokenizer;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 
@@ -34,6 +37,7 @@ import org.apache.commons.cli.DefaultParser;
 import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
+import org.apache.commons.io.FileUtils;
 import org.apache.james.cli.exceptions.InvalidArgumentNumberException;
 import org.apache.james.cli.exceptions.JamesCliException;
 import org.apache.james.cli.exceptions.MissingCommandException;
@@ -74,6 +78,7 @@ public class ServerCmd {
 
     public static final String JMX_USERNAME_OPT = "username";
     public static final String JMX_PASSWORD_OPT = "password";
+    public static final String JMX_PASSWORD_FILE_PATH_DEFAULT = System.getProperty("user.home") + "/conf/jmxremote.password";
 
     private static final String DEFAULT_HOST = "127.0.0.1";
     private static final int DEFAULT_PORT = 9999;
@@ -116,7 +121,7 @@ public class ServerCmd {
     public static void executeAndOutputToStream(String[] args, PrintStream printStream) throws Exception {
         Stopwatch stopWatch = Stopwatch.createStarted();
         CommandLine cmd = parseCommandLine(args);
-        JmxConnection jmxConnection = new JmxConnection(getHost(cmd), getPort(cmd), getAuthCredential(cmd));
+        JmxConnection jmxConnection = new JmxConnection(getHost(cmd), getPort(cmd), getAuthCredential(cmd, JMX_PASSWORD_FILE_PATH_DEFAULT));
 
         CmdType cmdType = new ServerCmd(
                 new JmxDataProbe().connect(jmxConnection),
@@ -161,7 +166,13 @@ public class ServerCmd {
         return host;
     }
 
-    static Optional<JmxConnection.AuthCredential> getAuthCredential(CommandLine cmd) {
+    @VisibleForTesting
+    static Optional<JmxConnection.AuthCredential> getAuthCredential(CommandLine cmd, String jmxPasswordFilePath) {
+        return getAuthCredentialFromCommandLine(cmd)
+            .or(() -> getAuthCredentialFromJmxPasswordFile(jmxPasswordFilePath));
+    }
+
+    static Optional<JmxConnection.AuthCredential> getAuthCredentialFromCommandLine(CommandLine cmd) {
         String username = cmd.getOptionValue(JMX_USERNAME_OPT);
         String password = cmd.getOptionValue(JMX_PASSWORD_OPT);
         if (Strings.isNullOrEmpty(username) || Strings.isNullOrEmpty(password)) {
@@ -170,6 +181,15 @@ public class ServerCmd {
         return Optional.of(new JmxConnection.AuthCredential(username, password));
     }
 
+    static Optional<JmxConnection.AuthCredential> getAuthCredentialFromJmxPasswordFile(String jmxPasswordFilePath) {
+        try {
+            StringTokenizer stringTokenizer = new StringTokenizer(FileUtils.readLines(new File(jmxPasswordFilePath), StandardCharsets.US_ASCII).get(0), " ");
+            return Optional.of(new JmxConnection.AuthCredential(stringTokenizer.nextToken(), stringTokenizer.nextToken()));
+        } catch (Exception e) {
+            return Optional.empty();
+        }
+    }
+
     @VisibleForTesting
     static int getPort(CommandLine cmd) throws ParseException {
         String portNum = cmd.getOptionValue(PORT_OPT_LONG);
diff --git a/server/apps/cli/src/main/java/org/apache/james/cli/probe/impl/JmxConnection.java b/server/apps/cli/src/main/java/org/apache/james/cli/probe/impl/JmxConnection.java
index 3c410fb988..a6762b1054 100644
--- a/server/apps/cli/src/main/java/org/apache/james/cli/probe/impl/JmxConnection.java
+++ b/server/apps/cli/src/main/java/org/apache/james/cli/probe/impl/JmxConnection.java
@@ -21,6 +21,7 @@ package org.apache.james.cli.probe.impl;
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 
 import javax.management.MBeanServerConnection;
@@ -43,6 +44,21 @@ public class JmxConnection implements Closeable {
             this.username = username;
             this.password = password;
         }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof AuthCredential) {
+                AuthCredential that = (AuthCredential) o;
+                return Objects.equals(this.username, that.username)
+                    && Objects.equals(this.password, that.password);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(username, password);
+        }
     }
 
     private static final String fmtUrl = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi";
diff --git a/server/apps/cli/src/test/java/org/apache/james/cli/ServerCmdTest.java b/server/apps/cli/src/test/java/org/apache/james/cli/ServerCmdTest.java
index 0b8d025dda..5f169ad3d3 100644
--- a/server/apps/cli/src/test/java/org/apache/james/cli/ServerCmdTest.java
+++ b/server/apps/cli/src/test/java/org/apache/james/cli/ServerCmdTest.java
@@ -25,14 +25,24 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Optional;
 
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.ParseException;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.james.cli.exceptions.InvalidArgumentNumberException;
 import org.apache.james.cli.exceptions.MissingCommandException;
 import org.apache.james.cli.exceptions.UnrecognizedCommandException;
+import org.apache.james.cli.probe.impl.JmxConnection;
 import org.apache.james.cli.probe.impl.JmxDataProbe;
 import org.apache.james.cli.probe.impl.JmxMailboxProbe;
 import org.apache.james.cli.probe.impl.JmxQuotaProbe;
@@ -48,6 +58,7 @@ import org.apache.james.mailbox.model.SerializableQuotaLimitValue;
 import org.apache.james.rrt.lib.MappingsImpl;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import com.google.common.collect.ImmutableList;
 
@@ -1200,4 +1211,52 @@ class ServerCmdTest {
             .isInstanceOf(IllegalArgumentException.class);
     }
 
+    @Test
+    void getAuthCredentialShouldReturnEmptyWhenNotGiven(@TempDir Path tempDir) throws Exception {
+        String[] arguments = {"-h", "127.0.0.1", "-p", "99999", "command", "arg1", "arg2", "arg3"};
+        CommandLine commandLine = ServerCmd.parseCommandLine(arguments);
+
+        assertThat(ServerCmd.getAuthCredential(commandLine, tempDir.toString()))
+            .isEmpty();
+    }
+
+    @Test
+    void getAuthCredentialShouldReturnValueWhenGivenViaCommandLine(@TempDir Path tempDir) throws Exception {
+        String[] arguments = {"-h", "127.0.0.1", "-p", "99999", "-username", "james-admin", "-password", "123456", "command", "arg1", "arg2", "arg3"};
+        CommandLine commandLine = ServerCmd.parseCommandLine(arguments);
+
+        assertThat(ServerCmd.getAuthCredential(commandLine, tempDir.toString()))
+            .isEqualTo(Optional.of(new JmxConnection.AuthCredential("james-admin", "123456")));
+    }
+
+    @Test
+    void getAuthCredentialShouldReturnValueWhenGivenViaJmxPasswordFile(@TempDir Path tempDir) throws Exception {
+        String[] arguments = {"-h", "127.0.0.1", "-p", "99999", "command", "arg1", "arg2", "arg3"};
+        CommandLine commandLine = ServerCmd.parseCommandLine(arguments);
+
+        File passwordFile = new File(tempDir.toString() + "/jmxremote.password");
+        try (OutputStream outputStream = new FileOutputStream(passwordFile)) {
+            IOUtils.write("james-admin1 pass2\n", outputStream, StandardCharsets.UTF_8);
+        } catch (IOException ignored) {
+        }
+
+        assertThat(ServerCmd.getAuthCredential(commandLine, passwordFile.getPath()))
+            .isEqualTo(Optional.of(new JmxConnection.AuthCredential("james-admin1", "pass2")));
+    }
+
+    @Test
+    void getAuthCredentialShouldPreferCommandlineValue(@TempDir Path tempDir) throws Exception {
+        String[] arguments = {"-h", "127.0.0.1", "-p", "99999", "-username", "james-admin", "-password", "123456", "command", "arg1", "arg2", "arg3"};
+        CommandLine commandLine = ServerCmd.parseCommandLine(arguments);
+
+        File passwordFile = new File(tempDir.toString() + "/jmxremote.password");
+        try (OutputStream outputStream = new FileOutputStream(passwordFile)) {
+            IOUtils.write("james-admin1 pass2\n", outputStream, StandardCharsets.UTF_8);
+        } catch (IOException ignored) {
+        }
+
+        assertThat(ServerCmd.getAuthCredential(commandLine, tempDir.toString()))
+            .isEqualTo(Optional.of(new JmxConnection.AuthCredential("james-admin", "123456")));
+    }
+
 }
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmx.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmx.adoc
index 695128588b..996b8a6ebd 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmx.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/jmx.adoc
@@ -22,3 +22,37 @@ in GIT to get some examples and hints.
 
 To access from a remote location, it has been reported that `-Dcom.sun.management.jmxremote.ssl=false` is needed as
 a JVM argument.
+
+
+== JMX Security
+
+In order to set up JMX authentication, we need to put `jmxremote.password` and `jmxremote.access` file
+to `/conf` directory.
+
+- `jmxremote.password`: define the username and password, that will be used by the client (here is james-cli)
+
+File's content example:
+```
+james-admin pass1
+```
+
+- `jmxremote.access`: define the pair of username and access permission
+
+File's content example:
+```
+james-admin readWrite
+```
+
+When James runs with option `-Djames.jmx.credential.generation=true`, James will automatically generate `jmxremote.password` if the file does not exist.
+Then the default username is `james-admin` and a random password.
+
+=== James-cli
+
+When the JMX server starts with authentication configuration, it will require the client need provide username/password for bypass.
+To do that, we need set arguments `-username` and `-password` for the command request.
+
+Command example:
+```
+james-cli -h 127.0.0.1 -p 9999 -username james-admin -password pass1 listdomains
+```
+
diff --git a/server/container/guice/common/src/main/java/org/apache/james/TemporaryJamesServer.java b/server/container/guice/common/src/main/java/org/apache/james/TemporaryJamesServer.java
index 811189f360..231d3586c2 100644
--- a/server/container/guice/common/src/main/java/org/apache/james/TemporaryJamesServer.java
+++ b/server/container/guice/common/src/main/java/org/apache/james/TemporaryJamesServer.java
@@ -88,7 +88,7 @@ public class TemporaryJamesServer {
             .forEach(resourceName -> copyResource(resourcesFolder, resourceName));
     }
 
-    private void copyResource(Path resourcesFolder, String resourceName) {
+    public static void copyResource(Path resourcesFolder, String resourceName) {
         var resolvedResource = resourcesFolder.resolve(resourceName);
         try (OutputStream outputStream = new FileOutputStream(resolvedResource.toFile())) {
             URL resource = ClassLoader.getSystemClassLoader().getResource(resourceName);
diff --git a/server/container/guice/jmx/src/main/java/org/apache/james/modules/server/JMXServer.java b/server/container/guice/jmx/src/main/java/org/apache/james/modules/server/JMXServer.java
index 2e16907674..badd87f9b3 100644
--- a/server/container/guice/jmx/src/main/java/org/apache/james/modules/server/JMXServer.java
+++ b/server/container/guice/jmx/src/main/java/org/apache/james/modules/server/JMXServer.java
@@ -19,8 +19,10 @@
 
 package org.apache.james.modules.server;
 
-import static org.apache.james.modules.server.JmxConfiguration.JMX_CREDENTIAL_GENERATION_ENABLE_DEFAULT;
-import static org.apache.james.modules.server.JmxConfiguration.JMX_CREDENTIAL_GENERATION_ENABLE_PROPERTY;
+import static org.apache.james.modules.server.JmxConfiguration.ACCESS_FILE_NAME;
+import static org.apache.james.modules.server.JmxConfiguration.JMX_CREDENTIAL_GENERATION_ENABLE_DEFAULT_VALUE;
+import static org.apache.james.modules.server.JmxConfiguration.JMX_CREDENTIAL_GENERATION_ENABLE_PROPERTY_KEY;
+import static org.apache.james.modules.server.JmxConfiguration.PASSWORD_FILE_NAME;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -31,6 +33,7 @@ import java.net.ServerSocket;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
 import java.rmi.registry.LocateRegistry;
 import java.util.HashSet;
 import java.util.Map;
@@ -47,6 +50,7 @@ import javax.management.remote.JMXServiceURL;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.james.filesystem.api.JamesDirectoriesProvider;
 import org.apache.james.lifecycle.api.Startable;
 import org.apache.james.util.FunctionalUtils;
 import org.apache.james.util.RestrictingRMISocketFactory;
@@ -62,13 +66,17 @@ public class JMXServer implements Startable {
     private final JmxConfiguration jmxConfiguration;
     private final Set<String> registeredKeys;
     private final Object lock;
+    private final String jmxPasswordFilePath;
+    private final String jmxAccessFilePath;
     private JMXConnectorServer jmxConnectorServer;
     private boolean isStarted;
     private RestrictingRMISocketFactory restrictingRMISocketFactory;
 
     @Inject
-    public JMXServer(JmxConfiguration jmxConfiguration) {
+    public JMXServer(JmxConfiguration jmxConfiguration, JamesDirectoriesProvider directoriesProvider) {
         this.jmxConfiguration = jmxConfiguration;
+        this.jmxPasswordFilePath = directoriesProvider.getConfDirectory() + PASSWORD_FILE_NAME;
+        this.jmxAccessFilePath = directoriesProvider.getConfDirectory() + ACCESS_FILE_NAME;
         isStarted = false;
         registeredKeys = new HashSet<>();
         lock = new Object();
@@ -116,8 +124,8 @@ public class JMXServer implements Startable {
 
             Map<String, String> environment = Optional.of(existJmxPasswordFile())
                 .filter(FunctionalUtils.identityPredicate())
-                .map(hasJmxPasswordFile -> ImmutableMap.of("jmx.remote.x.password.file", JmxConfiguration.PASSWORD_FILE_PATH,
-                    "jmx.remote.x.access.file", JmxConfiguration.ACCESS_FILE_PATH))
+                .map(hasJmxPasswordFile -> ImmutableMap.of("jmx.remote.x.password.file", jmxPasswordFilePath,
+                    "jmx.remote.x.access.file", jmxAccessFilePath))
                 .orElse(ImmutableMap.of());
 
             jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(new JMXServiceURL(serviceURL),
@@ -147,37 +155,42 @@ public class JMXServer implements Startable {
     }
 
     private void generateJMXPasswordFileIfNeed() {
-        if (Boolean.parseBoolean(System.getProperty(JMX_CREDENTIAL_GENERATION_ENABLE_PROPERTY, JMX_CREDENTIAL_GENERATION_ENABLE_DEFAULT))
+        if (Boolean.parseBoolean(System.getProperty(JMX_CREDENTIAL_GENERATION_ENABLE_PROPERTY_KEY, JMX_CREDENTIAL_GENERATION_ENABLE_DEFAULT_VALUE))
             && !existJmxPasswordFile()) {
             generateJMXPasswordFile();
         }
     }
 
     private boolean existJmxPasswordFile() {
-        return Files.exists(Path.of(JmxConfiguration.PASSWORD_FILE_PATH)) && Files.exists(Path.of(JmxConfiguration.ACCESS_FILE_PATH));
+        return Files.exists(Path.of(jmxPasswordFilePath)) && Files.exists(Path.of(jmxAccessFilePath));
     }
 
     private void generateJMXPasswordFile() {
-        File passwordFile = new File(JmxConfiguration.PASSWORD_FILE_PATH);
+        File passwordFile = new File(jmxPasswordFilePath);
         if (!passwordFile.exists()) {
             try (OutputStream outputStream = new FileOutputStream(passwordFile)) {
                 String randomPassword = RandomStringUtils.random(10, true, true);
                 IOUtils.write(JmxConfiguration.JAMES_ADMIN_USER_DEFAULT + " " + randomPassword + "\n", outputStream, StandardCharsets.UTF_8);
-                LOGGER.info("Generated JMX password file: " + JmxConfiguration.PASSWORD_FILE_PATH);
+                setPermissionOwnerOnly(passwordFile);
+                LOGGER.info("Generated JMX password file: " + passwordFile.getPath());
             } catch (IOException e) {
-                throw new RuntimeException("Error when creating JMX password file: " + JmxConfiguration.PASSWORD_FILE_PATH, e);
+                throw new RuntimeException("Error when creating JMX password file: " + passwordFile.getPath(), e);
             }
         }
 
-        File accessFile = new File(JmxConfiguration.ACCESS_FILE_PATH);
+        File accessFile = new File(jmxAccessFilePath);
         if (!accessFile.exists()) {
             try (OutputStream outputStream = new FileOutputStream(accessFile)) {
                 IOUtils.write(JmxConfiguration.JAMES_ADMIN_USER_DEFAULT + " readwrite\n", outputStream, StandardCharsets.UTF_8);
-                LOGGER.info("Generated JMX access file: " + JmxConfiguration.ACCESS_FILE_PATH);
+                setPermissionOwnerOnly(accessFile);
+                LOGGER.info("Generated JMX access file: " + accessFile.getPath());
             } catch (IOException e) {
-                throw new RuntimeException("Error when creating JMX access file: " + JmxConfiguration.ACCESS_FILE_PATH, e);
+                throw new RuntimeException("Error when creating JMX access file: " + accessFile.getPath(), e);
             }
         }
     }
 
+    private void setPermissionOwnerOnly(File file) throws IOException {
+        Files.setPosixFilePermissions(file.toPath(), Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));
+    }
 }
diff --git a/server/container/guice/jmx/src/main/java/org/apache/james/modules/server/JmxConfiguration.java b/server/container/guice/jmx/src/main/java/org/apache/james/modules/server/JmxConfiguration.java
index eeef6e2e09..c833990e20 100644
--- a/server/container/guice/jmx/src/main/java/org/apache/james/modules/server/JmxConfiguration.java
+++ b/server/container/guice/jmx/src/main/java/org/apache/james/modules/server/JmxConfiguration.java
@@ -34,10 +34,10 @@ public class JmxConfiguration {
     public static final String LOCALHOST = "localhost";
     public static final int DEFAULT_PORT = 9999;
     public static final boolean ENABLED = true;
-    public static final String JMX_CREDENTIAL_GENERATION_ENABLE_PROPERTY = "james.jmx.credential.generation";
-    public static final String JMX_CREDENTIAL_GENERATION_ENABLE_DEFAULT = "false";
-    public static final String PASSWORD_FILE_PATH = "/root/conf/jmxremote.password";
-    public static final String ACCESS_FILE_PATH = "/root/conf/jmxremote.access";
+    public static final String JMX_CREDENTIAL_GENERATION_ENABLE_PROPERTY_KEY = "james.jmx.credential.generation";
+    public static final String JMX_CREDENTIAL_GENERATION_ENABLE_DEFAULT_VALUE = "false";
+    public static final String PASSWORD_FILE_NAME = "jmxremote.password";
+    public static final String ACCESS_FILE_NAME = "jmxremote.access";
     public static final String JAMES_ADMIN_USER_DEFAULT = "james-admin";
 
     public static final JmxConfiguration DEFAULT_CONFIGURATION = new JmxConfiguration(ENABLED, Optional.of(Host.from(LOCALHOST, DEFAULT_PORT)));


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org