diff --git a/artemis-console/src/main/resources/META-INF/jolokia/detectors b/artemis-console/src/main/resources/META-INF/jolokia/detectors new file mode 100644 index 00000000000..5f05793dfe2 --- /dev/null +++ b/artemis-console/src/main/resources/META-INF/jolokia/detectors @@ -0,0 +1,26 @@ +# 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. + +!org.jolokia.server.detector.misc.ActiveMQDetector +!org.jolokia.server.detector.jee.GeronimoDetector +!org.jolokia.server.detector.jee.JBossDetector +!org.jolokia.server.detector.jee.TomcatDetector +!org.jolokia.server.detector.jee.JettyDetector +!org.jolokia.server.detector.jee.GlassfishDetector +!org.jolokia.server.detector.jee.WeblogicDetector +!org.jolokia.server.detector.jee.WebsphereDetector +!org.jolokia.server.detector.misc.LightstreamerDetector \ No newline at end of file diff --git a/tests/smoke-tests/src/main/resources/servers/jmx-rbac-broker-security/log4j2.properties b/tests/smoke-tests/src/main/resources/servers/jmx-rbac-broker-security/log4j2.properties new file mode 100644 index 00000000000..cb2be5c4c11 --- /dev/null +++ b/tests/smoke-tests/src/main/resources/servers/jmx-rbac-broker-security/log4j2.properties @@ -0,0 +1,101 @@ +# 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. + +# Log4J 2 configuration + +# Monitor config file every X seconds for updates +monitorInterval = 5 + +rootLogger.level = INFO +rootLogger.appenderRef.console.ref = console +rootLogger.appenderRef.log_file.ref = log_file + +logger.activemq.name=org.apache.activemq +logger.activemq.level=INFO + +logger.artemis_server.name=org.apache.activemq.artemis.core.server +logger.artemis_server.level=INFO + +logger.artemis_journal.name=org.apache.activemq.artemis.journal +logger.artemis_journal.level=INFO + +logger.artemis_utils.name=org.apache.activemq.artemis.utils +logger.artemis_utils.level=INFO + +# CriticalAnalyzer: If you have issues with the CriticalAnalyzer, setting this to TRACE would give +# you extra troubleshooting info, but do not use TRACE regularly as it would incur extra CPU usage. +logger.critical_analyzer.name=org.apache.activemq.artemis.utils.critical +logger.critical_analyzer.level=INFO + +# Audit loggers: to enable change levels from OFF to INFO +logger.audit_base.name = org.apache.activemq.audit.base +logger.audit_base.level = INFO +logger.audit_base.appenderRef.audit_log_file.ref = audit_log_file +logger.audit_base.additivity = false + +logger.audit_resource.name = org.apache.activemq.audit.resource +logger.audit_resource.level = INFO +logger.audit_resource.appenderRef.audit_log_file.ref = audit_log_file +logger.audit_resource.additivity = false + +logger.audit_message.name = org.apache.activemq.audit.message +logger.audit_message.level = INFO +logger.audit_message.appenderRef.audit_log_file.ref = audit_log_file +logger.audit_message.additivity = false + +# Jetty logger levels +logger.jetty.name=org.eclipse.jetty +logger.jetty.level=INFO + +# web console authenticator too verbose for impatient client +logger.authentication_filter.name=io.hawt.web.auth.AuthenticationFilter +logger.authentication_filter.level=ERROR + +# Quorum related logger levels +logger.curator.name=org.apache.curator +logger.curator.level=WARN +logger.zookeeper.name=org.apache.zookeeper +logger.zookeeper.level=ERROR + + +# Console appender +appender.console.type=Console +appender.console.name=console +appender.console.layout.type=PatternLayout +appender.console.layout.pattern=%d %-5level [%logger] %msg%n + +# Log file appender +appender.log_file.type = RollingFile +appender.log_file.name = log_file +appender.log_file.fileName = ${sys:artemis.instance}/log/artemis.log +appender.log_file.filePattern = ${sys:artemis.instance}/log/artemis.log.%d{yyyy-MM-dd} +appender.log_file.layout.type = PatternLayout +appender.log_file.layout.pattern = %d %-5level [%logger] %msg%n +appender.log_file.policies.type = Policies +appender.log_file.policies.cron.type = CronTriggeringPolicy +appender.log_file.policies.cron.schedule = 0 0 0 * * ? +appender.log_file.policies.cron.evaluateOnStartup = true + +# Audit log file appender +appender.audit_log_file.type = RollingFile +appender.audit_log_file.name = audit_log_file +appender.audit_log_file.fileName = ${sys:artemis.instance}/log/audit.log +appender.audit_log_file.filePattern = ${sys:artemis.instance}/log/audit.log.%d{yyyy-MM-dd} +appender.audit_log_file.layout.type = PatternLayout +appender.audit_log_file.layout.pattern = %d [AUDIT](%t) %msg%n +appender.audit_log_file.policies.type = Policies +appender.audit_log_file.policies.cron.type = CronTriggeringPolicy +appender.audit_log_file.policies.cron.schedule = 0 0 0 * * ? +appender.audit_log_file.policies.cron.evaluateOnStartup = true diff --git a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/jmxrbac/JmxRBACBrokerSecurityTest.java b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/jmxrbac/JmxRBACBrokerSecurityTest.java index 69c441f06a9..083142355b7 100644 --- a/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/jmxrbac/JmxRBACBrokerSecurityTest.java +++ b/tests/smoke-tests/src/test/java/org/apache/activemq/artemis/tests/smoke/jmxrbac/JmxRBACBrokerSecurityTest.java @@ -17,6 +17,8 @@ package org.apache.activemq.artemis.tests.smoke.jmxrbac; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import javax.management.MBeanServerConnection; @@ -26,18 +28,35 @@ import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; import java.util.Collections; +import java.util.function.Consumer; +import java.util.stream.Stream; import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; +import org.apache.activemq.artemis.api.core.JsonUtil; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl; import org.apache.activemq.artemis.api.core.management.AddressControl; import org.apache.activemq.artemis.api.core.management.ObjectNameBuilder; +import org.apache.activemq.artemis.json.JsonObject; import org.apache.activemq.artemis.tests.smoke.common.SmokeTestBase; import org.apache.activemq.artemis.util.ServerUtil; import org.apache.activemq.artemis.cli.commands.helper.HelperCreate; +import org.apache.activemq.artemis.utils.JsonLoader; +import org.apache.activemq.artemis.utils.VersionLoader; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -47,6 +66,7 @@ public class JmxRBACBrokerSecurityTest extends SmokeTestBase { private static final String JMX_SERVER_HOSTNAME = "localhost"; + private static final String JOLOKIA_URL = "http://localhost:8161/console/jolokia"; private static final int JMX_SERVER_PORT = 10099; public static final String BROKER_NAME = "0.0.0.0"; @@ -66,7 +86,7 @@ public static void createServers() throws Exception { { HelperCreate cliCreateServer = helperCreate(); - cliCreateServer.setRole("amq").setUser("admin").setPassword("admin").setAllowAnonymous(false).setNoWeb(false).setArtemisInstance(server0Location). + cliCreateServer.setRole("amq").setUser(SERVER_ADMIN).setPassword(SERVER_ADMIN).setAllowAnonymous(false).setNoWeb(false).setArtemisInstance(server0Location). setConfiguration("./src/main/resources/servers/jmx-rbac-broker-security").setArgs("--java-options", "-Djava.rmi.server.hostname=localhost -Djavax.management.builder.initial=org.apache.activemq.artemis.core.server.management.ArtemisRbacMBeanServerBuilder"); cliCreateServer.createServer(); } @@ -223,4 +243,102 @@ public void testSendMessageWithoutUserAndPassword() throws Exception { jmxConnector.close(); } } + + @Test + public void testJolokiaWithServerAdmin() throws Exception { + // Read an attribute via jolokia (view permission) + String readRequest = JsonLoader.createObjectBuilder() + .add("type", "read") + .add("mbean", "org.apache.activemq.artemis:broker=\"" + BROKER_NAME + "\"") + .add("attribute", "Version") + .build() + .toString(); + + makeJolokiaRequest(JOLOKIA_URL, readRequest, SERVER_ADMIN, SERVER_ADMIN, response -> { + assertNotNull(response); + assertEquals(200, response.getStatusLine().getStatusCode()); + + String responseBody = getResponseBody(response); + assertNotNull(responseBody); + + JsonObject jsonResponse = JsonUtil.readJsonObject(responseBody); + assertTrue(jsonResponse.containsKey("status")); + assertEquals(200, jsonResponse.getInt("status")); + assertTrue(jsonResponse.containsKey("value")); + assertEquals(VersionLoader.getVersion().getFullVersion(), jsonResponse.getString("value")); + }); + + // Query MBeans via jolokia + String queryRequest = JsonLoader.createObjectBuilder() + .add("type", "search") + .add("mbean", "org.apache.activemq.artemis:*") + .build() + .toString(); + + makeJolokiaRequest(JOLOKIA_URL, queryRequest, SERVER_ADMIN, SERVER_ADMIN, response -> { + assertNotNull(response); + assertEquals(200, response.getStatusLine().getStatusCode()); + + String responseBody = getResponseBody(response); + assertNotNull(responseBody); + }); + + } + + @Test + public void testJolokiaDisabledDetectors() throws Exception { + // Read an attribute via jolokia (view permission) + String readRequest = JsonLoader.createObjectBuilder() + .add("type", "read") + .add("mbean", "org.apache.activemq.artemis:broker=\"" + BROKER_NAME + "\"") + .add("attribute", "Version") + .build() + .toString(); + + makeJolokiaRequest(JOLOKIA_URL, readRequest, SERVER_ADMIN, SERVER_ADMIN, response -> { + assertNotNull(response); + assertEquals(200, response.getStatusLine().getStatusCode()); + }); + + // Verify artemis log does not contain AMQ229032 errors + try (Stream lines = Files.lines(Path.of("target/" + SERVER_NAME_0 + "/log/artemis.log"))) { + assertTrue(lines.noneMatch(line -> line.contains("ActiveMQDetector") || line.contains("AMQ229032"))); + } + + // Verify audit log does not contain AMQ229032 errors + try (Stream lines = Files.lines(Path.of("target/" + SERVER_NAME_0 + "/log/audit.log"))) { + assertTrue(lines.noneMatch(line -> line.contains("ActiveMQDetector") || line.contains("AMQ229032"))); + } + } + + private String getResponseBody(HttpResponse response) { + String responseBody; + try { + responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + return responseBody; + } + + private void makeJolokiaRequest(String url, String jsonBody, String username, String password, Consumer responseConsumer) throws IOException { + try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { + HttpPost httpPost = new HttpPost(url); + + // Set authentication header + String auth = username + ":" + password; + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8)); + httpPost.setHeader("Authorization", "Basic " + encodedAuth); + + // Set required headers for jolokia + httpPost.setHeader("Content-Type", "application/json"); + httpPost.setHeader("Origin", "http://localhost"); + + // Set request body + StringEntity entity = new StringEntity(jsonBody, StandardCharsets.UTF_8); + httpPost.setEntity(entity); + + responseConsumer.accept(httpClient.execute(httpPost)); + } + } }