/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.test.integration.security.common; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ALLOW_RESOURCE_SERVICE_RESTART; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPOSITE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLBACK_ON_RUNTIME_FAILURE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM; import static org.jboss.as.security.Constants.AUTH_MODULE; import static org.jboss.as.security.Constants.CLASSIC; import static org.jboss.as.security.Constants.FLAG; import static org.jboss.as.security.Constants.JSSE; import static org.jboss.as.security.Constants.KEYSTORE; import static org.jboss.as.security.Constants.LOGIN_MODULE; import static org.jboss.as.security.Constants.MODULE_OPTIONS; import static org.jboss.as.security.Constants.PASSWORD; import static org.jboss.as.security.Constants.SECURITY_DOMAIN; import static org.jboss.as.security.Constants.TRUSTSTORE; import static org.jboss.as.security.Constants.TYPE; import static org.jboss.as.security.Constants.URL; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.jboss.as.arquillian.api.ServerSetupTask; import org.jboss.as.arquillian.container.ManagementClient; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.test.integration.security.common.config.AuthnModule; import org.jboss.as.test.integration.security.common.config.JSSE; import org.jboss.as.test.integration.security.common.config.JaspiAuthn; import org.jboss.as.test.integration.security.common.config.LoginModuleStack; import org.jboss.as.test.integration.security.common.config.SecureStore; import org.jboss.as.test.integration.security.common.config.SecurityDomain; import org.jboss.as.test.integration.security.common.config.SecurityModule; import org.jboss.as.test.shared.ServerReload; import org.jboss.dmr.ModelNode; import org.jboss.logging.Logger; /** * {@link ServerSetupTask} instance for security domain setup. It supports JSSE configuration, JASPI authentication * configuration and stacks of login-modules (classic authentication), policy-modules and (role-)mapping-modules. * * @author Josef Cacek */ public abstract class AbstractSecurityDomainsServerSetupTask implements ServerSetupTask { private static final Logger LOGGER = Logger.getLogger(AbstractSecurityDomainsServerSetupTask.class); /** * The type attribute value of mapping-modules used for role assignment. */ private static final String ROLE = "role"; /** * The SUBSYSTEM_SECURITY */ private static final String SUBSYSTEM_SECURITY = "security"; protected ManagementClient managementClient; private SecurityDomain[] securityDomains; // Public methods -------------------------------------------------------- /** * Adds a security domain represented by this class to the AS configuration. * * @param managementClient * @param containerId * @throws Exception * @see org.jboss.as.arquillian.api.ServerSetupTask#setup(org.jboss.as.arquillian.container.ManagementClient, * java.lang.String) */ public final void setup(final ManagementClient managementClient, String containerId) throws Exception { this.managementClient = managementClient; securityDomains = getSecurityDomains(); if (securityDomains == null || securityDomains.length == 0) { LOGGER.warn("Empty security domain configuration."); return; } // TODO remove this once security domains expose their own capability // Currently subsystem=security-domain exposes one, but the individual domains don't // which with WFCORE-1106 has the effect that any individual sec-domain op that puts // the server in reload-required means all ops for any sec-domain won't execute Stage.RUNTIME // So, for now we preemptively reload if needed ServerReload.BeforeSetupTask.INSTANCE.setup(managementClient, containerId); final List<ModelNode> updates = new LinkedList<ModelNode>(); for (final SecurityDomain securityDomain : securityDomains) { final String securityDomainName = securityDomain.getName(); if (LOGGER.isInfoEnabled()) { LOGGER.trace("Adding security domain " + securityDomainName); } final ModelNode compositeOp = new ModelNode(); compositeOp.get(OP).set(COMPOSITE); compositeOp.get(OP_ADDR).setEmptyList(); ModelNode steps = compositeOp.get(STEPS); PathAddress opAddr = PathAddress.pathAddress() .append(SUBSYSTEM, SUBSYSTEM_SECURITY) .append(SECURITY_DOMAIN, securityDomainName); ModelNode op = Util.createAddOperation(opAddr); if (StringUtils.isNotEmpty(securityDomain.getCacheType())) { op.get(org.jboss.as.test.integration.security.common.Constants.CACHE_TYPE).set(securityDomain.getCacheType()); } steps.add(op); //only one can occur - authenticationType or authenticationJaspiType final boolean authNodeAdded = createSecurityModelNode(org.jboss.as.test.integration.security.common.Constants.AUTHENTICATION, LOGIN_MODULE, FLAG, org.jboss.as.test.integration.security.common.Constants.REQUIRED, securityDomain.getLoginModules(), securityDomainName, steps); if (!authNodeAdded) { final List<ModelNode> jaspiAuthnNodes = createJaspiAuthnNodes(securityDomain.getJaspiAuthn(), securityDomain.getName()); if (jaspiAuthnNodes != null) { for (ModelNode node : jaspiAuthnNodes) { steps.add(node); } } } createSecurityModelNode(org.jboss.as.test.integration.security.common.Constants.AUTHORIZATION, org.jboss.as.test.integration.security.common.Constants.POLICY_MODULE, FLAG, org.jboss.as.test.integration.security.common.Constants.REQUIRED, securityDomain.getAuthorizationModules(), securityDomainName, steps); createSecurityModelNode(org.jboss.as.test.integration.security.common.Constants.MAPPING, org.jboss.as.test.integration.security.common.Constants.MAPPING_MODULE, TYPE, ROLE, securityDomain.getMappingModules(), securityDomainName, steps); final ModelNode jsseNode = createJSSENode(securityDomain.getJsse(), securityDomain.getName()); if (jsseNode != null) { steps.add(jsseNode); } updates.add(compositeOp); } CoreUtils.applyUpdates(updates, managementClient.getControllerClient()); } /** * Removes the security domain from the AS configuration. * * @param managementClient * @param containerId * @see org.jboss.as.test.integration.security.common.AbstractSecurityDomainSetup#tearDown(org.jboss.as.arquillian.container.ManagementClient, * java.lang.String) */ public final void tearDown(ManagementClient managementClient, String containerId) throws Exception { if (securityDomains == null || securityDomains.length == 0) { LOGGER.warn("Empty security domain configuration."); return; } final List<ModelNode> updates = new ArrayList<ModelNode>(); for (final SecurityDomain securityDomain : securityDomains) { final String domainName = securityDomain.getName(); if (LOGGER.isInfoEnabled()) { LOGGER.trace("Removing security domain " + domainName); } final ModelNode op = new ModelNode(); op.get(OP).set(REMOVE); op.get(OP_ADDR).add(SUBSYSTEM, "security"); op.get(OP_ADDR).add(SECURITY_DOMAIN, domainName); // Don't rollback when the AS detects the war needs the module op.get(OPERATION_HEADERS, ROLLBACK_ON_RUNTIME_FAILURE).set(false); op.get(OPERATION_HEADERS, ALLOW_RESOURCE_SERVICE_RESTART).set(true); updates.add(op); } CoreUtils.applyUpdates(updates, managementClient.getControllerClient()); this.managementClient = null; } // Protected methods ----------------------------------------------------- /** * Returns configuration for creating security domains. * * @return array of SecurityDomain */ protected abstract SecurityDomain[] getSecurityDomains() throws Exception; // Private methods ------------------------------------------------------- /** * Creates authenticaton=>jaspi node and its child nodes. * * @param securityConfigurations * @return */ private List<ModelNode> createJaspiAuthnNodes(JaspiAuthn securityConfigurations, String domainName) { if (securityConfigurations == null) { LOGGER.trace("No security configuration for JASPI module."); return null; } if (securityConfigurations.getAuthnModules() == null || securityConfigurations.getAuthnModules().length == 0 || securityConfigurations.getLoginModuleStacks() == null || securityConfigurations.getLoginModuleStacks().length == 0) { throw new IllegalArgumentException("Missing mandatory part of JASPI configuration in the security domain."); } final List<ModelNode> steps = new ArrayList<ModelNode>(); PathAddress domainAddress = PathAddress.pathAddress() .append(SUBSYSTEM, SUBSYSTEM_SECURITY) .append(SECURITY_DOMAIN, domainName); PathAddress jaspiAddress = domainAddress.append(org.jboss.as.test.integration.security.common.Constants.AUTHENTICATION, org.jboss.as.test.integration.security.common.Constants.JASPI); steps.add(Util.createAddOperation(jaspiAddress)); for (final AuthnModule config : securityConfigurations.getAuthnModules()) { LOGGER.trace("Adding auth-module: " + config); final ModelNode securityModuleNode = Util.createAddOperation(jaspiAddress.append(AUTH_MODULE,config.getName())); steps.add(securityModuleNode); securityModuleNode.get(ModelDescriptionConstants.CODE).set(config.getName()); if (config.getFlag() != null) { securityModuleNode.get(FLAG).set(config.getFlag()); } if (config.getModule() != null) { securityModuleNode.get(org.jboss.as.test.integration.security.common.Constants.MODULE).set(config.getModule()); } if (config.getLoginModuleStackRef() != null) { securityModuleNode.get(org.jboss.as.test.integration.security.common.Constants.LOGIN_MODULE_STACK_REF).set(config.getLoginModuleStackRef()); } Map<String, String> configOptions = config.getOptions(); if (configOptions == null) { LOGGER.trace("No module options provided."); configOptions = Collections.emptyMap(); } final ModelNode moduleOptionsNode = securityModuleNode.get(MODULE_OPTIONS); for (final Map.Entry<String, String> entry : configOptions.entrySet()) { final String optionName = entry.getKey(); final String optionValue = entry.getValue(); moduleOptionsNode.add(optionName, optionValue); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Adding module option [" + optionName + "=" + optionValue + "]"); } } } //Unable to use securityComponentNode.get(OPERATION_HEADERS).get(ALLOW_RESOURCE_SERVICE_RESTART).set(true) because the login-module-stack is empty for (final LoginModuleStack lmStack : securityConfigurations.getLoginModuleStacks()) { PathAddress lmStackAddress = jaspiAddress.append(org.jboss.as.test.integration.security.common.Constants.LOGIN_MODULE_STACK, lmStack.getName()); steps.add(Util.createAddOperation(lmStackAddress)); for (final SecurityModule config : lmStack.getLoginModules()) { final String code = config.getName(); final ModelNode securityModuleNode = Util.createAddOperation(lmStackAddress.append(LOGIN_MODULE, code)); final String flag = StringUtils.defaultIfEmpty(config.getFlag(), org.jboss.as.test.integration.security.common.Constants.REQUIRED); securityModuleNode.get(ModelDescriptionConstants.CODE).set(code); securityModuleNode.get(FLAG).set(flag); if (LOGGER.isInfoEnabled()) { LOGGER.trace("Adding JASPI login module stack [code=" + code + ", flag=" + flag + "]"); } Map<String, String> configOptions = config.getOptions(); if (configOptions == null) { LOGGER.trace("No module options provided."); configOptions = Collections.emptyMap(); } final ModelNode moduleOptionsNode = securityModuleNode.get(MODULE_OPTIONS); for (final Map.Entry<String, String> entry : configOptions.entrySet()) { final String optionName = entry.getKey(); final String optionValue = entry.getValue(); moduleOptionsNode.add(optionName, optionValue); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Adding module option [" + optionName + "=" + optionValue + "]"); } } securityModuleNode.get(OPERATION_HEADERS).get(ALLOW_RESOURCE_SERVICE_RESTART).set(true); steps.add(securityModuleNode); } } return steps; } /** * Creates a {@link ModelNode} with the security component configuration. If the securityConfigurations array is empty or * null, then null is returned. * * @param securityComponent name of security component (e.g. {@link org.jboss.as.test.integration.security.common.Constants#AUTHORIZATION}) * @param subnodeName name of the security component subnode, which holds module configurations (e.g. * {@link org.jboss.as.test.integration.security.common.Constants#POLICY_MODULES}) * @param flagAttributeName name of attribute to which the value of {@link SecurityModule#getFlag()} is set * @param flagDefaultValue default value for flagAttributeName attr. * @param securityModules configurations * @return ModelNode instance or null */ private boolean createSecurityModelNode(String securityComponent, String subnodeName, String flagAttributeName, String flagDefaultValue, final SecurityModule[] securityModules, String domainName, ModelNode operations) { if (securityModules == null || securityModules.length == 0) { if (LOGGER.isInfoEnabled()) { LOGGER.trace("No security configuration for " + securityComponent + " module."); } return false; } PathAddress address = PathAddress.pathAddress() .append(SUBSYSTEM, SUBSYSTEM_SECURITY) .append(SECURITY_DOMAIN, domainName) .append(securityComponent, CLASSIC); operations.add(Util.createAddOperation(address)); for (final SecurityModule config : securityModules) { final String code = config.getName(); final ModelNode securityModuleNode = Util.createAddOperation(address.append(subnodeName, code)); final String flag = StringUtils.defaultIfEmpty(config.getFlag(), flagDefaultValue); securityModuleNode.get(ModelDescriptionConstants.CODE).set(code); securityModuleNode.get(flagAttributeName).set(flag); Map<String, String> configOptions = config.getOptions(); if (configOptions == null) { LOGGER.trace("No module options provided."); configOptions = Collections.emptyMap(); } if (LOGGER.isInfoEnabled()) { LOGGER.trace("Adding " + securityComponent + " module [code=" + code + ", " + flagAttributeName + "=" + flag + ", options = " + configOptions + "]"); } final ModelNode moduleOptionsNode = securityModuleNode.get(MODULE_OPTIONS); for (final Map.Entry<String, String> entry : configOptions.entrySet()) { final String optionName = entry.getKey(); final String optionValue = entry.getValue(); moduleOptionsNode.add(optionName, optionValue); } securityModuleNode.get(OPERATION_HEADERS).get(ALLOW_RESOURCE_SERVICE_RESTART).set(true); operations.add(securityModuleNode); } return true; } /** * Creates a {@link ModelNode} with configuration of the JSSE part of security domain. * * @param jsse * @param domainName * @return */ private ModelNode createJSSENode(final JSSE jsse, String domainName) { if (jsse == null) { if (LOGGER.isInfoEnabled()) { LOGGER.trace("No security configuration for JSSE module."); } return null; } final ModelNode securityComponentNode = new ModelNode(); securityComponentNode.get(OP).set(ADD); securityComponentNode.get(OP_ADDR).add(SUBSYSTEM, SUBSYSTEM_SECURITY); securityComponentNode.get(OP_ADDR).add(SECURITY_DOMAIN, domainName); securityComponentNode.get(OP_ADDR).add(JSSE, CLASSIC); addSecureStore(jsse.getTrustStore(), TRUSTSTORE, securityComponentNode); addSecureStore(jsse.getKeyStore(), KEYSTORE, securityComponentNode); securityComponentNode.get(OPERATION_HEADERS).get(ALLOW_RESOURCE_SERVICE_RESTART).set(true); return securityComponentNode; } /** * Adds given secureStore to a JSSE configuration represented by given ModelNode. * * @param secureStore * @param storeName * @param jsseNode */ private void addSecureStore(SecureStore secureStore, String storeName, ModelNode jsseNode) { if (secureStore == null) { return; } if (secureStore.getUrl() != null) { jsseNode.get(storeName, URL).set(secureStore.getUrl().toExternalForm()); } if (secureStore.getPassword() != null) { jsseNode.get(storeName, PASSWORD).set(secureStore.getPassword()); } if (secureStore.getType() != null) { jsseNode.get(storeName, TYPE).set(secureStore.getType()); } } }