io.hawt.osgi.jmx.RBACDecorator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hawtio-osgi-jmx Show documentation
Show all versions of hawtio-osgi-jmx Show documentation
hawtio :: ${project.artifactId}
/**
* Copyright 2005-2016 Red Hat, Inc.
*
* Red Hat 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 io.hawt.osgi.jmx;
import java.io.UnsupportedEncodingException;
import java.lang.management.ManagementFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import org.apache.commons.codec.binary.Hex;
import org.apache.karaf.management.JMXSecurityMBean;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An MBean that may be used by hawtio:type=security,name=RBACRegistry
to decorate optimized
* jolokia list
operation with RBAC info.
*/
public class RBACDecorator implements RBACDecoratorMBean {
public static Logger LOG = LoggerFactory.getLogger(RBACDecorator.class);
private static final String JMX_ACL_PID_PREFIX = "jmx.acl";
private static final String JMX_OBJECTNAME_PROPERTY_WILDCARD = "_";
private static final Comparator WILDCARD_PID_COMPARATOR = new WildcardPidComparator();
private final BundleContext bundleContext;
private ObjectName objectName;
private MBeanServer mBeanServer;
/**
* Run with verify mode.
*/
private boolean verify = false;
public RBACDecorator(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
void init() throws Exception {
if (objectName == null) {
objectName = new ObjectName("hawtio:type=security,area=jolokia,name=RBACDecorator");
}
if (mBeanServer == null) {
mBeanServer = ManagementFactory.getPlatformMBeanServer();
}
mBeanServer.registerMBean(this, objectName);
}
void destroy() throws Exception {
if (objectName != null && mBeanServer != null) {
mBeanServer.unregisterMBean(objectName);
}
}
/**
* If we have access to {@link ConfigurationAdmin}, we can add RBAC information
* @param result
*/
@Override
@SuppressWarnings("unchecked")
public void decorate(Map result) throws Exception {
try {
ServiceReference cmRef = bundleContext.getServiceReference(ConfigurationAdmin.class);
ServiceReference jmxSecRef = bundleContext.getServiceReference(JMXSecurityMBean.class);
if (cmRef == null || jmxSecRef == null) {
return;
}
ConfigurationAdmin configAdmin = bundleContext.getService(cmRef);
JMXSecurityMBean jmxSec = bundleContext.getService(jmxSecRef);
if (configAdmin == null || jmxSec == null) {
return;
}
// 1. each pair of MBean/operation has to be marked with RBAC flag (can/can't invoke)
// 2. the information is provided by org.apache.karaf.management.JMXSecurityMBean.canInvoke(java.util.Map)
// 3. we'll peek into available configadmin jmx.acl* configs, to see which MBeans/operations have to
// be examined and which will produce same results
// 4. only then we'll prepare Map as parameter for canInvoke()
Configuration[] configurations = configAdmin.listConfigurations("(service.pid=jmx.acl*)");
if (configurations == null) {
return;
}
List allJmxAclPids = Arrays.stream(configurations)
.map(Configuration::getPid)
.collect(Collectors.toCollection(LinkedList::new));
if (allJmxAclPids.isEmpty()) {
return;
}
Map> domains = (Map>) result.get("domains");
// cache contains MBeanInfos for different MBeans/ObjectNames
Map> cache = (Map>) result.get("cache");
// new cache will contain MBeanInfos + RBAC info
Map> rbacCache = new HashMap<>();
// the fact that some MBeans share JSON MBeanInfo doesn't mean that they can share RBAC info
// - each MBean's name may have RBAC information configured in different PIDs.
// when iterating through all repeating MBeans that share MBeanInfo (that doesn't have RBAC info
// yet), we have to decide if it'll use shared info after RBAC check or will switch to dedicated
// info. we have to be careful not to end with most MBeans *not* sharing MBeanInfo (in case if
// somehow the shared info will be "special case" from RBAC point of view)
Map> queryForMBeans = new HashMap<>();
Map> queryForMBeanOperations = new HashMap<>();
constructQueries(allJmxAclPids, domains, cache, rbacCache, queryForMBeans, queryForMBeanOperations);
// RBAC per MBeans (can invoke *any* operation or attribute?)
doQueryForMBeans(jmxSec, domains, rbacCache, queryForMBeans);
// RBAC per { MBean,operation } (can invoke status for each operation)
doQueryForMBeanOperations(jmxSec, domains, rbacCache, queryForMBeanOperations);
result.remove("cache");
result.put("cache", rbacCache);
if (verify) {
verify(result);
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
// simply do not decorate
}
}
@SuppressWarnings("unchecked")
private void constructQueries(List allJmxAclPids, Map> domains,
Map> cache, Map> rbacCache,
Map> queryForMBeans, Map> queryForMBeanOperations) throws MalformedObjectNameException, NoSuchAlgorithmException, UnsupportedEncodingException {
for (String domain : domains.keySet()) {
Map domainMBeansCheck = new HashMap<>(domains.get(domain)); // shallow copy is ok for a domain
Map domainMBeans = domains.get(domain);
for (String name : domainMBeansCheck.keySet()) {
Object mBeanInfo = domainMBeansCheck.get(name);
String fullName = domain + ":" + name;
ObjectName oName = new ObjectName(fullName);
if (mBeanInfo instanceof Map) {
// not shared JSONified MBeanInfo
prepareKarafRbacInvocations(fullName, (Map) mBeanInfo,
queryForMBeans, queryForMBeanOperations);
} else {
// shared JSONified MBeanInfo
// shard mbeanNames sharing MBeanInfo by the hierarchy of jmx.acl* PIDs used to
// check RBAC info
String key = (String) mBeanInfo;
String pidListKey = pidListKey(allJmxAclPids, oName);
if (!rbacCache.containsKey(key + ":" + pidListKey)) {
// deep copy - "op" / "opByString" may differ per MBeanInfo with different pid list key
Map sharedMBeanAndRbacInfo = deepCopy(cache.get(key));
rbacCache.put(key + ":" + pidListKey, sharedMBeanAndRbacInfo);
// we'll be checking RBAC only for single (first) MBean having this pidListKey
prepareKarafRbacInvocations(fullName, sharedMBeanAndRbacInfo,
queryForMBeans, queryForMBeanOperations);
}
// switch key from shared MBeanInfo-only to shared MBean+RbacInfo
domainMBeans.put(name, key + ":" + pidListKey);
}
}
}
}
@SuppressWarnings("unchecked")
static Map deepCopy(Map mBeanInfo) {
// it's not really "deep" copy; it just copies deep enough
Map copy = new HashMap<>(mBeanInfo);
// copy "op" deep enough
Map ops = (Map) mBeanInfo.get("op");
Map newOps = new HashMap<>(ops.size());
for (String name : ops.keySet()) {
Object op = ops.get(name);
Object newOp;
if (op instanceof List) { // for method overloading
List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy