/* * 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.impl.console.osgi.secured; import java.security.AccessControlContext; import java.security.AccessController; import java.security.Principal; import java.util.ArrayList; import java.util.Dictionary; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.security.auth.Subject; import org.apache.felix.gogo.runtime.CommandNotFoundException; import org.apache.felix.service.command.Function; import org.apache.felix.service.threadio.ThreadIO; import org.apache.karaf.jaas.boot.principal.RolePrincipal; import org.apache.karaf.service.guard.tools.ACLConfigurationParser; import org.apache.karaf.shell.api.console.Command; import org.apache.karaf.shell.api.console.Session; import org.apache.karaf.shell.impl.console.SessionFactoryImpl; import org.apache.karaf.util.tracker.SingleServiceTracker; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceRegistration; 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.slf4j.Logger; import org.slf4j.LoggerFactory; public class SecuredSessionFactoryImpl extends SessionFactoryImpl implements ConfigurationListener { private static final String PROXY_COMMAND_ACL_PID_PREFIX = "org.apache.karaf.command.acl."; private static final String CONFIGURATION_FILTER = "(" + Constants.SERVICE_PID + "=" + PROXY_COMMAND_ACL_PID_PREFIX + "*)"; private static final Logger LOGGER = LoggerFactory.getLogger(SecuredSessionFactoryImpl.class); private BundleContext bundleContext; private Map<String, Dictionary<String, Object>> scopes = new HashMap<String, Dictionary<String, Object>>(); private SingleServiceTracker<ConfigurationAdmin> configAdminTracker; private ServiceRegistration<ConfigurationListener> registration; public SecuredSessionFactoryImpl(BundleContext bundleContext, ThreadIO threadIO) throws InvalidSyntaxException { super(threadIO); this.bundleContext = bundleContext; this.registration = bundleContext.registerService(ConfigurationListener.class, this, null); this.configAdminTracker = new SingleServiceTracker<>(bundleContext, ConfigurationAdmin.class, this::update); this.configAdminTracker.open(); } public void stop() { this.registration.unregister(); this.configAdminTracker.close(); super.stop(); } @Override protected Function wrap(Command command) { return new SecuredCommand(this, command); } @Override protected boolean isVisible(Object service) { if (service instanceof Command) { return isVisible((Command) service); } else { return super.isVisible(service); } } protected boolean isVisible(Command command) { Dictionary<String, Object> config = getScopeConfig(command.getScope()); if (config != null) { List<String> roles = new ArrayList<String>(); ACLConfigurationParser.getRolesForInvocation(command.getName(), null, null, config, roles); if (roles.isEmpty()) { return true; } else { for (String role : roles) { if (currentUserHasRole(role)) { return true; } } return false; } } return true; } void checkSecurity(SecuredCommand command, Session session, List<Object> arguments) { Dictionary<String, Object> config = getScopeConfig(command.getScope()); if (config != null) { if (!isVisible(command)) { throw new CommandNotFoundException(command.getScope() + ":" + command.getName()); } List<String> roles = new ArrayList<String>(); ACLConfigurationParser.Specificity s = ACLConfigurationParser.getRolesForInvocation(command.getName(), new Object[] { arguments.toString() }, null, config, roles); if (s == ACLConfigurationParser.Specificity.NO_MATCH) { return; } for (String role : roles) { if (currentUserHasRole(role)) { return; } } throw new SecurityException("Insufficient credentials."); } } 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 = RolePrincipal.class.getName(); 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; } @Override public void configurationEvent(ConfigurationEvent event) { if (!event.getPid().startsWith(PROXY_COMMAND_ACL_PID_PREFIX)) return; try { switch (event.getType()) { case ConfigurationEvent.CM_DELETED: removeScopeConfig(event.getPid().substring(PROXY_COMMAND_ACL_PID_PREFIX.length())); break; case ConfigurationEvent.CM_UPDATED: ConfigurationAdmin configAdmin = bundleContext.getService(event.getReference()); try { addScopeConfig(configAdmin.getConfiguration(event.getPid(), null)); } finally { bundleContext.ungetService(event.getReference()); } break; } } catch (Exception e) { LOGGER.error("Problem processing Configuration Event {}", event, e); } } private void addScopeConfig(Configuration config) { if (!config.getPid().startsWith(PROXY_COMMAND_ACL_PID_PREFIX)) { // not a command scope configuration file return; } String scope = config.getPid().substring(PROXY_COMMAND_ACL_PID_PREFIX.length()); if (scope.indexOf('.') >= 0) { // scopes don't contains dots, not a command scope return; } scope = scope.trim(); synchronized (scopes) { scopes.put(scope, config.getProperties()); } } private void removeScopeConfig(String scope) { synchronized (scopes) { scopes.remove(scope); } } private Dictionary<String, Object> getScopeConfig(String scope) { synchronized (scopes) { return scopes.get(scope); } } protected void update(ConfigurationAdmin prev, ConfigurationAdmin configAdmin) { try { Configuration[] configs = configAdmin.listConfigurations(CONFIGURATION_FILTER); if (configs != null) { for (Configuration config : configs) { addScopeConfig(config); } } } catch (Exception e) { // Ignore, should never happen } } }