You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by ka...@apache.org on 2023/03/12 11:50:48 UTC

[james-project] branch 3.7.x updated (fd3812c0bc -> d3d7388386)

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

kao pushed a change to branch 3.7.x
in repository https://gitbox.apache.org/repos/asf/james-project.git


    from fd3812c0bc JAMES-3892 Allow configuring the count of retries in LocalDelivery [BACKPORT] (#1469)
     new e3afb128cf JAMES-3881 Set a JMX password
     new c8ea7c484d JAMES-3881 Ignore error generating JMX password
     new d2e722ab85 JAMES-3881 Sample configuration
     new 967d2fa54c JAMES-3881 Enable JMX password generation by default
     new 83d795a6e0 JAMES-3881 Update documentation accordingly
     new c2e6376b16 JAMES-3881 Docker packagings should locate jvm.properties
     new d3d7388386 JAMES-3881 WARN if no JMX authentication is setup

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../sample-configuration/jvm.properties            |   3 +
 .../cli/JmxSecurityServerIntegrationTest.java      | 115 +++++++++++++++++++++
 .../main/java/org/apache/james/cli/ServerCmd.java  |  39 ++++++-
 .../apache/james/cli/probe/impl/JmxConnection.java |  39 ++++++-
 .../java/org/apache/james/cli/ServerCmdTest.java   |  59 +++++++++++
 .../docs/modules/ROOT/pages/configure/jmx.adoc     |  34 ++++++
 .../sample-configuration/jvm.properties            |   3 +
 .../sample-configuration/jvm.properties            |   3 +
 server/apps/jpa-app/pom.xml                        |   1 +
 .../jpa-app/sample-configuration/jvm.properties    |   3 +
 server/apps/jpa-smtp-app/pom.xml                   |   1 +
 .../sample-configuration/jvm.properties            |   3 +
 server/apps/memory-app/pom.xml                     |   1 +
 .../memory-app/sample-configuration/jvm.properties |   3 +
 .../james/app/spring/JamesAppSpringMain.java       |   9 +-
 .../org/apache/james/TemporaryJamesServer.java     | 106 +++++++++++++++++++
 .../org/apache/james/modules/server/JMXServer.java |  79 +++++++++++++-
 .../james/modules/server/JmxConfiguration.java     |   5 +
 src/site/xdoc/server/config-system.xml             |  28 ++++-
 19 files changed, 524 insertions(+), 10 deletions(-)
 create mode 100644 server/apps/cli-integration-tests/src/test/java/org/apache/james/cli/JmxSecurityServerIntegrationTest.java
 create mode 100644 server/container/guice/common/src/main/java/org/apache/james/TemporaryJamesServer.java


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


[james-project] 04/07: JAMES-3881 Enable JMX password generation by default

Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 967d2fa54cee9fcc410364794c1f7e98c9251853
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Feb 15 22:00:18 2023 +0700

    JAMES-3881 Enable JMX password generation by default
    
    As this is more secure
    
    (cherry picked from commit 51eb19001b65581115331eb8db3112ec6fcd9378)
---
 .../src/main/java/org/apache/james/modules/server/JmxConfiguration.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 c833990e20..9eb616ce69 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
@@ -35,7 +35,7 @@ public class JmxConfiguration {
     public static final int DEFAULT_PORT = 9999;
     public static final boolean ENABLED = true;
     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 JMX_CREDENTIAL_GENERATION_ENABLE_DEFAULT_VALUE = "true";
     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";


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


[james-project] 02/07: JAMES-3881 Ignore error generating JMX password

Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c8ea7c484d5d88f79dc3e2d66ac2bc1d534fe24e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Feb 15 21:58:38 2023 +0700

    JAMES-3881 Ignore error generating JMX password
    
    Log a warning
    
    (cherry picked from commit a08b34c7971d90824fd174b8c91d54921a62f312)
---
 .../org/apache/james/modules/server/JMXServer.java | 40 ++++++++++++----------
 1 file changed, 22 insertions(+), 18 deletions(-)

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 badd87f9b3..6aa801314a 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
@@ -166,27 +166,31 @@ public class JMXServer implements Startable {
     }
 
     private void generateJMXPasswordFile() {
-        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);
-                setPermissionOwnerOnly(passwordFile);
-                LOGGER.info("Generated JMX password file: " + passwordFile.getPath());
-            } catch (IOException e) {
-                throw new RuntimeException("Error when creating JMX password file: " + passwordFile.getPath(), e);
+        try {
+            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);
+                    setPermissionOwnerOnly(passwordFile);
+                    LOGGER.info("Generated JMX password file: " + passwordFile.getPath());
+                } catch (IOException e) {
+                    throw new RuntimeException("Error when creating JMX password file: " + passwordFile.getPath(), e);
+                }
             }
-        }
 
-        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);
-                setPermissionOwnerOnly(accessFile);
-                LOGGER.info("Generated JMX access file: " + accessFile.getPath());
-            } catch (IOException e) {
-                throw new RuntimeException("Error when creating JMX access file: " + accessFile.getPath(), e);
+            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);
+                    setPermissionOwnerOnly(accessFile);
+                    LOGGER.info("Generated JMX access file: " + accessFile.getPath());
+                } catch (IOException e) {
+                    throw new RuntimeException("Error when creating JMX access file: " + accessFile.getPath(), e);
+                }
             }
+        } catch (Exception e) {
+            LOGGER.warn("Failure to auto-generate JMX password, fallback to unsecure JMX", e);
         }
     }
 


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


[james-project] 01/07: JAMES-3881 Set a JMX password

Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit e3afb128cfb5ff2bc22ad3a45d22cffa47cf2848
Author: Tung Van TRAN <vt...@linagora.com>
AuthorDate: Mon Feb 13 07:36:58 2023 +0700

    JAMES-3881 Set a JMX password
    
    (cherry picked from commit 57874c0a41fbdbf9e96740583cf2337766a5c816)
---
 .../cli/JmxSecurityServerIntegrationTest.java      | 115 +++++++++++++++++++++
 .../main/java/org/apache/james/cli/ServerCmd.java  |  39 ++++++-
 .../apache/james/cli/probe/impl/JmxConnection.java |  39 ++++++-
 .../java/org/apache/james/cli/ServerCmdTest.java   |  59 +++++++++++
 .../docs/modules/ROOT/pages/configure/jmx.adoc     |  34 ++++++
 server/apps/memory-app/pom.xml                     |   1 +
 .../org/apache/james/TemporaryJamesServer.java     | 106 +++++++++++++++++++
 .../org/apache/james/modules/server/JMXServer.java |  71 ++++++++++++-
 .../james/modules/server/JmxConfiguration.java     |   5 +
 9 files changed, 462 insertions(+), 7 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 29b9ff42f3..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;
@@ -72,6 +76,10 @@ public class ServerCmd {
     public static final String PORT_OPT_LONG = "port";
     public static final String PORT_OPT_SHORT = "p";
 
+    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;
     private static final Logger LOG = LoggerFactory.getLogger(ServerCmd.class);
@@ -79,7 +87,9 @@ public class ServerCmd {
     private static Options createOptions() {
         return new Options()
                 .addOption(HOST_OPT_SHORT, HOST_OPT_LONG, true, "node hostname or ip address")
-                .addOption(PORT_OPT_SHORT, PORT_OPT_LONG, true, "remote jmx agent port number");
+                .addOption(PORT_OPT_SHORT, PORT_OPT_LONG, true, "remote jmx agent port number")
+                .addOption(JMX_USERNAME_OPT, JMX_USERNAME_OPT, true, "remote jmx username")
+                .addOption(JMX_PASSWORD_OPT, JMX_PASSWORD_OPT, true, "remote jmx password");
     }
 
     /**
@@ -111,7 +121,8 @@ 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));
+        JmxConnection jmxConnection = new JmxConnection(getHost(cmd), getPort(cmd), getAuthCredential(cmd, JMX_PASSWORD_FILE_PATH_DEFAULT));
+
         CmdType cmdType = new ServerCmd(
                 new JmxDataProbe().connect(jmxConnection),
                 new JmxMailboxProbe().connect(jmxConnection),
@@ -155,6 +166,30 @@ public class ServerCmd {
         return host;
     }
 
+    @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)) {
+            return Optional.empty();
+        }
+        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 ed416f39f5..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
@@ -20,6 +20,9 @@ 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;
 import javax.management.MBeanServerInvocationHandler;
@@ -29,20 +32,50 @@ import javax.management.remote.JMXConnector;
 import javax.management.remote.JMXConnectorFactory;
 import javax.management.remote.JMXServiceURL;
 
+import com.google.common.collect.ImmutableMap;
+
 public class JmxConnection implements Closeable {
 
+    public static class AuthCredential {
+        String username;
+        String password;
+
+        public AuthCredential(String username, String password) {
+            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";
     private static final int defaultPort = 9999;
 
     public static JmxConnection defaultJmxConnection(String host) throws IOException {
-        return new JmxConnection(host, defaultPort);
+        return new JmxConnection(host, defaultPort, Optional.empty());
     }
     
     private final JMXConnector jmxConnector;
 
-    public JmxConnection(String host, int port) throws IOException {
+    public JmxConnection(String host, int port, Optional<AuthCredential> authCredential) throws IOException {
         JMXServiceURL jmxUrl = new JMXServiceURL(String.format(fmtUrl, host, port));
-        jmxConnector = JMXConnectorFactory.connect(jmxUrl, null);
+        Map<String, ?> env = authCredential
+            .map(credential -> ImmutableMap.of("jmx.remote.credentials", new String[]{credential.username, credential.password}))
+            .orElse(ImmutableMap.of());
+        jmxConnector = JMXConnectorFactory.connect(jmxUrl, env);
     }
 
     @Override
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/apps/memory-app/pom.xml b/server/apps/memory-app/pom.xml
index 1e1729e469..1736218be8 100644
--- a/server/apps/memory-app/pom.xml
+++ b/server/apps/memory-app/pom.xml
@@ -297,6 +297,7 @@
                             <jvmFlag>-Dworking.directory=/root/</jvmFlag>
                             <!-- Prevents Logjam (CVE-2015-4000) -->
                             <jvmFlag>-Djdk.tls.ephemeralDHKeySize=2048</jvmFlag>
+                            <jvmFlag>-Djames.jmx.credential.generation=true</jvmFlag>
                         </jvmFlags>
                         <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
                         <volumes>
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
new file mode 100644
index 0000000000..231d3586c2
--- /dev/null
+++ b/server/container/guice/common/src/main/java/org/apache/james/TemporaryJamesServer.java
@@ -0,0 +1,106 @@
+/****************************************************************
+ * 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;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.james.server.core.configuration.Configuration;
+
+import com.google.common.collect.ImmutableList;
+
+public class TemporaryJamesServer {
+    private static final List<String> CONFIGURATION_FILE_NAMES = ImmutableList.of(
+        "dnsservice.xml",
+        "domainlist.xml",
+        "imapserver.xml",
+        "keystore",
+        "listeners.xml",
+        "lmtpserver.xml",
+        "mailetcontainer.xml",
+        "mailrepositorystore.xml",
+        "managesieveserver.xml",
+        "pop3server.xml",
+        "smtpserver.xml",
+        "usersrepository.xml");
+
+    private final Configuration configuration;
+    private final File configurationFolder;
+    private final List<String> configurationFileNames;
+    private GuiceJamesServer jamesServer;
+
+    public TemporaryJamesServer(File workingDir) {
+        this(workingDir, CONFIGURATION_FILE_NAMES);
+    }
+
+    public TemporaryJamesServer(File workingDir, List<String> configurationFileNames) {
+        this.configurationFileNames = configurationFileNames;
+        Configuration configuration = Configuration.builder().workingDirectory(workingDir).build();
+        configurationFolder = workingDir.toPath().resolve("conf").toFile();
+        if (!configurationFolder.exists()) {
+            configurationFolder.mkdir();
+        }
+        copyResources(Paths.get(configurationFolder.getAbsolutePath()));
+        this.configuration = configuration;
+    }
+
+    public GuiceJamesServer getJamesServer() {
+        if (jamesServer == null) {
+            jamesServer = GuiceJamesServer.forConfiguration(configuration);
+        }
+        return jamesServer;
+    }
+
+    public void appendConfigurationFile(String configurationData, String configurationFileName) throws IOException {
+        try (OutputStream outputStream = new FileOutputStream(Paths.get(configurationFolder.getAbsolutePath(), configurationFileName).toFile())) {
+            IOUtils.write(configurationData, outputStream, StandardCharsets.UTF_8);
+        }
+    }
+
+    private void copyResources(Path resourcesFolder) {
+        configurationFileNames
+            .forEach(resourceName -> copyResource(resourcesFolder, 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);
+            if (resource != null) {
+                try (InputStream stream = resource.openStream()) {
+                    stream.transferTo(outputStream);
+                }
+            } else {
+                throw new RuntimeException("Failed to load configuration resource " + resourceName);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
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 e1ffb3ca2f..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,11 +19,25 @@
 
 package org.apache.james.modules.server;
 
+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;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.lang.management.ManagementFactory;
 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;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.annotation.PreDestroy;
@@ -34,7 +48,11 @@ import javax.management.remote.JMXConnectorServer;
 import javax.management.remote.JMXConnectorServerFactory;
 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;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -48,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();
@@ -98,8 +120,14 @@ public class JMXServer implements Startable {
                 + ":" + jmxConfiguration.getHost().getPort() + "/jmxrmi";
             restrictingRMISocketFactory = new RestrictingRMISocketFactory(jmxConfiguration.getHost().getHostName());
             LocateRegistry.createRegistry(jmxConfiguration.getHost().getPort(), restrictingRMISocketFactory, restrictingRMISocketFactory);
+            generateJMXPasswordFileIfNeed();
+
+            Map<String, String> environment = Optional.of(existJmxPasswordFile())
+                .filter(FunctionalUtils.identityPredicate())
+                .map(hasJmxPasswordFile -> ImmutableMap.of("jmx.remote.x.password.file", jmxPasswordFilePath,
+                    "jmx.remote.x.access.file", jmxAccessFilePath))
+                .orElse(ImmutableMap.of());
 
-            Map<String, ?> environment = ImmutableMap.of();
             jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(new JMXServiceURL(serviceURL),
                 environment,
                 ManagementFactory.getPlatformMBeanServer());
@@ -126,4 +154,43 @@ public class JMXServer implements Startable {
         }
     }
 
+    private void generateJMXPasswordFileIfNeed() {
+        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(jmxPasswordFilePath)) && Files.exists(Path.of(jmxAccessFilePath));
+    }
+
+    private void generateJMXPasswordFile() {
+        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);
+                setPermissionOwnerOnly(passwordFile);
+                LOGGER.info("Generated JMX password file: " + passwordFile.getPath());
+            } catch (IOException e) {
+                throw new RuntimeException("Error when creating JMX password file: " + passwordFile.getPath(), e);
+            }
+        }
+
+        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);
+                setPermissionOwnerOnly(accessFile);
+                LOGGER.info("Generated JMX access file: " + accessFile.getPath());
+            } catch (IOException 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 9bbebd3a28..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,6 +34,11 @@ 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_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)));
     public static final JmxConfiguration DISABLED = new JmxConfiguration(!ENABLED, Optional.empty());


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


[james-project] 03/07: JAMES-3881 Sample configuration

Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d2e722ab856fb90bcf97644fe2ea033f564c9888
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Feb 15 15:59:10 2023 +0100

    JAMES-3881 Sample configuration
    
    (cherry picked from commit 98d6e6550048612f2c861c4d61f6cec344a4ad87)
---
 server/apps/cassandra-app/sample-configuration/jvm.properties        | 3 +++
 server/apps/distributed-app/sample-configuration/jvm.properties      | 3 +++
 server/apps/distributed-pop3-app/sample-configuration/jvm.properties | 3 +++
 server/apps/jpa-app/sample-configuration/jvm.properties              | 3 +++
 server/apps/jpa-smtp-app/sample-configuration/jvm.properties         | 3 +++
 server/apps/memory-app/sample-configuration/jvm.properties           | 3 +++
 6 files changed, 18 insertions(+)

diff --git a/server/apps/cassandra-app/sample-configuration/jvm.properties b/server/apps/cassandra-app/sample-configuration/jvm.properties
index 04613ace32..1170fd1660 100644
--- a/server/apps/cassandra-app/sample-configuration/jvm.properties
+++ b/server/apps/cassandra-app/sample-configuration/jvm.properties
@@ -17,6 +17,9 @@
 # be copied to temporary files?
 #james.message.usememorycopy=false
 
+# Automatically generate a JMX password upon start. CLI is able to retrieve this password.
+james.jmx.credential.generation=true
+
 # Disable Remote Code Execution feature from JMX
 # CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
\ No newline at end of file
diff --git a/server/apps/distributed-app/sample-configuration/jvm.properties b/server/apps/distributed-app/sample-configuration/jvm.properties
index 04613ace32..1170fd1660 100644
--- a/server/apps/distributed-app/sample-configuration/jvm.properties
+++ b/server/apps/distributed-app/sample-configuration/jvm.properties
@@ -17,6 +17,9 @@
 # be copied to temporary files?
 #james.message.usememorycopy=false
 
+# Automatically generate a JMX password upon start. CLI is able to retrieve this password.
+james.jmx.credential.generation=true
+
 # Disable Remote Code Execution feature from JMX
 # CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
\ No newline at end of file
diff --git a/server/apps/distributed-pop3-app/sample-configuration/jvm.properties b/server/apps/distributed-pop3-app/sample-configuration/jvm.properties
index 65e94f8a5a..8d4c060e63 100644
--- a/server/apps/distributed-pop3-app/sample-configuration/jvm.properties
+++ b/server/apps/distributed-pop3-app/sample-configuration/jvm.properties
@@ -17,6 +17,9 @@
 # be copied to temporary files?
 #james.message.usememorycopy=false
 
+# Automatically generate a JMX password upon start. CLI is able to retrieve this password.
+james.jmx.credential.generation=true
+
 # Disable Remote Code Execution feature from JMX
 # CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
diff --git a/server/apps/jpa-app/sample-configuration/jvm.properties b/server/apps/jpa-app/sample-configuration/jvm.properties
index 04613ace32..1170fd1660 100644
--- a/server/apps/jpa-app/sample-configuration/jvm.properties
+++ b/server/apps/jpa-app/sample-configuration/jvm.properties
@@ -17,6 +17,9 @@
 # be copied to temporary files?
 #james.message.usememorycopy=false
 
+# Automatically generate a JMX password upon start. CLI is able to retrieve this password.
+james.jmx.credential.generation=true
+
 # Disable Remote Code Execution feature from JMX
 # CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
\ No newline at end of file
diff --git a/server/apps/jpa-smtp-app/sample-configuration/jvm.properties b/server/apps/jpa-smtp-app/sample-configuration/jvm.properties
index 65e94f8a5a..8d4c060e63 100644
--- a/server/apps/jpa-smtp-app/sample-configuration/jvm.properties
+++ b/server/apps/jpa-smtp-app/sample-configuration/jvm.properties
@@ -17,6 +17,9 @@
 # be copied to temporary files?
 #james.message.usememorycopy=false
 
+# Automatically generate a JMX password upon start. CLI is able to retrieve this password.
+james.jmx.credential.generation=true
+
 # Disable Remote Code Execution feature from JMX
 # CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
diff --git a/server/apps/memory-app/sample-configuration/jvm.properties b/server/apps/memory-app/sample-configuration/jvm.properties
index 65e94f8a5a..8d4c060e63 100644
--- a/server/apps/memory-app/sample-configuration/jvm.properties
+++ b/server/apps/memory-app/sample-configuration/jvm.properties
@@ -17,6 +17,9 @@
 # be copied to temporary files?
 #james.message.usememorycopy=false
 
+# Automatically generate a JMX password upon start. CLI is able to retrieve this password.
+james.jmx.credential.generation=true
+
 # Disable Remote Code Execution feature from JMX
 # CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false


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


[james-project] 06/07: JAMES-3881 Docker packagings should locate jvm.properties

Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c2e6376b1619fe58e91da8de2b23f701d23287f4
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Feb 15 22:06:37 2023 +0700

    JAMES-3881 Docker packagings should locate jvm.properties
    
    (cherry picked from commit 48d2266ac342226bf6ae362e19e62b9065b8b192)
---
 server/apps/jpa-app/pom.xml      | 1 +
 server/apps/jpa-smtp-app/pom.xml | 1 +
 server/apps/memory-app/pom.xml   | 2 +-
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/server/apps/jpa-app/pom.xml b/server/apps/jpa-app/pom.xml
index 18f1aa609b..810225c818 100644
--- a/server/apps/jpa-app/pom.xml
+++ b/server/apps/jpa-app/pom.xml
@@ -302,6 +302,7 @@
                             <jvmFlag>-Dworking.directory=/root/</jvmFlag>
                             <!-- Prevents Logjam (CVE-2015-4000) -->
                             <jvmFlag>-Djdk.tls.ephemeralDHKeySize=2048</jvmFlag>
+                            <jvmFlag>-Dextra.props=/root/conf/jvm.properties</jvmFlag>
                         </jvmFlags>
                         <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
                         <volumes>
diff --git a/server/apps/jpa-smtp-app/pom.xml b/server/apps/jpa-smtp-app/pom.xml
index d5ac47663f..31c31f540b 100644
--- a/server/apps/jpa-smtp-app/pom.xml
+++ b/server/apps/jpa-smtp-app/pom.xml
@@ -269,6 +269,7 @@
                             <jvmFlag>-Dworking.directory=/root/</jvmFlag>
                             <!-- Prevents Logjam (CVE-2015-4000) -->
                             <jvmFlag>-Djdk.tls.ephemeralDHKeySize=2048</jvmFlag>
+                            <jvmFlag>-Dextra.props=/root/conf/jvm.properties</jvmFlag>
                         </jvmFlags>
                         <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
                         <volumes>
diff --git a/server/apps/memory-app/pom.xml b/server/apps/memory-app/pom.xml
index 1736218be8..836534694e 100644
--- a/server/apps/memory-app/pom.xml
+++ b/server/apps/memory-app/pom.xml
@@ -297,7 +297,7 @@
                             <jvmFlag>-Dworking.directory=/root/</jvmFlag>
                             <!-- Prevents Logjam (CVE-2015-4000) -->
                             <jvmFlag>-Djdk.tls.ephemeralDHKeySize=2048</jvmFlag>
-                            <jvmFlag>-Djames.jmx.credential.generation=true</jvmFlag>
+                            <jvmFlag>-Dextra.props=/root/conf/jvm.properties</jvmFlag>
                         </jvmFlags>
                         <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
                         <volumes>


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


[james-project] 05/07: JAMES-3881 Update documentation accordingly

Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 83d795a6e0ad5637469ce63a4e14851a01213ec0
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Feb 15 22:00:50 2023 +0700

    JAMES-3881 Update documentation accordingly
    
    (cherry picked from commit 05bf241e4362cd55437900d1afeab9f5300c315f)
---
 .../docs/modules/ROOT/pages/configure/jmx.adoc     |  2 +-
 src/site/xdoc/server/config-system.xml             | 28 +++++++++++++++++++++-
 2 files changed, 28 insertions(+), 2 deletions(-)

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 996b8a6ebd..1b388427f0 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
@@ -44,7 +44,7 @@ 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.
+Then the default username is `james-admin` and a random password. This option defaults to true.
 
 === James-cli
 
diff --git a/src/site/xdoc/server/config-system.xml b/src/site/xdoc/server/config-system.xml
index 93e520d9fc..09211b265d 100644
--- a/src/site/xdoc/server/config-system.xml
+++ b/src/site/xdoc/server/config-system.xml
@@ -125,9 +125,35 @@
                     <dd>The port number the MBean Server will bind/listen to.</dd>
                 </dl>
 
-                <p>To access from a remote location, it has been reported that -Dcom.sun.management.jmxremote.ssl=false is
+                <p>To access from a remote location, it has been reported that <code>-Dcom.sun.management.jmxremote.ssl=false</code> is
                     needed in the startup script.</p>
 
+                <b>JMX Security</b>
+
+                <p>Guice only</p>
+
+                <p>In order to set up JMX authentication, we need to put <code>jmxremote.password</code> and <code>jmxremote.access</code> file
+                    to <code>/conf</code> directory.</p>
+
+                <p><code>jmxremote.password</code>: define the username and password, that will be used by the client (here is james-cli)</p>
+
+                <p>File's content example:</p>
+                <pre><code>
+                james-admin pass1
+                </code></pre>
+
+                <p><code>jmxremote.access</code>: define the pair of username and access permission</p>
+
+                <p>File's content example:</p>
+                <pre><code>
+                james-admin readWrite
+                </code></pre>
+
+                <p>When James runs with option <code>-Djames.jmx.credential.generation=true</code>, James will automatically
+                    generate <code>jmxremote.password</code> if the file does not exist.
+                    Then the default username is <code>james-admin</code> and a random password. This option defaults to true.</p>
+
+
             </subsection>
 
             <subsection name="sqlResources.xml">


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


[james-project] 07/07: JAMES-3881 WARN if no JMX authentication is setup

Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d3d738838634e1ccb6699e1af64f7e8c63b0bb84
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 17 10:21:12 2023 +0700

    JAMES-3881 WARN if no JMX authentication is setup
    
    (cherry picked from commit 3718ee5a79e1986156c8c1e13ba201a02bb8f232)
---
 .../java/org/apache/james/app/spring/JamesAppSpringMain.java     | 9 +++++++--
 .../src/main/java/org/apache/james/modules/server/JMXServer.java | 4 ++++
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/server/apps/spring-app/src/main/java/org/apache/james/app/spring/JamesAppSpringMain.java b/server/apps/spring-app/src/main/java/org/apache/james/app/spring/JamesAppSpringMain.java
index 7366395b6c..e88001f8ac 100644
--- a/server/apps/spring-app/src/main/java/org/apache/james/app/spring/JamesAppSpringMain.java
+++ b/server/apps/spring-app/src/main/java/org/apache/james/app/spring/JamesAppSpringMain.java
@@ -40,12 +40,17 @@ public class JamesAppSpringMain implements Daemon {
     private static final ObjectName ALL_OBJECT_NAME = null;
     private static final QueryExp ALL_QUERY_EXP = null;
 
-    private static final Logger log = LoggerFactory.getLogger(JamesAppSpringMain.class.getName());
+    private static final Logger LOGGER = LoggerFactory.getLogger(JamesAppSpringMain.class.getName());
     private JamesServerApplicationContext context;
 
     public static void main(String[] args) throws Exception {
         unregisterLog4JMBeans();
 
+        if (System.getProperty("com.sun.management.jmxremote.password.file") == null) {
+            LOGGER.warn("No authentication setted up for the JMX component. This expose you to local privilege escalation attacks risk. " +
+                "This can be done via the 'com.sun.management.jmxremote.password.file' system property.");
+        }
+
         long start = Calendar.getInstance().getTimeInMillis();
 
         JamesAppSpringMain main = new JamesAppSpringMain();
@@ -53,7 +58,7 @@ public class JamesAppSpringMain implements Daemon {
 
         long end = Calendar.getInstance().getTimeInMillis();
 
-        log.info("Apache James Server is successfully started in {} milliseconds.", end - start);
+        LOGGER.info("Apache James Server is successfully started in {} milliseconds.", end - start);
 
     }
 
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 6aa801314a..901926dad2 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
@@ -121,6 +121,10 @@ public class JMXServer implements Startable {
             restrictingRMISocketFactory = new RestrictingRMISocketFactory(jmxConfiguration.getHost().getHostName());
             LocateRegistry.createRegistry(jmxConfiguration.getHost().getPort(), restrictingRMISocketFactory, restrictingRMISocketFactory);
             generateJMXPasswordFileIfNeed();
+            
+            if (!existJmxPasswordFile()) {
+                LOGGER.warn("No authentication setted up for the JMX component. This expose you to local privilege escalation attacks risk.");
+            }
 
             Map<String, String> environment = Optional.of(existJmxPasswordFile())
                 .filter(FunctionalUtils.identityPredicate())


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