This closes #2851
This commit is contained in:
commit
4f3df1f1e4
|
@ -18,79 +18,95 @@ package org.apache.activemq.artemis.core.server.management;
|
||||||
|
|
||||||
import javax.management.ObjectName;
|
import javax.management.ObjectName;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class JMXAccessControlList {
|
public class JMXAccessControlList {
|
||||||
|
private static final String WILDCARD = "*";
|
||||||
|
|
||||||
private Access defaultAccess = new Access("*");
|
private Access defaultAccess = new Access(WILDCARD);
|
||||||
private Map<String, Access> domainAccess = new HashMap<>();
|
private ConcurrentHashMap<String, TreeMap<String, Access>> domainAccess = new ConcurrentHashMap<>();
|
||||||
private ConcurrentHashMap<String, List<String>> whitelist = new ConcurrentHashMap<>();
|
private ConcurrentHashMap<String, TreeMap<String, Access>> whitelist = new ConcurrentHashMap<>();
|
||||||
|
private Comparator<String> keyComparator = (key1, key2) -> {
|
||||||
|
boolean key1ContainsWildCard = key1.contains(WILDCARD);
|
||||||
public void addToWhiteList(String domain, String match) {
|
boolean key2ContainsWildcard = key2.contains(WILDCARD);
|
||||||
List<String> list = new ArrayList<>();
|
if (key1ContainsWildCard && !key2ContainsWildcard) {
|
||||||
list = whitelist.putIfAbsent(domain, list);
|
return +1;
|
||||||
if (list == null) {
|
} else if (!key1ContainsWildCard && key2ContainsWildcard) {
|
||||||
list = whitelist.get(domain);
|
return -1;
|
||||||
|
} else if (key1ContainsWildCard && key2ContainsWildcard) {
|
||||||
|
return key2.length() - key1.length();
|
||||||
}
|
}
|
||||||
list.add(match != null ? match : "*");
|
return key1.length() - key2.length();
|
||||||
|
};
|
||||||
|
|
||||||
|
public void addToWhiteList(String domain, String key) {
|
||||||
|
TreeMap<String, Access> domainMap = new TreeMap<>(keyComparator);
|
||||||
|
domainMap = whitelist.putIfAbsent(domain, domainMap);
|
||||||
|
if (domainMap == null) {
|
||||||
|
domainMap = whitelist.get(domain);
|
||||||
|
}
|
||||||
|
Access access = new Access(domain, normalizeKey(key));
|
||||||
|
domainMap.putIfAbsent(access.getKey(), access);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<String> getRolesForObject(ObjectName objectName, String methodName) {
|
public List<String> getRolesForObject(ObjectName objectName, String methodName) {
|
||||||
Hashtable<String, String> keyPropertyList = objectName.getKeyPropertyList();
|
TreeMap<String, Access> domainMap = domainAccess.get(objectName.getDomain());
|
||||||
for (Map.Entry<String, String> keyEntry : keyPropertyList.entrySet()) {
|
if (domainMap != null) {
|
||||||
String key = keyEntry.getKey() + "=" + keyEntry.getValue();
|
Hashtable<String, String> keyPropertyList = objectName.getKeyPropertyList();
|
||||||
Access access = domainAccess.get(getObjectID(objectName.getDomain(), key));
|
for (Map.Entry<String, String> keyEntry : keyPropertyList.entrySet()) {
|
||||||
|
String key = normalizeKey(keyEntry.getKey() + "=" + keyEntry.getValue());
|
||||||
|
for (Access accessEntry : domainMap.values()) {
|
||||||
|
if (accessEntry.getKeyPattern().matcher(key).matches()) {
|
||||||
|
return accessEntry.getMatchingRolesForMethod(methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Access access = domainMap.get("");
|
||||||
if (access != null) {
|
if (access != null) {
|
||||||
return access.getMatchingRolesForMethod(methodName);
|
return access.getMatchingRolesForMethod(methodName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, String> keyEntry : keyPropertyList.entrySet()) {
|
|
||||||
String key = keyEntry.getKey() + "=*";
|
return defaultAccess.getMatchingRolesForMethod(methodName);
|
||||||
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) {
|
public boolean isInWhiteList(ObjectName objectName) {
|
||||||
List<String> matches = whitelist.get(objectName.getDomain());
|
TreeMap<String, Access> domainMap = whitelist.get(objectName.getDomain());
|
||||||
if (matches != null) {
|
if (domainMap != null) {
|
||||||
for (String match : matches) {
|
if (domainMap.containsKey("")) {
|
||||||
if (match.equals("*")) {
|
return true;
|
||||||
return true;
|
}
|
||||||
} else {
|
|
||||||
String[] split = match.split("=");
|
Hashtable<String, String> keyPropertyList = objectName.getKeyPropertyList();
|
||||||
String key = split[0];
|
for (Map.Entry<String, String> keyEntry : keyPropertyList.entrySet()) {
|
||||||
String val = split[1];
|
String key = normalizeKey(keyEntry.getKey() + "=" + keyEntry.getValue());
|
||||||
String propVal = objectName.getKeyProperty(key);
|
for (Access accessEntry : domainMap.values()) {
|
||||||
if (propVal != null && (val.equals("*") || propVal.equals(val))) {
|
if (accessEntry.getKeyPattern().matcher(key).matches()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToDefaultAccess(String method, String... roles) {
|
public void addToDefaultAccess(String method, String... roles) {
|
||||||
if (roles != null) {
|
if (roles != null) {
|
||||||
if ( method.equals("*")) {
|
if ( method.equals(WILDCARD)) {
|
||||||
defaultAccess.addCatchAll(roles);
|
defaultAccess.addCatchAll(roles);
|
||||||
} else if (method.endsWith("*")) {
|
} else if (method.endsWith(WILDCARD)) {
|
||||||
String prefix = method.replace("*", "");
|
String prefix = method.replace(WILDCARD, "");
|
||||||
defaultAccess.addMethodsPrefixes(prefix, roles);
|
defaultAccess.addMethodsPrefixes(prefix, roles);
|
||||||
} else {
|
} else {
|
||||||
defaultAccess.addMethods(method, roles);
|
defaultAccess.addMethods(method, roles);
|
||||||
|
@ -99,44 +115,54 @@ public class JMXAccessControlList {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToRoleAccess(String domain,String key, String method, String... roles) {
|
public void addToRoleAccess(String domain,String key, String method, String... roles) {
|
||||||
String id = getObjectID(domain, key);
|
TreeMap<String, Access> domainMap = new TreeMap<>(keyComparator);
|
||||||
Access access = domainAccess.get(id);
|
domainMap = domainAccess.putIfAbsent(domain, domainMap);
|
||||||
if (access == null) {
|
if (domainMap == null) {
|
||||||
access = new Access(domain);
|
domainMap = domainAccess.get(domain);
|
||||||
domainAccess.put(id, access);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method.equals("*")) {
|
String accessKey = normalizeKey(key);
|
||||||
|
Access access = domainMap.get(accessKey);
|
||||||
|
if (access == null) {
|
||||||
|
access = new Access(domain, accessKey);
|
||||||
|
domainMap.put(accessKey, access);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.equals(WILDCARD)) {
|
||||||
access.addCatchAll(roles);
|
access.addCatchAll(roles);
|
||||||
} else if (method.endsWith("*")) {
|
} else if (method.endsWith(WILDCARD)) {
|
||||||
String prefix = method.replace("*", "");
|
String prefix = method.replace(WILDCARD, "");
|
||||||
access.addMethodsPrefixes(prefix, roles);
|
access.addMethodsPrefixes(prefix, roles);
|
||||||
} else {
|
} else {
|
||||||
access.addMethods(method, roles);
|
access.addMethods(method, roles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getObjectID(String domain, String key) {
|
private String normalizeKey(final String key) {
|
||||||
String id = domain;
|
if (key == null) {
|
||||||
|
return "";
|
||||||
if (key != null) {
|
} else if (key.endsWith("\"")) {
|
||||||
String actualKey = key;
|
return key.replace("\"", "");
|
||||||
if (key.endsWith("\"")) {
|
|
||||||
actualKey = actualKey.replace("\"", "");
|
|
||||||
}
|
|
||||||
id += ":" + actualKey;
|
|
||||||
}
|
}
|
||||||
return id;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Access {
|
static class Access {
|
||||||
private final String domain;
|
private final String id;
|
||||||
|
private final String key;
|
||||||
|
private final Pattern keyPattern;
|
||||||
List<String> catchAllRoles = new ArrayList<>();
|
List<String> catchAllRoles = new ArrayList<>();
|
||||||
Map<String, List<String>> methodRoles = new HashMap<>();
|
Map<String, List<String>> methodRoles = new HashMap<>();
|
||||||
Map<String, List<String>> methodPrefixRoles = new LinkedHashMap<>();
|
Map<String, List<String>> methodPrefixRoles = new LinkedHashMap<>();
|
||||||
|
|
||||||
Access(String domain) {
|
Access(String id) {
|
||||||
this.domain = domain;
|
this(id, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
Access(String id, String key) {
|
||||||
|
this.id = id;
|
||||||
|
this.key = key;
|
||||||
|
this.keyPattern = Pattern.compile(key.replace(WILDCARD, ".*"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void addMethods(String prefix, String... roles) {
|
public synchronized void addMethods(String prefix, String... roles) {
|
||||||
|
@ -167,8 +193,16 @@ public class JMXAccessControlList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDomain() {
|
public String getId() {
|
||||||
return domain;
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pattern getKeyPattern() {
|
||||||
|
return keyPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getMatchingRolesForMethod(String methodName) {
|
public List<String> getMatchingRolesForMethod(String methodName) {
|
||||||
|
@ -184,22 +218,23 @@ public class JMXAccessControlList {
|
||||||
return catchAllRoles;
|
return catchAllRoles;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JMXAccessControlList createDefaultList() {
|
public static JMXAccessControlList createDefaultList() {
|
||||||
JMXAccessControlList accessControlList = new JMXAccessControlList();
|
JMXAccessControlList accessControlList = new JMXAccessControlList();
|
||||||
|
|
||||||
accessControlList.addToWhiteList("hawtio", "type=*");
|
accessControlList.addToWhiteList("hawtio", "type=*");
|
||||||
|
|
||||||
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null, "list*", "view", "update", "amq");
|
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null, "list*", "view", "update", "amq");
|
||||||
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null,"get*", "view", "update", "amq");
|
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null, "get*", "view", "update", "amq");
|
||||||
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null,"is*", "view", "update", "amq");
|
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null, "is*", "view", "update", "amq");
|
||||||
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null,"set*","update", "amq");
|
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null, "set*", "update", "amq");
|
||||||
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null,"*", "amq");
|
accessControlList.addToRoleAccess("org.apache.activemq.artemis", null, WILDCARD, "amq");
|
||||||
|
|
||||||
accessControlList.addToDefaultAccess("list*", "view", "update", "amq");
|
accessControlList.addToDefaultAccess("list*", "view", "update", "amq");
|
||||||
accessControlList.addToDefaultAccess("get*", "view", "update", "amq");
|
accessControlList.addToDefaultAccess("get*", "view", "update", "amq");
|
||||||
accessControlList.addToDefaultAccess("is*", "view", "update", "amq");
|
accessControlList.addToDefaultAccess("is*", "view", "update", "amq");
|
||||||
accessControlList.addToDefaultAccess("set*", "update", "amq");
|
accessControlList.addToDefaultAccess("set*", "update", "amq");
|
||||||
accessControlList.addToDefaultAccess("*", "amq");
|
accessControlList.addToDefaultAccess(WILDCARD, "amq");
|
||||||
|
|
||||||
return accessControlList;
|
return accessControlList;
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,16 @@ public class JMXAccessControlListTest {
|
||||||
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
|
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicRoleWithWildcardInKey() throws MalformedObjectNameException {
|
||||||
|
JMXAccessControlList controlList = new JMXAccessControlList();
|
||||||
|
controlList.addToRoleAccess("org.myDomain", "type=foo*","listSomething", "update");
|
||||||
|
controlList.addToRoleAccess("org.myDomain", "type=foo.bar*","listSomething", "admin");
|
||||||
|
controlList.addToRoleAccess("org.myDomain", null,"listSomething", "view");
|
||||||
|
List<String> roles = controlList.getRolesForObject(new ObjectName("org.myDomain:type=foo.bar.test"), "listSomething");
|
||||||
|
Assert.assertArrayEquals(roles.toArray(), new String[]{"admin"});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMutipleBasicRoles() throws MalformedObjectNameException {
|
public void testMutipleBasicRoles() throws MalformedObjectNameException {
|
||||||
JMXAccessControlList controlList = new JMXAccessControlList();
|
JMXAccessControlList controlList = new JMXAccessControlList();
|
||||||
|
|
|
@ -388,6 +388,19 @@ by configuring:
|
||||||
</match>
|
</match>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also use wildcards for the mBean properties so the following would
|
||||||
|
also match, allowing prefix match for the mBean properties.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<match domain="org.apache.activemq.artemis" key="queue=example*">
|
||||||
|
<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
|
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
|
controlled via the `set*`, `get*` and `is*`. The `*` access is the catch all
|
||||||
for everything other method that isn't specifically matched.
|
for everything other method that isn't specifically matched.
|
||||||
|
|
Loading…
Reference in New Issue