ARTEMIS-1463 - add role based authentication to the JMX objects

This is done by creating a guard and using JAAS to check for access to mbean objects and their methods.

NB this also implements https://issues.apache.org/jira/browse/ARTEMIS-534

https://issues.apache.org/jira/browse/ARTEMIS-1463
This commit is contained in:
Andy Taylor 2017-10-12 13:53:19 +01:00
parent d0f3f67193
commit 62a2b14dd0
37 changed files with 2492 additions and 33 deletions

View File

@ -27,9 +27,11 @@ import io.airlift.airline.model.CommandGroupMetadata;
import io.airlift.airline.model.CommandMetadata;
import io.airlift.airline.model.GlobalMetadata;
import org.apache.activemq.artemis.cli.factory.BrokerFactory;
import org.apache.activemq.artemis.cli.factory.jmx.ManagementFactory;
import org.apache.activemq.artemis.core.config.FileDeploymentManager;
import org.apache.activemq.artemis.core.config.impl.FileConfiguration;
import org.apache.activemq.artemis.dto.BrokerDTO;
import org.apache.activemq.artemis.dto.ManagementContextDTO;
import org.apache.activemq.artemis.integration.bootstrap.ActiveMQBootstrapLogger;
import org.apache.activemq.artemis.jms.server.config.impl.FileJMSConfiguration;
@ -116,6 +118,11 @@ public abstract class Configurable extends ActionAbstract {
return brokerDTO;
}
protected ManagementContextDTO getManagementDTO() throws Exception {
String configuration = getManagementConfiguration();
return ManagementFactory.createJmxAclConfiguration(configuration, getBrokerHome(), getBrokerInstance(), getBrokerURIInstance());
}
protected String getConfiguration() {
if (configuration == null) {
File xmlFile = new File(new File(new File(getBrokerInstance()), "etc"), "bootstrap.xml");
@ -130,4 +137,14 @@ public abstract class Configurable extends ActionAbstract {
return configuration;
}
protected String getManagementConfiguration() {
File xmlFile = new File(new File(new File(getBrokerInstance()), "etc"), "management.xml");
String configuration = "xml:" + xmlFile.toURI().toString().substring("file:".length());
// To support Windows paths as explained above.
configuration = configuration.replace("\\", "/");
return configuration;
}
}

View File

@ -82,6 +82,7 @@ public class Create extends InputAbstract {
public static final String ETC_ARTEMIS_PROFILE = "etc/artemis.profile";
public static final String ETC_LOGGING_PROPERTIES = "etc/logging.properties";
public static final String ETC_BOOTSTRAP_XML = "etc/bootstrap.xml";
public static final String ETC_MANAGEMENT_XML = "etc/management.xml";
public static final String ETC_BROKER_XML = "etc/broker.xml";
public static final String ETC_ARTEMIS_ROLES_PROPERTIES = "etc/artemis-roles.properties";
@ -689,6 +690,7 @@ public class Create extends InputAbstract {
// we want this variable to remain unchanged so that it will use the value set in the profile
filters.remove("${artemis.instance}");
write(ETC_BOOTSTRAP_XML, filters, false);
write(ETC_MANAGEMENT_XML, filters, false);
write(ETC_JOLOKIA_ACCESS_XML, filters, false);
context.out.println("");

View File

@ -25,10 +25,13 @@ import io.airlift.airline.Option;
import org.apache.activemq.artemis.cli.Artemis;
import org.apache.activemq.artemis.cli.commands.tools.LockAbstract;
import org.apache.activemq.artemis.cli.factory.BrokerFactory;
import org.apache.activemq.artemis.cli.factory.jmx.ManagementFactory;
import org.apache.activemq.artemis.cli.factory.security.SecurityManagerFactory;
import org.apache.activemq.artemis.components.ExternalComponent;
import org.apache.activemq.artemis.core.server.management.ManagementContext;
import org.apache.activemq.artemis.dto.BrokerDTO;
import org.apache.activemq.artemis.dto.ComponentDTO;
import org.apache.activemq.artemis.dto.ManagementContextDTO;
import org.apache.activemq.artemis.integration.Broker;
import org.apache.activemq.artemis.integration.bootstrap.ActiveMQBootstrapLogger;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
@ -44,6 +47,8 @@ public class Run extends LockAbstract {
public static final ReusableLatch latchRunning = new ReusableLatch(0);
private ManagementContext managementContext;
/**
* This will disable the System.exit at the end of the server.stop, as that means there are other things
* happening on the same VM.
@ -60,6 +65,9 @@ public class Run extends LockAbstract {
public Object execute(ActionContext context) throws Exception {
super.execute(context);
ManagementContextDTO managementDTO = getManagementDTO();
managementContext = ManagementFactory.create(managementDTO);
Artemis.printBanner();
BrokerDTO broker = getBrokerDTO();
@ -70,6 +78,7 @@ public class Run extends LockAbstract {
server = BrokerFactory.createServer(broker.server, security);
managementContext.start();
server.start();
if (broker.web != null) {
@ -121,11 +130,7 @@ public class Run extends LockAbstract {
}
if (file.exists()) {
try {
try {
server.stop(true);
} catch (Exception e) {
e.printStackTrace();
}
stop();
timer.cancel();
} finally {
System.out.println("Server stopped!");
@ -142,13 +147,20 @@ public class Run extends LockAbstract {
Runtime.getRuntime().addShutdownHook(new Thread("shutdown-hook") {
@Override
public void run() {
try {
server.stop(true);
} catch (Exception e) {
e.printStackTrace();
}
Run.this.stop();
}
});
}
protected void stop() {
try {
server.stop(true);
if (managementContext != null) {
managementContext.stop();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,25 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.cli.factory.jmx;
import org.apache.activemq.artemis.dto.ManagementContextDTO;
import java.net.URI;
public interface JmxAclHandler {
ManagementContextDTO createJmxAcl(URI configURI, String artemisHome, String artemisInstance, URI artemisURIInstance) throws Exception;
}

View File

@ -0,0 +1,134 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.cli.factory.jmx;
import org.apache.activemq.artemis.cli.ConfigurationException;
import org.apache.activemq.artemis.core.config.JMXConnectorConfiguration;
import org.apache.activemq.artemis.core.server.management.ManagementContext;
import org.apache.activemq.artemis.dto.AccessDTO;
import org.apache.activemq.artemis.dto.AuthorisationDTO;
import org.apache.activemq.artemis.dto.EntryDTO;
import org.apache.activemq.artemis.dto.JMXConnectorDTO;
import org.apache.activemq.artemis.dto.ManagementContextDTO;
import org.apache.activemq.artemis.dto.MatchDTO;
import org.apache.activemq.artemis.core.server.management.JMXAccessControlList;
import org.apache.activemq.artemis.utils.FactoryFinder;
import java.io.IOException;
import java.net.URI;
import java.util.List;
public class ManagementFactory {
private static ManagementContextDTO createJmxAclConfiguration(URI configURI,
String artemisHome,
String artemisInstance,
URI artemisURIInstance) throws Exception {
if (configURI.getScheme() == null) {
throw new ConfigurationException("Invalid configuration URI, no scheme specified: " + configURI);
}
JmxAclHandler factory = null;
try {
FactoryFinder finder = new FactoryFinder("META-INF/services/org/apache/activemq/artemis/broker/jmx/");
factory = (JmxAclHandler) finder.newInstance(configURI.getScheme());
} catch (IOException ioe) {
throw new ConfigurationException("Invalid configuration URI, can't find configuration scheme: " + configURI.getScheme());
}
return factory.createJmxAcl(configURI, artemisHome, artemisInstance, artemisURIInstance);
}
public static ManagementContextDTO createJmxAclConfiguration(String configuration,
String artemisHome,
String artemisInstance,
URI artemisURIInstance) throws Exception {
return createJmxAclConfiguration(new URI(configuration), artemisHome, artemisInstance, artemisURIInstance);
}
public static ManagementContext create(ManagementContextDTO config) {
ManagementContext context = new ManagementContext();
if (config.getAuthorisation() != null) {
AuthorisationDTO authorisation = config.getAuthorisation();
JMXAccessControlList accessControlList = new JMXAccessControlList();
List<EntryDTO> entries = authorisation.getWhiteList().getEntries();
for (EntryDTO entry : entries) {
accessControlList.addToWhiteList(entry.domain, entry.key);
}
List<AccessDTO> accessList = authorisation.getDefaultAccess().getAccess();
for (AccessDTO access : accessList) {
String[] split = access.roles.split(",");
accessControlList.addToDefaultAccess(access.method, split);
}
List<MatchDTO> matches = authorisation.getRoleAccess().getMatch();
for (MatchDTO match : matches) {
List<AccessDTO> accesses = match.getAccess();
for (AccessDTO access : accesses) {
String[] split = access.roles.split(",");
accessControlList.addToRoleAccess(match.getDomain(), match.getKey(), access.method, split);
}
}
context.setAccessControlList(accessControlList);
}
if (config.getJmxConnector() != null) {
JMXConnectorDTO jmxConnector = config.getJmxConnector();
JMXConnectorConfiguration jmxConnectorConfiguration = new JMXConnectorConfiguration();
jmxConnectorConfiguration.setConnectorPort(jmxConnector.getConnectorPort());
if (jmxConnector.getConnectorHost() != null) {
jmxConnectorConfiguration.setConnectorHost(jmxConnector.getConnectorHost());
}
if (jmxConnector.getJmxRealm() != null) {
jmxConnectorConfiguration.setJmxRealm(jmxConnector.getJmxRealm());
}
if (jmxConnector.getAuthenticatorType() != null) {
jmxConnectorConfiguration.setAuthenticatorType(jmxConnector.getAuthenticatorType());
}
if (jmxConnector.getKeyStorePath() != null) {
jmxConnectorConfiguration.setKeyStorePath(jmxConnector.getKeyStorePath());
}
if (jmxConnector.getKeyStoreProvider() != null) {
jmxConnectorConfiguration.setKeyStoreProvider(jmxConnector.getKeyStoreProvider());
}
if (jmxConnector.getKeyStorePassword() != null) {
jmxConnectorConfiguration.setKeyStorePassword(jmxConnector.getKeyStorePassword());
}
if (jmxConnector.getTrustStorePath() != null) {
jmxConnectorConfiguration.setTrustStorePath(jmxConnector.getTrustStorePath());
}
if (jmxConnector.getTrustStoreProvider() != null) {
jmxConnectorConfiguration.setTrustStoreProvider(jmxConnector.getTrustStoreProvider());
}
if (jmxConnector.getTrustStorePassword() != null) {
jmxConnectorConfiguration.setTrustStorePassword(jmxConnector.getTrustStorePassword());
}
if (jmxConnector.getObjectName() != null) {
jmxConnectorConfiguration.setObjectName(jmxConnector.getObjectName());
}
if (jmxConnector.isSecured() != null) {
jmxConnectorConfiguration.setSecured(jmxConnector.isSecured());
}
context.setJmxConnectorConfiguration(jmxConnectorConfiguration);
}
return context;
}
}

View File

@ -0,0 +1,36 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.cli.factory.jmx;
import org.apache.activemq.artemis.cli.ConfigurationException;
import org.apache.activemq.artemis.dto.ManagementContextDTO;
import org.apache.activemq.artemis.dto.XmlUtil;
import java.io.File;
import java.net.URI;
public class XmlJmxAclHandler implements JmxAclHandler {
@Override
public ManagementContextDTO createJmxAcl(URI configURI, String artemisHome, String artemisInstance, URI artemisURIInstance) throws Exception {
File file = new File(configURI.getSchemeSpecificPart());
if (!file.exists()) {
throw new ConfigurationException("Invalid configuration URI, can't find file: " + file.getName());
}
return XmlUtil.decode(ManagementContextDTO.class, file, artemisHome, artemisInstance, artemisURIInstance);
}
}

View File

@ -0,0 +1,17 @@
## ---------------------------------------------------------------------------
## 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.
## ---------------------------------------------------------------------------
class=org.apache.activemq.artemis.cli.factory.jmx.XmlJmxAclHandler

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ 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.
-->
<management-context xmlns="http://activemq.org/schema">
<!--<connector connector-port="1099"/>-->
<authorisation>
<whitelist>
<entry domain="hawtio"/>
</whitelist>
<default-access>
<access method="list*" roles="view,update,amq"/>
<access method="get*" roles="view,update,amq"/>
<access method="is*" roles="view,update,amq"/>
<access method="set*" roles="update,amq"/>
<access method="*" roles="amq"/>
</default-access>
<role-access>
<match domain="org.apache.activemq.apache">
<access method="list*" roles="view,update,amq"/>
<access method="get*" roles="view,update,amq"/>
<access method="is*" roles="view,update,amq"/>
<access method="set*" roles="update,amq"/>
<access method="*" roles="amq"/>
</match>
<!--example of how to configure a specific object-->
<!--<match domain="org.apache.activemq.apache" key="subcomponent=queues">
<access method="list*" roles="view,update,amq"/>
<access method="get*" roles="view,update,amq"/>
<access method="is*" roles="view,update,amq"/>
<access method="set*" roles="update,amq"/>
<access method="*" roles="amq"/>
</match>-->
</role-access>
</authorisation>
</management-context>

View File

@ -39,6 +39,7 @@ public class StreamClassPathTest {
openStream(Create.ETC_ARTEMIS_PROFILE);
openStream(Create.ETC_LOGGING_PROPERTIES);
openStream(Create.ETC_BOOTSTRAP_XML);
openStream(Create.ETC_MANAGEMENT_XML);
openStream(Create.ETC_BROKER_XML);
openStream(Create.ETC_ARTEMIS_ROLES_PROPERTIES);
openStream(Create.ETC_ARTEMIS_USERS_PROPERTIES);

View File

@ -0,0 +1,34 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "access")
@XmlAccessorType(XmlAccessType.FIELD)
public class AccessDTO {
@XmlAttribute
public String method;
@XmlAttribute
public String roles;
}

View File

@ -0,0 +1,49 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "authorisation")
@XmlAccessorType(XmlAccessType.FIELD)
public class AuthorisationDTO {
@XmlElementRef
WhiteListDTO whitelist;
@XmlElementRef
DefaultAccessDTO defaultAccess;
@XmlElementRef
RoleAccessDTO roleAccess;
public WhiteListDTO getWhiteList() {
return whitelist;
}
public DefaultAccessDTO getDefaultAccess() {
return defaultAccess;
}
public RoleAccessDTO getRoleAccess() {
return roleAccess;
}
}

View File

@ -0,0 +1,36 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@XmlRootElement(name = "default-access")
@XmlAccessorType(XmlAccessType.FIELD)
public class DefaultAccessDTO {
@XmlElementRef
List<AccessDTO> access;
public List<AccessDTO> getAccess() {
return access;
}
}

View File

@ -0,0 +1,34 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "entry")
@XmlAccessorType(XmlAccessType.FIELD)
public class EntryDTO {
@XmlAttribute
public String domain;
@XmlAttribute
public String key;
}

View File

@ -0,0 +1,112 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "connector")
@XmlAccessorType(XmlAccessType.FIELD)
public class JMXConnectorDTO {
@XmlAttribute (name = "connector-host")
String connectorHost;
@XmlAttribute (name = "connector-port", required = true)
Integer connectorPort;
@XmlAttribute (name = "jmx-realm")
String jmxRealm;
@XmlAttribute (name = "object-name")
String objectName;
@XmlAttribute (name = "authenticator-type")
String authenticatorType;
@XmlAttribute (name = "secured")
Boolean secured;
@XmlAttribute (name = "key-store-provider")
String keyStoreProvider;
@XmlAttribute (name = "key-store-path")
String keyStorePath;
@XmlAttribute (name = "key-store-password")
String keyStorePassword;
@XmlAttribute (name = "trust-store-provider")
String trustStoreProvider;
@XmlAttribute (name = "trust-store-path")
String trustStorePath;
@XmlAttribute (name = "trust-store-password")
String trustStorePassword;
public String getConnectorHost() {
return connectorHost;
}
public int getConnectorPort() {
return connectorPort;
}
public String getJmxRealm() {
return jmxRealm;
}
public String getObjectName() {
return objectName;
}
public String getAuthenticatorType() {
return authenticatorType;
}
public Boolean isSecured() {
return secured;
}
public String getKeyStoreProvider() {
return keyStoreProvider;
}
public String getKeyStorePath() {
return keyStorePath;
}
public String getKeyStorePassword() {
return keyStorePassword;
}
public String getTrustStoreProvider() {
return trustStoreProvider;
}
public String getTrustStorePath() {
return trustStorePath;
}
public String getTrustStorePassword() {
return trustStorePassword;
}
}

View File

@ -0,0 +1,41 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "management-context")
@XmlAccessorType(XmlAccessType.FIELD)
public class ManagementContextDTO {
@XmlElementRef (required = false)
JMXConnectorDTO jmxConnector;
@XmlElementRef (required = false)
AuthorisationDTO authorisation;
public JMXConnectorDTO getJmxConnector() {
return jmxConnector;
}
public AuthorisationDTO getAuthorisation() {
return authorisation;
}
}

View File

@ -0,0 +1,53 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@XmlRootElement(name = "match")
@XmlAccessorType(XmlAccessType.FIELD)
public class MatchDTO {
@XmlAttribute
public String domain;
@XmlAttribute
public String key;
@XmlElementRef
List<AccessDTO> access;
public String getDomain() {
return domain;
}
public String getKey() {
return key;
}
public List<AccessDTO> getAccess() {
return access;
}
}

View File

@ -0,0 +1,36 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@XmlRootElement(name = "role-access")
@XmlAccessorType(XmlAccessType.FIELD)
public class RoleAccessDTO {
@XmlElementRef
List<MatchDTO> match;
public List<MatchDTO> getMatch() {
return match;
}
}

View File

@ -0,0 +1,36 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@XmlRootElement(name = "whitelist")
@XmlAccessorType(XmlAccessType.FIELD)
public class WhiteListDTO {
@XmlElementRef
List<EntryDTO> entry;
public List<EntryDTO> getEntries() {
return entry;
}
}

View File

@ -17,4 +17,5 @@
BrokerDTO
SecurityDTO
JaasSecurityDTO
ManagementContextDTO

View File

@ -0,0 +1,149 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.core.config;
public class JMXConnectorConfiguration {
private int rmiRegistryPort;
private String connectorHost = "localhost";
private int connectorPort = 1099;
private String connectorPath = "/jmxrmi";
private String jmxRealm = "activemq";
private String objectName = "connector:name=rmi";
private String authenticatorType = "password";
private boolean secured = false;
private String keyStoreProvider;
private String keyStorePath;
private String keyStorePassword;
private String trustStoreProvider;
private String trustStorePath;
private String trustStorePassword;
public int getRmiRegistryPort() {
return rmiRegistryPort;
}
public void setRmiRegistryPort(int rmiRegistryPort) {
this.rmiRegistryPort = rmiRegistryPort;
}
public String getConnectorHost() {
return connectorHost;
}
public void setConnectorHost(String connectorHost) {
this.connectorHost = connectorHost;
}
public int getConnectorPort() {
return connectorPort;
}
public void setConnectorPort(int connectorPort) {
this.connectorPort = connectorPort;
}
public String getJmxRealm() {
return jmxRealm;
}
public void setJmxRealm(String jmxRealm) {
this.jmxRealm = jmxRealm;
}
public String getServiceUrl() {
String rmiServer = "";
if (rmiRegistryPort != 0) {
// This is handy to use if you have a firewall and need to force JMX to use fixed ports.
rmiServer = "" + getConnectorHost() + ":" + rmiRegistryPort;
}
return "service:jmx:rmi://" + rmiServer + "/jndi/rmi://" + getConnectorHost() + ":" + connectorPort + connectorPath;
}
public String getAuthenticatorType() {
return authenticatorType;
}
public void setAuthenticatorType(String authenticatorType) {
this.authenticatorType = authenticatorType;
}
public boolean isSecured() {
return secured;
}
public String getKeyStoreProvider() {
return keyStoreProvider;
}
public void setKeyStoreProvider(String keyStoreProvider) {
this.keyStoreProvider = keyStoreProvider;
}
public String getKeyStorePath() {
return keyStorePath;
}
public void setKeyStorePath(String keyStorePath) {
this.keyStorePath = keyStorePath;
}
public String getKeyStorePassword() {
return keyStorePassword;
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public String getTrustStoreProvider() {
return trustStoreProvider;
}
public void setTrustStoreProvider(String trustStoreProvider) {
this.trustStoreProvider = trustStoreProvider;
}
public String getTrustStorePath() {
return trustStorePath;
}
public void setTrustStorePath(String trustStorePath) {
this.trustStorePath = trustStorePath;
}
public String getTrustStorePassword() {
return trustStorePassword;
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
public void setObjectName(String objectName) {
this.objectName = objectName;
}
public String getObjectName() {
return objectName;
}
public void setSecured(Boolean secured) {
this.secured = secured;
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.activemq.artemis.core.server.management;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.management.MBeanServer;
import javax.management.MBeanServerBuilder;
import javax.management.MBeanServerDelegate;
public class ArtemisMBeanServerBuilder extends MBeanServerBuilder {
private static volatile InvocationHandler guard;
public static void setGuard(InvocationHandler guardHandler) {
guard = guardHandler;
}
public static ArtemisMBeanServerGuard getArtemisMBeanServerGuard() {
return (ArtemisMBeanServerGuard) guard;
}
@Override
public MBeanServer newMBeanServer(String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate) {
InvocationHandler handler = new MBeanInvocationHandler(super.newMBeanServer(defaultDomain, outer, delegate));
return (MBeanServer) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{MBeanServer.class}, handler);
}
private static final class MBeanInvocationHandler implements InvocationHandler {
private final MBeanServer wrapped;
private final List<String> guarded = Collections.unmodifiableList(Arrays.asList("invoke", "getAttribute", "getAttributes", "setAttribute", "setAttributes"));
MBeanInvocationHandler(MBeanServer mbeanServer) {
wrapped = mbeanServer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (guarded.contains(method.getName())) {
if (ArtemisMBeanServerBuilder.guard == null) {
throw new IllegalStateException("ArtemisMBeanServerBuilder not initialized");
}
guard.invoke(proxy, method, args);
}
if (method.getName().equals("queryMBeans")) {
guard.invoke(wrapped, method, args);
}
if (method.getName().equals("equals")
&& method.getParameterTypes().length == 1
&& method.getParameterTypes()[0] == Object.class) {
Object target = args[0];
if (target != null && Proxy.isProxyClass(target.getClass())) {
InvocationHandler handler = Proxy.getInvocationHandler(target);
if (handler instanceof MBeanInvocationHandler) {
args[0] = ((MBeanInvocationHandler) handler).wrapped;
}
}
} else if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) {
// special case finalize, don't route through to delegate because that will get its own call
return null;
}
try {
return method.invoke(wrapped, args);
} catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
}
}

View File

@ -0,0 +1,162 @@
/*
* 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.activemq.artemis.core.server.management;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.security.auth.Subject;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Principal;
import java.util.List;
public class ArtemisMBeanServerGuard implements InvocationHandler {
private JMXAccessControlList jmxAccessControlList = JMXAccessControlList.createDefaultList();
public void init() {
ArtemisMBeanServerBuilder.setGuard(this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getParameterTypes().length == 0)
return null;
if (!ObjectName.class.isAssignableFrom(method.getParameterTypes()[0]))
return null;
ObjectName objectName = (ObjectName) args[0];
if ("getAttribute".equals(method.getName())) {
handleGetAttribute((MBeanServer) proxy, objectName, (String) args[1]);
} else if ("getAttributes".equals(method.getName())) {
handleGetAttributes((MBeanServer) proxy, objectName, (String[]) args[1]);
} else if ("setAttribute".equals(method.getName())) {
handleSetAttribute((MBeanServer) proxy, objectName, (Attribute) args[1]);
} else if ("setAttributes".equals(method.getName())) {
handleSetAttributes((MBeanServer) proxy, objectName, (AttributeList) args[1]);
} else if ("invoke".equals(method.getName())) {
handleInvoke(objectName, (String) args[1], (Object[]) args[2], (String[]) args[3]);
}
return null;
}
private void handleGetAttribute(MBeanServer proxy, ObjectName objectName, String attributeName) throws JMException, IOException {
MBeanInfo info = proxy.getMBeanInfo(objectName);
String prefix = null;
for (MBeanAttributeInfo attr : info.getAttributes()) {
if (attr.getName().equals(attributeName)) {
prefix = attr.isIs() ? "is" : "get";
}
}
if (prefix == null) {
//ActiveMQServerLogger.LOGGER.debug("Attribute " + attributeName + " can not be found for MBean " + objectName.toString());
} else {
handleInvoke(objectName, prefix + attributeName, new Object[]{}, new String[]{});
}
}
private void handleGetAttributes(MBeanServer proxy, ObjectName objectName, String[] attributeNames) throws JMException, IOException {
for (String attr : attributeNames) {
handleGetAttribute(proxy, objectName, attr);
}
}
private void handleSetAttribute(MBeanServer proxy, ObjectName objectName, Attribute attribute) throws JMException, IOException {
String dataType = null;
MBeanInfo info = proxy.getMBeanInfo(objectName);
for (MBeanAttributeInfo attr : info.getAttributes()) {
if (attr.getName().equals(attribute.getName())) {
dataType = attr.getType();
break;
}
}
if (dataType == null)
throw new IllegalStateException("Attribute data type can not be found");
handleInvoke(objectName, "set" + attribute.getName(), new Object[]{attribute.getValue()}, new String[]{dataType});
}
private void handleSetAttributes(MBeanServer proxy, ObjectName objectName, AttributeList attributes) throws JMException, IOException {
for (Attribute attr : attributes.asList()) {
handleSetAttribute(proxy, objectName, attr);
}
}
private boolean canBypassRBAC(ObjectName objectName) {
return jmxAccessControlList.isInWhiteList(objectName);
}
void handleInvoke(ObjectName objectName, String operationName, Object[] params, String[] signature) throws IOException {
if (canBypassRBAC(objectName)) {
return;
}
List<String> requiredRoles = getRequiredRoles(objectName, operationName, params, signature);
for (String role : requiredRoles) {
if (currentUserHasRole(role))
return;
}
throw new SecurityException("Insufficient roles/credentials for operation");
}
List<String> getRequiredRoles(ObjectName objectName, String methodName, Object[] params, String[] signature) throws IOException {
return jmxAccessControlList.getRolesForObject(objectName, methodName);
}
public void setJMXAccessControlList(JMXAccessControlList JMXAccessControlList) {
this.jmxAccessControlList = JMXAccessControlList;
}
public static boolean currentUserHasRole(String requestedRole) {
String clazz;
String role;
int index = requestedRole.indexOf(':');
if (index > 0) {
clazz = requestedRole.substring(0, index);
role = requestedRole.substring(index + 1);
} else {
clazz = "org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal";
role = requestedRole;
}
AccessControlContext acc = AccessController.getContext();
if (acc == null) {
return false;
}
Subject subject = Subject.getSubject(acc);
if (subject == null) {
return false;
}
for (Principal p : subject.getPrincipals()) {
if (clazz.equals(p.getClass().getName()) && role.equals(p.getName())) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,282 @@
/*
* 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.activemq.artemis.core.server.management;
import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.util.Map;
public class ConnectorServerFactory {
public void setkeyStoreProvider(String keyStoreProvider) {
this.keyStoreProvider = keyStoreProvider;
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public void setTrustStorePath(String trustStorePath) {
this.trustStorePath = trustStorePath;
}
public void setTrustStoreProvider(String trustStoreProvider) {
this.trustStoreProvider = trustStoreProvider;
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
private enum AuthenticatorType { NONE, PASSWORD, CERTIFICATE };
private MBeanServer server;
private String serviceUrl;
private String rmiServerHost;
private Map environment;
private ObjectName objectName;
private JMXConnectorServer connectorServer;
private AuthenticatorType authenticatorType = AuthenticatorType.PASSWORD;
private boolean secured;
private String keyStoreProvider;
private String keyStorePath;
private String keyStorePassword;
private String trustStoreProvider;
private String trustStorePath;
private String trustStorePassword;
public String getKeyStoreProvider() {
return keyStoreProvider;
}
public String getKeyStorePath() {
return keyStorePath;
}
public void setKeyStorePath(String keyStorePath) {
this.keyStorePath = keyStorePath;
}
public String getKeyStorePassword() {
return keyStorePassword;
}
public String getTrustStoreProvider() {
return trustStoreProvider;
}
public String getTrustStorePath() {
return trustStorePath;
}
public String getTrustStorePassword() {
return trustStorePassword;
}
public MBeanServer getServer() {
return server;
}
public void setServer(MBeanServer server) {
this.server = server;
}
public String getServiceUrl() {
return serviceUrl;
}
public void setServiceUrl(String serviceUrl) {
this.serviceUrl = serviceUrl;
}
public String getRmiServerHost() {
return this.rmiServerHost;
}
public void setRmiServerHost(String rmiServerHost) {
this.rmiServerHost = rmiServerHost;
}
public Map getEnvironment() {
return environment;
}
public void setEnvironment(Map environment) {
this.environment = environment;
}
public ObjectName getObjectName() {
return objectName;
}
public void setObjectName(ObjectName objectName) {
this.objectName = objectName;
}
public String getAuthenticatorType() {
return this.authenticatorType.name().toLowerCase();
}
/**
* Authenticator type to use. Acceptable values are "none", "password", and "certificate"
*
* @param value
*/
public void setAuthenticatorType(String value) {
this.authenticatorType = AuthenticatorType.valueOf(value.toUpperCase());
}
public boolean isSecured() {
return this.secured;
}
public void setSecured(boolean secured) {
this.secured = secured;
}
private boolean isClientAuth() {
return this.authenticatorType.equals(AuthenticatorType.CERTIFICATE);
}
public void init() throws Exception {
if (this.server == null) {
throw new IllegalArgumentException("server must be set");
}
JMXServiceURL url = new JMXServiceURL(this.serviceUrl);
setupArtemisRMIServerSocketFactory();
if (isClientAuth()) {
this.secured = true;
}
if (this.secured) {
this.setupSsl();
}
if (!AuthenticatorType.PASSWORD.equals(this.authenticatorType)) {
this.environment.remove("jmx.remote.authenticator");
}
this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, this.environment, this.server);
if (this.objectName != null) {
this.server.registerMBean(this.connectorServer, this.objectName);
}
try {
this.connectorServer.start();
} catch (Exception ex) {
doUnregister(this.objectName);
throw ex;
}
}
public void destroy() throws Exception {
try {
if (this.connectorServer != null) {
this.connectorServer.stop();
}
} finally {
doUnregister(this.objectName);
}
}
protected void doUnregister(ObjectName objectName) {
try {
if (this.objectName != null && this.server.isRegistered(objectName)) {
this.server.unregisterMBean(objectName);
}
} catch (JMException ex) {
// Ignore
}
}
//todo fix
private void setupSsl() throws Exception {
SSLContext context = SSLSupport.createContext(keyStoreProvider, keyStorePath, keyStorePassword, trustStoreProvider, trustStorePath, trustStorePassword);
SSLServerSocketFactory sssf = context.getServerSocketFactory();
RMIServerSocketFactory rssf = new ArtemisSslRMIServerSocketFactory(sssf, this.isClientAuth(), rmiServerHost);
RMIClientSocketFactory rcsf = new SslRMIClientSocketFactory();
environment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, rssf);
environment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, rcsf);
}
private void setupArtemisRMIServerSocketFactory() {
RMIServerSocketFactory rmiServerSocketFactory = new ArtemisRMIServerSocketFactory(getRmiServerHost());
environment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, rmiServerSocketFactory);
}
private static class ArtemisSslRMIServerSocketFactory implements RMIServerSocketFactory {
private SSLServerSocketFactory sssf;
private boolean clientAuth;
private String rmiServerHost;
ArtemisSslRMIServerSocketFactory(SSLServerSocketFactory sssf, boolean clientAuth, String rmiServerHost) {
this.sssf = sssf;
this.clientAuth = clientAuth;
this.rmiServerHost = rmiServerHost;
}
@Override
public ServerSocket createServerSocket(int port) throws IOException {
SSLServerSocket ss = (SSLServerSocket) sssf.createServerSocket(port, 50, InetAddress.getByName(rmiServerHost));
ss.setNeedClientAuth(clientAuth);
return ss;
}
}
private static class ArtemisRMIServerSocketFactory implements RMIServerSocketFactory {
private String rmiServerHost;
ArtemisRMIServerSocketFactory(String rmiServerHost) {
this.rmiServerHost = rmiServerHost;
}
@Override
public ServerSocket createServerSocket(int port) throws IOException {
ServerSocket serverSocket = (ServerSocket) ServerSocketFactory.getDefault().createServerSocket(port, 50, InetAddress.getByName(rmiServerHost));
return serverSocket;
}
}
}

View File

@ -0,0 +1,203 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.core.server.management;
import javax.management.ObjectName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class JMXAccessControlList {
private Access defaultAccess = new Access("*");
private Map<String, Access> domainAccess = new HashMap<>();
private ConcurrentHashMap<String, List<String>> whitelist = new ConcurrentHashMap<>();
public void addToWhiteList(String domain, String match) {
List<String> list = new ArrayList<>();
list = whitelist.putIfAbsent(domain, list);
if (list == null) {
list = whitelist.get(domain);
}
list.add(match != null ? match : "*");
}
public List<String> getRolesForObject(ObjectName objectName, String methodName) {
Hashtable<String, String> keyPropertyList = objectName.getKeyPropertyList();
for (Map.Entry<String, String> keyEntry : keyPropertyList.entrySet()) {
String key = keyEntry.getKey() + "=" + keyEntry.getValue();
Access access = domainAccess.get(getObjectID(objectName.getDomain(), key));
if (access != null) {
return access.getMatchingRolesForMethod(methodName);
}
}
for (Map.Entry<String, String> keyEntry : keyPropertyList.entrySet()) {
String key = keyEntry.getKey() + "=*";
Access access = domainAccess.get(getObjectID(objectName.getDomain(), key));
if (access != null) {
return access.getMatchingRolesForMethod(methodName);
}
}
Access access = domainAccess.get(objectName.getDomain());
if (access == null) {
access = defaultAccess;
}
return access.getMatchingRolesForMethod(methodName);
}
public boolean isInWhiteList(ObjectName objectName) {
List<String> matches = whitelist.get(objectName.getDomain());
if (matches != null) {
for (String match : matches) {
if (match.equals("*")) {
return true;
} else {
String[] split = match.split("=");
String key = split[0];
String val = split[1];
String propVal = objectName.getKeyProperty(key);
if (propVal != null && (val.equals("*") || propVal.equals(val))) {
return true;
}
}
}
}
return false;
}
public void addToDefaultAccess(String method, String... roles) {
if (roles != null) {
if ( method.equals("*")) {
defaultAccess.addCatchAll(roles);
} else if (method.endsWith("*")) {
String prefix = method.replace("*", "");
defaultAccess.addMethodsPrefixes(prefix, roles);
} else {
defaultAccess.addMethods(method, roles);
}
}
}
public void addToRoleAccess(String domain,String key, String method, String... roles) {
String id = getObjectID(domain, key);
Access access = domainAccess.get(id);
if (access == null) {
access = new Access(domain);
domainAccess.put(id, access);
}
if (method.endsWith("*")) {
String prefix = method.replace("*", "");
access.addMethodsPrefixes(prefix, roles);
} else {
access.addMethods(method, roles);
}
}
private String getObjectID(String domain, String key) {
String id = domain;
if (key != null) {
String actualKey = key;
if (key.endsWith("\"")) {
actualKey = actualKey.replace("\"", "");
}
id += ":" + actualKey;
}
return id;
}
static class Access {
private final String domain;
List<String> catchAllRoles = new ArrayList<>();
Map<String, List<String>> methodRoles = new HashMap<>();
Map<String, List<String>> methodPrefixRoles = new HashMap<>();
Access(String domain) {
this.domain = domain;
}
public synchronized void addMethods(String prefix, String... roles) {
List<String> rolesList = methodRoles.get(prefix);
if (rolesList == null) {
rolesList = new ArrayList<>();
methodRoles.put(prefix, rolesList);
}
for (String role : roles) {
rolesList.add(role);
}
}
public synchronized void addMethodsPrefixes(String prefix, String... roles) {
List<String> rolesList = methodPrefixRoles.get(prefix);
if (rolesList == null) {
rolesList = new ArrayList<>();
methodPrefixRoles.put(prefix, rolesList);
}
for (String role : roles) {
rolesList.add(role);
}
}
public void addCatchAll(String... roles) {
for (String role : roles) {
catchAllRoles.add(role);
}
}
public String getDomain() {
return domain;
}
public List<String> getMatchingRolesForMethod(String methodName) {
List<String> roles = methodRoles.get(methodName);
if (roles != null) {
return roles;
}
for (Map.Entry<String, List<String>> entry : methodPrefixRoles.entrySet()) {
if (methodName.startsWith(entry.getKey())) {
return entry.getValue();
}
}
return catchAllRoles;
}
}
public static JMXAccessControlList createDefaultList() {
JMXAccessControlList accessControlList = new JMXAccessControlList();
accessControlList.addToWhiteList("hawtio", "type=*");
accessControlList.addToRoleAccess("org.apache.activemq.apache", null, "list*", "view", "update", "amq");
accessControlList.addToRoleAccess("org.apache.activemq.apache", null,"get*", "view", "update", "amq");
accessControlList.addToRoleAccess("org.apache.activemq.apache", null,"is*", "view", "update", "amq");
accessControlList.addToRoleAccess("org.apache.activemq.apache", null,"set*","update", "amq");
accessControlList.addToRoleAccess("org.apache.activemq.apache", null,"*", "amq");
accessControlList.addToDefaultAccess("list*", "view", "update", "amq");
accessControlList.addToDefaultAccess("get*", "view", "update", "amq");
accessControlList.addToDefaultAccess("is*", "view", "update", "amq");
accessControlList.addToDefaultAccess("set*", "update", "amq");
accessControlList.addToDefaultAccess("*", "amq");
return accessControlList;
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.activemq.artemis.core.server.management;
import javax.management.remote.JMXAuthenticator;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.io.IOException;
public class JaasAuthenticator implements JMXAuthenticator {
private String realm;
public String getRealm() {
return realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
@Override
public Subject authenticate(Object credentials) throws SecurityException {
if (!(credentials instanceof String[])) {
throw new IllegalArgumentException("Expected String[2], got "
+ (credentials != null ? credentials.getClass().getName() : null));
}
final String[] params = (String[]) credentials;
if (params.length != 2) {
throw new IllegalArgumentException("Expected String[2] but length was " + params.length);
}
try {
Subject subject = new Subject();
LoginContext loginContext = new LoginContext(realm, subject, new CallbackHandler() {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName(params[0]);
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword((params[1].toCharArray()));
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
}
}
});
loginContext.login();
/* if (subject.getPrincipals().size() == 0) {
// there must be some Principals, but which ones required are tested later
throw new FailedLoginException("User does not have the required role");
}*/
return subject;
} catch (LoginException e) {
throw new SecurityException("Authentication failed", e);
}
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.activemq.artemis.core.server.management;
import javax.management.MBeanServer;
import java.lang.management.ManagementFactory;
import java.util.List;
public class MBeanServerFactory {
private boolean locateExistingServerIfPossible = false;
private String defaultDomain;
private boolean registerWithFactory = true;
private MBeanServer server;
private boolean newlyRegistered = false;
public boolean isLocateExistingServerIfPossible() {
return locateExistingServerIfPossible;
}
public void setLocateExistingServerIfPossible(boolean locateExistingServerIfPossible) {
this.locateExistingServerIfPossible = locateExistingServerIfPossible;
}
public String getDefaultDomain() {
return defaultDomain;
}
public void setDefaultDomain(String defaultDomain) {
this.defaultDomain = defaultDomain;
}
public boolean isRegisterWithFactory() {
return registerWithFactory;
}
public void setRegisterWithFactory(boolean registerWithFactory) {
this.registerWithFactory = registerWithFactory;
}
public boolean isNewlyRegistered() {
return newlyRegistered;
}
public void setNewlyRegistered(boolean newlyRegistered) {
this.newlyRegistered = newlyRegistered;
}
public MBeanServer getServer() throws Exception {
if (this.server == null) {
init();
}
return server;
}
public void init() throws Exception {
if (this.locateExistingServerIfPossible) {
List servers = javax.management.MBeanServerFactory.findMBeanServer(null);
if (servers != null && servers.size() > 0) {
this.server = (MBeanServer) servers.get(0);
}
if (this.server == null) {
this.server = ManagementFactory.getPlatformMBeanServer();
}
if (this.server == null) {
throw new Exception("Unable to locate MBeanServer");
}
}
if (this.server == null) {
if (this.registerWithFactory) {
this.server = javax.management.MBeanServerFactory.createMBeanServer(this.defaultDomain);
} else {
this.server = javax.management.MBeanServerFactory.newMBeanServer(this.defaultDomain);
}
this.newlyRegistered = this.registerWithFactory;
}
}
public void destroy() throws Exception {
if (this.newlyRegistered) {
javax.management.MBeanServerFactory.releaseMBeanServer(this.server);
}
}
}

View File

@ -0,0 +1,114 @@
/*
* 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.activemq.artemis.core.server.management;
import org.apache.activemq.artemis.core.config.JMXConnectorConfiguration;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.util.HashMap;
import java.util.Map;
public class ManagementConnector implements ActiveMQComponent {
private final JMXConnectorConfiguration configuration;
private ConnectorServerFactory connectorServerFactory;
private RmiRegistryFactory rmiRegistryFactory;
private MBeanServerFactory mbeanServerFactory;
public ManagementConnector(JMXConnectorConfiguration configuration) {
this.configuration = configuration;
}
@Override
public boolean isStarted() {
return rmiRegistryFactory != null;
}
@Override
public void start() throws Exception {
ArtemisMBeanServerGuard guard = new ArtemisMBeanServerGuard();
guard.init();
rmiRegistryFactory = new RmiRegistryFactory();
rmiRegistryFactory.setPort(configuration.getConnectorPort());
rmiRegistryFactory.init();
mbeanServerFactory = new MBeanServerFactory();
mbeanServerFactory.setLocateExistingServerIfPossible(true);
mbeanServerFactory.init();
MBeanServer mbeanServer = mbeanServerFactory.getServer();
JaasAuthenticator jaasAuthenticator = new JaasAuthenticator();
jaasAuthenticator.setRealm(configuration.getJmxRealm());
connectorServerFactory = new ConnectorServerFactory();
connectorServerFactory.setServer(mbeanServer);
connectorServerFactory.setServiceUrl(configuration.getServiceUrl());
connectorServerFactory.setRmiServerHost(configuration.getConnectorHost());
connectorServerFactory.setObjectName(new ObjectName(configuration.getObjectName()));
Map<String, Object> environment = new HashMap<>();
environment.put("jmx.remote.authenticator", jaasAuthenticator);
try {
connectorServerFactory.setEnvironment(environment);
connectorServerFactory.setAuthenticatorType(configuration.getAuthenticatorType());
connectorServerFactory.setSecured(configuration.isSecured());
connectorServerFactory.setKeyStorePath(configuration.getKeyStorePath());
connectorServerFactory.setkeyStoreProvider(configuration.getKeyStoreProvider());
connectorServerFactory.setKeyStorePassword(configuration.getKeyStorePassword());
connectorServerFactory.setTrustStorePath(configuration.getTrustStorePath());
connectorServerFactory.setTrustStoreProvider(configuration.getTrustStoreProvider());
connectorServerFactory.setTrustStorePassword(configuration.getTrustStorePassword());
connectorServerFactory.init();
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.error("Can't init JMXConnectorServer: " + e.getMessage());
}
}
@Override
public void stop() {
if (connectorServerFactory != null) {
try {
connectorServerFactory.destroy();
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.warn("Error destroying ConnectorServerFactory", e);
}
connectorServerFactory = null;
}
if (mbeanServerFactory != null) {
try {
mbeanServerFactory.destroy();
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.warn("Error destroying MBeanServerFactory", e);
}
mbeanServerFactory = null;
}
if (rmiRegistryFactory != null) {
try {
rmiRegistryFactory.destroy();
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.warn("Error destroying RMIRegistryFactory", e);
}
rmiRegistryFactory = null;
}
}
}

View File

@ -0,0 +1,74 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.core.server.management;
import org.apache.activemq.artemis.core.config.JMXConnectorConfiguration;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
public class ManagementContext implements ActiveMQComponent {
private boolean isStarted = false;
private JMXAccessControlList accessControlList;
private JMXConnectorConfiguration jmxConnectorConfiguration;
private ManagementConnector mBeanServer;
@Override
public void start() throws Exception {
if (accessControlList != null) {
//if we are configured then assume we want to use the guard so set the system property
System.setProperty("javax.management.builder.initial", ArtemisMBeanServerBuilder.class.getCanonicalName());
ArtemisMBeanServerGuard guardHandler = new ArtemisMBeanServerGuard();
guardHandler.setJMXAccessControlList(accessControlList);
ArtemisMBeanServerBuilder.setGuard(guardHandler);
}
if (jmxConnectorConfiguration != null) {
mBeanServer = new ManagementConnector(jmxConnectorConfiguration);
mBeanServer.start();
}
isStarted = true;
}
@Override
public void stop() throws Exception {
isStarted = false;
if (mBeanServer != null) {
mBeanServer.stop();
}
}
@Override
public boolean isStarted() {
return isStarted;
}
public void setAccessControlList(JMXAccessControlList accessControlList) {
this.accessControlList = accessControlList;
}
public JMXAccessControlList getAccessControlList() {
return accessControlList;
}
public void setJmxConnectorConfiguration(JMXConnectorConfiguration jmxConnectorConfiguration) {
this.jmxConnectorConfiguration = jmxConnectorConfiguration;
}
public JMXConnectorConfiguration getJmxConnectorConfiguration() {
return jmxConnectorConfiguration;
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.activemq.artemis.core.server.management;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class RmiRegistryFactory {
private int port = Registry.REGISTRY_PORT;
private Registry registry;
/**
* @return the port
*/
public int getPort() {
return port;
}
/**
* @param port the port to set
*/
public void setPort(int port) {
this.port = port;
}
public Object getObject() throws Exception {
return registry;
}
public void init() throws RemoteException, UnknownHostException {
registry = LocateRegistry.createRegistry(port);
}
public void destroy() throws RemoteException {
if (registry != null) {
UnicastRemoteObject.unexportObject(registry, true);
}
}
}

View File

@ -0,0 +1,153 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.core.server.management;
import org.junit.Assert;
import org.junit.Test;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.util.List;
public class JMXAccessControlListTest {
@Test
public void testBasicDomain() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToWhiteList("org.myDomain", null);
controlList.addToWhiteList("org.myDomain.foo", null);
Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain:*")));
Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain.foo:*")));
Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.bar:*")));
}
@Test
public void testBasicDomainWithProperty() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToWhiteList("org.myDomain", "type=foo");
controlList.addToWhiteList("org.myDomain.foo", "type=bar");
Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain:*")));
Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.foo:*")));
Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.bar:*")));
Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain:subType=foo")));
Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain:type=foo")));
Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain:subType=bar,type=foo")));
}
@Test
public void testBasicDomainWithWildCardProperty() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToWhiteList("org.myDomain", "type=*");
Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain:*")));
Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.foo:*")));
Assert.assertFalse(controlList.isInWhiteList(new ObjectName("org.myDomain.bar:*")));
Assert.assertTrue(controlList.isInWhiteList(new ObjectName("org.myDomain:type=foo")));
}
@Test
public void testBasicRole() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToRoleAccess("org.myDomain", null,"listSomething", "admin");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomething");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
}
@Test
public void testBasicRoleWithKey() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToRoleAccess("org.myDomain", "type=foo","listSomething", "admin");
controlList.addToRoleAccess("org.myDomain", null,"listSomething", "view");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain:type=foo"), "listSomething");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
}
@Test
public void testBasicRoleWithKeyContainingQuotes() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToRoleAccess("org.myDomain", "type=foo","listSomething", "admin");
controlList.addToRoleAccess("org.myDomain", null,"listSomething", "view");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain:type=\"foo\""), "listSomething");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
}
@Test
public void testBasicRoleWithWildcardKey() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToRoleAccess("org.myDomain", "type=*","listSomething", "admin");
controlList.addToRoleAccess("org.myDomain", null,"listSomething", "view");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain:type=foo"), "listSomething");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
}
@Test
public void testMutipleBasicRoles() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToRoleAccess("org.myDomain", null, "listSomething", "admin", "view", "update");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomething");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin", "view", "update"});
}
@Test
public void testBasicRoleWithPrefix() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToRoleAccess("org.myDomain", null,"list*", "admin");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomething");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
}
@Test
public void testBasicRoleWithBoth() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToRoleAccess("org.myDomain", null,"listSomething", "admin");
controlList.addToRoleAccess("org.myDomain", null,"list*", "view");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomething");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
roles = controlList.getRolesForObject(new ObjectName("org.myDomain:*"), "listSomethingMore");
Assert.assertArrayEquals(roles.toArray(), new String[]{"view"});
}
@Test
public void testBasicRoleWithDefaultsPrefix() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToDefaultAccess("setSomething","admin");
controlList.addToRoleAccess("org.myDomain", null,"list*", "view");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain.foo:*"), "setSomething");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
}
@Test
public void testBasicRoleWithDefaultsWildcardPrefix() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToDefaultAccess("setSomething","admin");
controlList.addToDefaultAccess("set*","admin");
controlList.addToRoleAccess("org.myDomain", null,"list*", "view");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain.foo:*"), "setSomethingMore");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
}
@Test
public void testBasicRoleWithDefaultscatchAllPrefix() throws MalformedObjectNameException {
JMXAccessControlList controlList = new JMXAccessControlList();
controlList.addToDefaultAccess("setSomething","admin");
controlList.addToDefaultAccess("*","admin");
controlList.addToRoleAccess("org.myDomain", null,"list*", "view");
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain.foo:*"), "setSomethingMore");
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
}
}

View File

@ -296,16 +296,132 @@ setting `jmx-management-enabled` to `false` in
<!-- false to disable JMX management for Apache ActiveMQ Artemis -->
<jmx-management-enabled>false</jmx-management-enabled>
#### Role Based Authentication with JMX
If JMX is enabled, Apache ActiveMQ Artemis can be managed locally using `jconsole`.
Although by default Artemis uses the Java Virtual Machine's `Platform MBeanServer`
this is guarded using role based authentication that leverages Artemis's JAAS plugin support.
This is configured via the `authorisation` element in the `management.xml` configuration file
and can be used to restrict access to attributes and methods on mbeans.
There are 3 elements within the `authorisation` element, `whitelist`, `default-access` and
`role-access`, Lets discuss each in turn.
Whitelist contains a list of mBeans that will by pass the authentication, this is typically
used for any mbeans that are needed by the console to run etc. The default configuration is:
```xml
<whitelist>
<entry domain="hawtio"/>
</whitelist>
```
This means that any mbean with the domain `hawtio` will be allowed access without authorisation. for instance
`hawtio:plugin=artemis`. You can also use wildcards for the mBean properties so the following would also match.
```xml
<whitelist>
<entry domain="hawtio" key="type=*"/>
</whitelist>
```
The `role-access`defines how roles are mapped to particular mBeans and its attributes and methods,
the default configuration looks like:
```xml
<role-access>
<match domain="org.apache.activemq.apache">
<access method="list*" roles="view,update,amq"/>
<access method="get*" roles="view,update,amq"/>
<access method="is*" roles="view,update,amq"/>
<access method="set*" roles="update,amq"/>
<access method="*" roles="amq"/>
</match>
</role-access>
```
This contains 1 match and will be applied to any mBean that has the domain `org.apache.activemq.apache`.
Any access to any mBeans that have this domain are controlled by the `access` elements which contain a
method and a set of roles. The method being invoked will be used to pick the closest matching method and
the roles for this will be applied for access. For instance if you try the invoke a method called `listMessages` on an mBean
with the `org.apache.activemq.apache` domain then this would match the `access` with the method of `list*`.
You could also explicitly configure this by using the full method name, like so:
```xml
<access method="listMessages" roles="view,update,amq"/>
```
You can also match specific mBeans within a domain by adding a key attribute that is used to match one of the properties
on the mBean, like:
```xml
<match domain="org.apache.activemq.apache" key="subcomponent=queues">
<access method="list*" roles="view,update,amq"/>
<access method="get*" roles="view,update,amq"/>
<access method="is*" roles="view,update,amq"/>
<access method="set*" roles="update,amq"/>
<access method="*" roles="amq"/>
</match>
```
You could also match a specific queue for instance :
`org.apache.activemq.artemis:broker=<brokerName>,component=addresses,address="exampleAddress",subcomponent=queues,routing-type="anycast",queue="exampleQueue"`
by configuring:
```xml
<match domain="org.apache.activemq.apache" key="queue=exampleQueue">
<access method="list*" roles="view,update,amq"/>
<access method="get*" roles="view,update,amq"/>
<access method="is*" roles="view,update,amq"/>
<access method="set*" roles="update,amq"/>
<access method="*" roles="amq"/>
</match>
```
Access to JMX mBean attributes are converted to method calls so these are controlled via the `set*`, `get*` and `is*`.
The `*` access is the catch all for everything other method that isnt specifically matched.
The `default-access` element is basically the catch all for every method call that isn't handled via the `role-access` configuration.
This has teh same semantics as a `match` element.
> **Note**
>
> Remote connections to JMX are not enabled by default for security
> reasons. Please refer to [Java Management
> guide](http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html)
> to configure the server for remote management (system properties must
> be set in `artemis.profile`).
> If JMX is enabled, Apache ActiveMQ Artemis can *not* be managed locally using `jconsole` when connecting as a local process,
> this is because jconsole does not using any authentication when connecting this way. If you want to use jconsole you will
either have to disable authentication, by removing the `authentication` element or enable remote access.
#### Configuring remote JMX Access
By default remote JMX access to Artemis is disabled for security reasons.
Artemis has a JMX agent which allows access to JMX mBeans remotely. This is configured via the `connector` element in the
`management.xml` configuration file. To enable this you simpl ad the following xml:
```xml
<connector connector-port="1099"/>
```
This exposes the agent remotely on the port 1099. If you were connecting via jconsole you would connect as a remote process
using the service url `service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi` and an appropriate user name and password.
You can also configure the connector using the following:
- connector-host The host to expose the agent on
- connector-port The port to expose the agent on
- jmx-realm The jmx realm to use for authentication, defaults to `activemq` to match the JAAS configuration.
- object-name The object name to expose the remote connector on, default is `connector:name=rmi`
> **Note**
>
> It is important to note that the rmi registry will pick an ip address to bind to, If you have a multi IP addresses/NICs
> present on the system then you can choose the ip address to use by adding the following to artemis.profile
> `-Djava.rmi.server.hostname=localhost`
> **Note**
>
> Remote connections using the default JVM Agent not enabled by default as Artemis exposes the mBean Server via its own configuration.
> This is so Artemis can leverage the JAAS authentication layer via JMX. If you want to expose this then you will need to
> disable both the connector and the authorisation by removing them from the `management.xml` configuration.
> Please refer to [Java Management guide](http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html)
> to configure the server for remote management (system properties must be set in `artemis.profile`).
By default, Apache ActiveMQ Artemis server uses the JMX domain "org.apache.activemq.artemis".
To manage several Apache ActiveMQ Artemis servers from the *same* MBeanServer, the JMX
@ -315,12 +431,6 @@ domain can be configured for each individual Apache ActiveMQ Artemis server by s
<!-- use a specific JMX domain for ActiveMQ Artemis MBeans -->
<jmx-domain>my.org.apache.activemq</jmx-domain>
#### MBeanServer configuration
When Apache ActiveMQ Artemis is run in standalone, it uses the Java Virtual Machine's
`Platform MBeanServer` to register its MBeans. By default, [Jolokia](http://www.jolokia.org/)
is also deployed to allow access to the MBean server via [REST](https://en.wikipedia.org/wiki/Representational_state_transfer).
### Example
See the [Examples](examples.md) chapter for an example which shows how to use a remote connection to JMX
@ -333,12 +443,23 @@ HTTP agent deployed as a Web Application. Jolokia is a remote
JMX-over-HTTP bridge that exposes MBeans. For a full guide as
to how to use it refer to [Jolokia Documentation](http://www.jolokia.org/documentation.html),
however a simple example to query the broker's version would
be to use a browser and go to the URL [http://localhost:8161/jolokia/read/org.apache.activemq.artemis:broker="0.0.0.0"/Version]().
be to use a browser and go to the URL [http://username:password@localhost:8161/jolokia/read/org.apache.activemq.artemis:broker="0.0.0.0"/Version]().
This would give you back something like the following:
{"request":{"mbean":"org.apache.activemq.artemis:broker=\"0.0.0.0\"","attribute":"Version","type":"read"},"value":"2.0.0-SNAPSHOT","timestamp":1487017918,"status":200}
### JMX and the Console
The console that ships with Artemis uses Jolokia under the covers which in turn uses JMX. This will use the authentication
configuration in the `management.xml` file as described in the previous section. This means that when mBeans are accessed
via the console the credentials used to log into the console and the roles associated with them. By default access to the
console is only allow via users with the amq role. This is configured in the `artemis.profile` via the system property `-Dhawtio.role=amq`.
You can configure multiple roles by changing this to `-Dhawtio.roles=amq,view,update`.
If a user doesn't have the correct role to invoke a specific operation then this will display an authorisation exception
in the console.
## Using Management Via Apache ActiveMQ Artemis API
The management API in ActiveMQ Artemis is accessed by sending Core Client messages

View File

@ -72,6 +72,7 @@ under the License.
<goal>cli</goal>
</goals>
<configuration>
<debug>true</debug>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<testURI>tcp://localhost:61616</testURI>

View File

@ -34,16 +34,12 @@ under the License.
<h2>Example configuration</h2>
<p>ActiveMQ Artemis exposes its managed resources by default on the platform MBeanServer.</p>
<p>To access this MBeanServer remotely, the Java Virtual machine must be started with system properties:
<p>To access this MBeanServer remotely, add the following to the management.xml configuration:
<pre class="prettyprint">
<code>-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=3000
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false</code>
<code><connector connector-port="1099" connector-host="127.0.0.1"/></code>
</pre>
<p>These properties are explained in the Java <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html#gdenl">management guide</a>
(please note that for this example, we will disable user authentication for simplicity sake).</p>
<p>With these properties, ActiveMQ Artemis server will be manageable remotely using standard JMX URL on port <code>3000</code>.</p>
<p>If the example does not work then try changing the host to the ip address of the machine running this example on</p>
<p>With these properties, ActiveMQ Artemis server will be manageable remotely using standard JMX URL on port <code>1099</code>.</p>
</p>
<h2>Example step-by-step</h2>

View File

@ -44,7 +44,7 @@ import org.apache.activemq.artemis.jms.client.ActiveMQTextMessage;
*/
public class JMXExample {
private static final String JMX_URL = "service:jmx:rmi:///jndi/rmi://localhost:3000/jmxrmi";
private static final String JMX_URL = "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi";
public static void main(final String[] args) throws Exception {
QueueConnection connection = null;
@ -79,7 +79,11 @@ public class JMXExample {
ObjectName on = ObjectNameBuilder.DEFAULT.getQueueObjectName(SimpleString.toSimpleString(queue.getQueueName()), SimpleString.toSimpleString(queue.getQueueName()), RoutingType.ANYCAST);
// Step 10. Create JMX Connector to connect to the server's MBeanServer
JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(JMXExample.JMX_URL), new HashMap());
HashMap env = new HashMap();
String[] creds = {"admin", "password"};
env.put(JMXConnector.CREDENTIALS, creds);
JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(JMXExample.JMX_URL), env);
// Step 11. Retrieve the MBeanServerConnection
MBeanServerConnection mbsc = connector.getMBeanServerConnection();

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ 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.
-->
<management-context xmlns="http://activemq.org/schema">
<connector connector-port="1099" connector-host="127.0.0.1"/>
<authorisation>
<whitelist>
<entry domain="hawtio"/>
</whitelist>
<default-access>
<access method="list*" roles="view,update,amq"/>
<access method="get*" roles="view,update,amq"/>
<access method="is*" roles="view,update,amq"/>
<access method="set*" roles="update,amq"/>
<access method="*" roles="amq"/>
</default-access>
<role-access>
<match domain="org.apache.activemq.apache">
<access method="list*" roles="view,update,amq"/>
<access method="get*" roles="view,update,amq"/>
<access method="is*" roles="view,update,amq"/>
<access method="set*" roles="update,amq"/>
<access method="*" roles="amq"/>
</match>
<!--example of how to configure a specific object-->
<!--<match domain="org.apache.activemq.apache" key="subcomponent=queues">
<access method="list*" roles="view,update,amq"/>
<access method="get*" roles="view,update,amq"/>
<access method="is*" roles="view,update,amq"/>
<access method="set*" roles="update,amq"/>
<access method="*" roles="amq"/>
</match>-->
</role-access>
</authorisation>
</management-context>

View File

@ -0,0 +1,94 @@
/**
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.activemq.artemis.tests.integration.management;
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import java.io.IOException;
import java.util.Map;
public class DummyLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private Map<String, ?> sharedState;
private Map<String, ?> options;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
System.out.println("DummyLoginModule.initialize");
}
@Override
public boolean login() throws LoginException {
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("Username: ");
callbacks[1] = new PasswordCallback("Password: ", false);
try {
callbackHandler.handle(callbacks);
} catch (IOException ioe) {
throw new LoginException(ioe.getMessage());
} catch (UnsupportedCallbackException uce) {
throw new LoginException(uce.getMessage() + " not available to obtain information from user");
}
String user = ((NameCallback) callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
if (tmpPassword == null) {
tmpPassword = new char[0];
}
if (user == null) {
throw new FailedLoginException("User is null");
}
subject.getPrincipals().add(new RolePrincipal("amq"));
// String password = users.getProperty(user);
/*if (password == null) {
throw new FailedLoginException("User does not exist: " + user);
}*/
return true;
}
@Override
public boolean commit() throws LoginException {
return true;
}
@Override
public boolean abort() throws LoginException {
return false;
}
@Override
public boolean logout() throws LoginException {
return false;
}
}

View File

@ -76,6 +76,10 @@ public abstract class ManagementTestBase extends ActiveMQTestBase {
public void setUp() throws Exception {
super.setUp();
createMBeanServer();
}
protected void createMBeanServer() {
mbeanServer = MBeanServerFactory.createMBeanServer();
}