/* * 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.karaf.shell.security.impl; import org.apache.felix.service.command.CommandProcessor; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.cm.ConfigurationEvent; import org.osgi.service.cm.ConfigurationListener; import org.osgi.service.packageadmin.PackageAdmin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.*; import java.util.Map.Entry; @Deprecated public class SecuredCommandConfigTransformer implements ConfigurationListener { static final String PROXY_COMMAND_ACL_PID_PREFIX = "org.apache.karaf.command.acl."; static final String PROXY_SERVICE_ACL_PID_PREFIX = "org.apache.karaf.service.acl.command."; private static final Logger LOGGER = LoggerFactory.getLogger(SecuredCommandConfigTransformer.class); private static final String CONFIGURATION_FILTER = "(" + Constants.SERVICE_PID + "=" + PROXY_COMMAND_ACL_PID_PREFIX + "*)"; private static final String ACL_SCOPE_BUNDLE_MAP = "org.apache.karaf.command.acl.scope_bundle"; private ConfigurationAdmin configAdmin; public void setConfigAdmin(ConfigurationAdmin configAdmin) { this.configAdmin = configAdmin; } public void init() throws Exception { Configuration[] configs = configAdmin.listConfigurations(CONFIGURATION_FILTER); if (configs == null) return; for (Configuration config : configs) { generateServiceGuardConfig(config); } } void generateServiceGuardConfig(Configuration config) throws IOException { if (!config.getPid().startsWith(PROXY_COMMAND_ACL_PID_PREFIX)) { // not a command scope configuration file return; } String scopeName = config.getPid().substring(PROXY_COMMAND_ACL_PID_PREFIX.length()); if (scopeName.indexOf('.') >= 0) { // scopes don't contains dots, not a command scope return; } scopeName = scopeName.trim(); Dictionary<String, Object> configProps = config.getProperties(); Map<String, Dictionary<String, Object>> configMaps = new HashMap<String, Dictionary<String, Object>>(); for (Enumeration<String> e = configProps.keys(); e.hasMoreElements(); ) { String key = e.nextElement(); String bareCommand = key; String arguments = ""; int idx = bareCommand.indexOf('['); if (idx >= 0) { arguments = convertArgs(bareCommand.substring(idx)); bareCommand = bareCommand.substring(0, idx); } if (bareCommand.indexOf('.') >= 0) { // not a command continue; } bareCommand = bareCommand.trim(); String pid = PROXY_SERVICE_ACL_PID_PREFIX + scopeName + "." + bareCommand; Dictionary<String, Object> map; if (!configMaps.containsKey(pid)) { map = new Hashtable<String, Object>(); map.put("service.guard", "(&(" + CommandProcessor.COMMAND_SCOPE + "=" + scopeName + ")(" + CommandProcessor.COMMAND_FUNCTION + "=" + bareCommand + "))"); configMaps.put(pid, map); } else { map = configMaps.get(pid); } // put rules on the map twice, once for commands that 'execute' (implement Function) and // once for commands that are invoked directly Object roleString = configProps.get(key); map.put("execute" + arguments, roleString); map.put(key, roleString); map.put("*", "*"); // any other method may be invoked by anyone } LOGGER.info("Generating command ACL config {} into service ACL configs {}", config.getPid(), configMaps.keySet()); // update config admin with the generated configuration for (Map.Entry<String, Dictionary<String, Object>> entry : configMaps.entrySet()) { Configuration genConfig = configAdmin.getConfiguration(entry.getKey(), null); genConfig.update(entry.getValue()); } } private String convertArgs(String commandACLArgs) { if (!commandACLArgs.startsWith("[/")) { throw new IllegalStateException("Badly formatted argument match: " + commandACLArgs + " Should start with '[/'"); } if (!commandACLArgs.endsWith("/]")) { throw new IllegalStateException("Badly formatted argument match: " + commandACLArgs + " Should end with '/]'"); } StringBuilder sb = new StringBuilder(); sb.append("[/.*/,"); // add a wildcard argument since the Function execute method has the arguments as second arg sb.append(commandACLArgs.substring(1)); return sb.toString(); } void deleteServiceGuardConfig(String originatingPid, String scope) throws IOException, InvalidSyntaxException { if (scope.contains(".")) // This is not a command scope as that should be a single word without any further dots return; // Delete all the generated configurations for this scope Configuration[] configs = configAdmin.listConfigurations("(service.pid=" + PROXY_SERVICE_ACL_PID_PREFIX + scope + ".*)"); if (configs == null) return; LOGGER.info("Config ACL deleted: {}. Deleting generated service ACL configs {}", originatingPid, configs); for (Configuration config : configs) { config.delete(); } } @Override public void configurationEvent(ConfigurationEvent event) { if (!event.getPid().startsWith(PROXY_COMMAND_ACL_PID_PREFIX)) return; try { switch (event.getType()) { case ConfigurationEvent.CM_DELETED: deleteServiceGuardConfig(event.getPid(), event.getPid().substring(PROXY_COMMAND_ACL_PID_PREFIX.length())); break; case ConfigurationEvent.CM_UPDATED: Configuration config = configAdmin.getConfiguration(event.getPid(), null); generateServiceGuardConfig(config); refreshTheAffectedShellCommandBundle(event, config); break; } } catch (Exception e) { LOGGER.error("Problem processing Configuration Event {}", event, e); } } private void refreshTheAffectedShellCommandBundle(ConfigurationEvent event, Configuration config) { if (!config.getPid().startsWith(PROXY_COMMAND_ACL_PID_PREFIX)) { // not a command scope configuration file return; } String filter = ""; String scopeName = config.getPid().substring(PROXY_COMMAND_ACL_PID_PREFIX.length()); if (scopeName.indexOf('.') >= 0) { // scopes don't contains dots, not a command scope return; } scopeName = scopeName.trim(); for (Entry<String, String> entry : loadScopeBundleMaps().entrySet()) { if (entry.getKey().equals(scopeName)) { filter = "(" + "osgi.blueprint.container.symbolicname" + "=" + entry.getValue() + ")"; break; } } if (filter.length() == 0) { return; } BundleContext bundleContext = event.getReference().getBundle().getBundleContext(); try { ServiceReference<?>[] sr = bundleContext.getServiceReferences("org.osgi.service.blueprint.container.BlueprintContainer", filter); if (sr == null) { LOGGER.error("can't find the command bundle for scope " + scopeName); return; } LOGGER.debug("the refreshed bundle is " + sr[0].getBundle().getSymbolicName()); ServiceReference ref = bundleContext.getServiceReference(PackageAdmin.class.getName()); if (ref == null) { LOGGER.error("PackageAdmin service is unavailable."); return; } try { PackageAdmin pa = (PackageAdmin) bundleContext.getService(ref); if (pa == null) { LOGGER.error("PackageAdmin service is unavailable."); return; } pa.refreshPackages(new Bundle[]{sr[0].getBundle()}); } finally { bundleContext.ungetService(ref); } } catch (InvalidSyntaxException ex) { LOGGER.error("Problem refresh the affected shell command bundle", ex); } } private Map<String, String> loadScopeBundleMaps() { Map<String, String> scopeBundleMaps = new HashMap<String, String>(); try { for (Configuration config : configAdmin.listConfigurations("(service.pid=" + ACL_SCOPE_BUNDLE_MAP + ")")) { Enumeration<String> keys = config.getProperties().keys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); scopeBundleMaps.put(key, (String)config.getProperties().get(key)); } } } catch (Exception ex) { LOGGER.error("Problem load the scope bundle map", ex); } return scopeBundleMaps; } }