/* * JBoss, Home of Professional Open Source. * Copyright 2015, 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.web; import static io.undertow.util.Headers.X_FORWARDED_FOR_STRING; import static io.undertow.util.Headers.X_FORWARDED_PROTO_STRING; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.ProcessType; import org.jboss.as.controller.RunningMode; import org.jboss.as.controller.SimpleMapAttributeDefinition; import org.jboss.as.controller.SimpleOperationDefinitionBuilder; import org.jboss.as.controller.StringListAttributeDefinition; import org.jboss.as.controller.access.management.SensitiveTargetAccessConstraintDefinition; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.descriptions.ResourceDescriptionResolver; import org.jboss.as.controller.operations.MultistepUtil; import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.controller.registry.Resource; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.dmr.ValueExpression; import org.wildfly.extension.io.IOExtension; import org.wildfly.extension.undertow.Constants; import org.wildfly.extension.undertow.UndertowExtension; import org.wildfly.extension.undertow.filters.CustomFilterDefinition; import org.wildfly.extension.undertow.filters.ExpressionFilterDefinition; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import static org.jboss.as.controller.OperationContext.Stage.MODEL; import static org.jboss.as.controller.PathAddress.pathAddress; import static org.jboss.as.controller.PathElement.pathElement; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.AUTHENTICATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CORE_SERVICE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXTENSION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MANAGEMENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MODULE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PATH; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROTOCOL; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELATIVE_TO; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SECURITY_REALM; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVER_IDENTITY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SSL; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.TRUSTSTORE; import static org.jboss.as.controller.operations.common.Util.createAddOperation; import static org.jboss.as.controller.operations.common.Util.createOperation; import static org.jboss.as.controller.operations.common.Util.createRemoveOperation; import static org.wildfly.extension.undertow.UndertowExtension.PATH_FILTERS; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Operation to migrate from the legacy web subsystem to the new undertow subsystem. * <p/> * This operation must be performed when the server is in admin-only mode. * Internally, the operation: * <p/> * <ul> * <li>query the description of all the web subsystem by invoking the :describe operation. * This returns a list of :add operations for each web resources.</li> * <li>:add the new org.widlfy.extension.undertow extension</li> * <li>for each web resources, transform the :add operations to add the * corresponding resource to the new undertow subsystem. * In this step, changes to the resources model are taken into account</li> * <li>:remove the web subsystem</li> * </ul> * <p/> * The companion <code>:describe-migration</code> operation will return a list of all the actual operations that would be * performed during the invocation of the <code>:migrate</code> operation. * <p/> * Note that all new operation addresses are generated for standalone mode. If this is a domain mode server * then the addresses are fixed after they have been generated * * @author <a href="http://jmesnil.net/">Jeff Mesnil</a> (c) 2015 Red Hat inc. * @author Stuart Douglas */ public class WebMigrateOperation implements OperationStepHandler { private static final String UNDERTOW_EXTENSION = "org.wildfly.extension.undertow"; private static final String IO_EXTENSION = "org.wildfly.extension.io"; private static final String REALM_NAME = "jbossweb-migration-security-realm"; private static final OperationStepHandler DESCRIBE_MIGRATION_INSTANCE = new WebMigrateOperation(true); private static final OperationStepHandler MIGRATE_INSTANCE = new WebMigrateOperation(false); public static final PathElement DEFAULT_SERVER_PATH = pathElement(Constants.SERVER, "default-server"); public static final PathAddress EXTENSION_ADDRESS = pathAddress(pathElement(EXTENSION, "org.jboss.as.web")); private static final PathAddress VALVE_ACCESS_LOG_ADDRESS = pathAddress(UndertowExtension.SUBSYSTEM_PATH, DEFAULT_SERVER_PATH, pathElement(Constants.HOST), UndertowExtension.PATH_ACCESS_LOG); public static final String MIGRATE = "migrate"; public static final String MIGRATION_WARNINGS = "migration-warnings"; public static final String MIGRATION_ERROR = "migration-error"; public static final String MIGRATION_OPERATIONS = "migration-operations"; public static final String DESCRIBE_MIGRATION = "describe-migration"; private static final Pattern ACCESS_LOG_PATTERN = Pattern.compile("%\\{(.*?)\\}(\\w)"); public static final StringListAttributeDefinition MIGRATION_WARNINGS_ATTR = new StringListAttributeDefinition.Builder(MIGRATION_WARNINGS) .setRequired(false) .build(); public static final SimpleMapAttributeDefinition MIGRATION_ERROR_ATTR = new SimpleMapAttributeDefinition.Builder(MIGRATION_ERROR, ModelType.OBJECT, true) .setValueType(ModelType.OBJECT) .setRequired(false) .build(); private final boolean describe; private WebMigrateOperation(boolean describe) { this.describe = describe; } static void registerOperations(ManagementResourceRegistration registry, ResourceDescriptionResolver resourceDescriptionResolver) { registry.registerOperationHandler(new SimpleOperationDefinitionBuilder(MIGRATE, resourceDescriptionResolver) .setRuntimeOnly() .setAccessConstraints(SensitiveTargetAccessConstraintDefinition.READ_WHOLE_CONFIG) .setReplyParameters(MIGRATION_WARNINGS_ATTR, MIGRATION_ERROR_ATTR) .build(), WebMigrateOperation.MIGRATE_INSTANCE); registry.registerOperationHandler(new SimpleOperationDefinitionBuilder(DESCRIBE_MIGRATION, resourceDescriptionResolver) .setRuntimeOnly() .setAccessConstraints(SensitiveTargetAccessConstraintDefinition.READ_WHOLE_CONFIG) .setReplyParameters(MIGRATION_WARNINGS_ATTR) .build(), WebMigrateOperation.DESCRIBE_MIGRATION_INSTANCE); } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { if (!describe && context.getRunningMode() != RunningMode.ADMIN_ONLY) { throw WebLogger.ROOT_LOGGER.migrateOperationAllowedOnlyInAdminOnly(); } final List<String> warnings = new ArrayList<>(); // node containing the description (list of add operations) of the legacy subsystem final ModelNode legacyModelAddOps = new ModelNode(); //we don't preserve order, instead we sort by address length final Map<PathAddress, ModelNode> sortedMigrationOperations = new TreeMap<>(new Comparator<PathAddress>() { @Override public int compare(PathAddress o1, PathAddress o2) { final int compare = Integer.compare(o1.size(), o2.size()); if (compare != 0) { return compare; } return o1.toString().compareTo(o2.toString()); } }); // invoke an OSH to describe the legacy messaging subsystem describeLegacyWebResources(context, legacyModelAddOps); // invoke an OSH to add the messaging-activemq extension // FIXME: this does not work it the extension :add is added to the migrationOperations directly (https://issues.jboss.org/browse/WFCORE-323) addExtension(context, sortedMigrationOperations, describe, UNDERTOW_EXTENSION); addExtension(context, sortedMigrationOperations, describe, IO_EXTENSION); context.addStep(new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { addDefaultResources(sortedMigrationOperations, legacyModelAddOps, warnings); // transform the legacy add operations and put them in migrationOperations ProcessType processType = context.getCallEnvironment().getProcessType(); boolean domainMode = processType != ProcessType.STANDALONE_SERVER && processType != ProcessType.SELF_CONTAINED; PathAddress baseAddres; if(domainMode) { baseAddres = pathAddress(operation.get(ADDRESS)).getParent(); } else { baseAddres = pathAddress(); } //create the new IO subsystem createIoSubsystem(context, sortedMigrationOperations, baseAddres); createWelcomeContentHandler(sortedMigrationOperations); transformResources(context, legacyModelAddOps, sortedMigrationOperations, warnings, domainMode); fixAddressesForDomainMode(pathAddress(operation.get(ADDRESS)), sortedMigrationOperations); // put the /subsystem=web:remove operation as first LinkedHashMap<PathAddress, ModelNode> orderedMigrationOperations = new LinkedHashMap<>(); removeWebSubsystem(orderedMigrationOperations, context.getProcessType() == ProcessType.STANDALONE_SERVER, pathAddress(operation.get(ADDRESS))); orderedMigrationOperations.putAll(sortedMigrationOperations); if (describe) { // :describe-migration operation // for describe-migration operation, do nothing and return the list of operations that would // be executed in the composite operation final Collection<ModelNode> values = orderedMigrationOperations.values(); ModelNode result = new ModelNode(); if(!warnings.isEmpty()) { ModelNode rw = new ModelNode().setEmptyList(); for (String warning : warnings) { rw.add(warning); } result.get(MIGRATION_WARNINGS).set(rw); } result.get(MIGRATION_OPERATIONS).set(values); context.getResult().set(result); } else { // :migrate operation // invoke an OSH on a composite operation with all the migration operations final Map<PathAddress, ModelNode> migrateOpResponses = migrateSubsystems(context, orderedMigrationOperations); context.completeStep(new OperationContext.ResultHandler() { @Override public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) { final ModelNode result = new ModelNode(); ModelNode rw = new ModelNode().setEmptyList(); for (String warning : warnings) { rw.add(warning); } result.get(MIGRATION_WARNINGS).set(rw); if (resultAction == OperationContext.ResultAction.ROLLBACK) { for (Map.Entry<PathAddress, ModelNode> entry : migrateOpResponses.entrySet()) { if (entry.getValue().hasDefined(FAILURE_DESCRIPTION)) { //we check for failure description, as every node has 'failed', but one //the real error has a failure description //we break when we find the first one, as there will only ever be one failure //as the op stops after the first failure ModelNode desc = new ModelNode(); desc.get(OP).set(orderedMigrationOperations.get(entry.getKey())); desc.get(RESULT).set(entry.getValue()); result.get(MIGRATION_ERROR).set(desc); break; } } context.getFailureDescription().set(new ModelNode(WebLogger.ROOT_LOGGER.migrationFailed())); } context.getResult().set(result); } }); } } }, MODEL); } /** * Creates the security realm * * @param context * @param migrationOperations * @return */ private SSLInformation createSecurityRealm(OperationContext context, Map<PathAddress, ModelNode> migrationOperations, ModelNode legacyModelAddOps, String connector, List<String> warnings, boolean domainMode) { ModelNode legacyAddOp = findResource(pathAddress(WebExtension.SUBSYSTEM_PATH, pathElement(WebExtension.CONNECTOR_PATH.getKey(), connector), pathElement("configuration", "ssl")), legacyModelAddOps); if (legacyAddOp == null) { return null; } //we have SSL //read all the info from the SSL definition ModelNode keyAlias = legacyAddOp.get(WebSSLDefinition.KEY_ALIAS.getName()); ModelNode password = legacyAddOp.get(WebSSLDefinition.PASSWORD.getName()); ModelNode certificateKeyFile = legacyAddOp.get(WebSSLDefinition.CERTIFICATE_KEY_FILE.getName()); ModelNode cipherSuite = legacyAddOp.get(WebSSLDefinition.CIPHER_SUITE.getName()); ModelNode protocol = legacyAddOp.get(WebSSLDefinition.PROTOCOL.getName()); ModelNode verifyClient = legacyAddOp.get(WebSSLDefinition.VERIFY_CLIENT.getName()); ModelNode verifyDepth = legacyAddOp.get(WebSSLDefinition.VERIFY_DEPTH.getName()); ModelNode certificateFile = legacyAddOp.get(WebSSLDefinition.CERTIFICATE_FILE.getName()); ModelNode caCertificateFile = legacyAddOp.get(WebSSLDefinition.CA_CERTIFICATE_FILE.getName()); ModelNode caCertificatePassword = legacyAddOp.get(WebSSLDefinition.CA_CERTIFICATE_PASSWORD.getName()); ModelNode csRevocationURL = legacyAddOp.get(WebSSLDefinition.CA_REVOCATION_URL.getName()); ModelNode trustStoreType = legacyAddOp.get(WebSSLDefinition.TRUSTSTORE_TYPE.getName()); ModelNode keystoreType = legacyAddOp.get(WebSSLDefinition.KEYSTORE_TYPE.getName()); ModelNode sessionCacheSize = legacyAddOp.get(WebSSLDefinition.SESSION_CACHE_SIZE.getName()); ModelNode sessionTimeout = legacyAddOp.get(WebSSLDefinition.SESSION_TIMEOUT.getName()); ModelNode sslProvider = legacyAddOp.get(WebSSLDefinition.SSL_PROTOCOL.getName()); if(verifyDepth.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebSSLDefinition.VERIFY_DEPTH.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); } if(certificateFile.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebSSLDefinition.CERTIFICATE_FILE.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); } if(sslProvider.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebSSLDefinition.SSL_PROTOCOL.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); } if(csRevocationURL.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebSSLDefinition.CA_REVOCATION_URL.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); } String realmName; PathAddress managementCoreService; if(domainMode) { Set<String> hosts = new HashSet<>(); Resource hostResource = context.readResourceFromRoot(pathAddress(), false); hosts.addAll(hostResource.getChildrenNames(HOST)); //now we need to find a unique name //in domain mode different profiles could have different SSL configurations //but the realms are not scoped to a profile //if we hard coded a name migration would fail when migrating domains with multiple profiles int counter = 1; realmName = REALM_NAME + counter; while(true) { boolean hostOk = true; for(String host : hosts) { Resource root = context.readResourceFromRoot(pathAddress(pathElement(HOST, host), pathElement(CORE_SERVICE, MANAGEMENT)), false); if (root.getChildrenNames(SECURITY_REALM).contains(realmName)) { counter++; realmName = REALM_NAME + counter; hostOk = false; break; } } if(hostOk) { break; } } for (String host : hosts) { createHostSSLConfig(realmName, migrationOperations, keyAlias, password, certificateKeyFile, protocol, caCertificateFile, caCertificatePassword, trustStoreType, keystoreType, pathAddress(pathElement(HOST, host), pathElement(CORE_SERVICE, MANAGEMENT))); } } else { managementCoreService = pathAddress(CORE_SERVICE, MANAGEMENT); //now we need to find a unique name //in domain mode different profiles could have different SSL configurations //but the realms are not scoped to a profile //if we hard coded a name migration would fail when migrating domains with multiple profiles int counter = 1; realmName = REALM_NAME + counter; boolean ok = false; do { Resource root = context.readResourceFromRoot(managementCoreService, false); if (root.getChildrenNames(SECURITY_REALM).contains(realmName)) { counter++; realmName = REALM_NAME + counter; } else { ok = true; } } while (!ok); //we have a unique realm name createHostSSLConfig(realmName, migrationOperations, keyAlias, password, certificateKeyFile, protocol, caCertificateFile, caCertificatePassword, trustStoreType, keystoreType, managementCoreService); } return new SSLInformation(realmName, verifyClient, sessionCacheSize, sessionTimeout, protocol, cipherSuite); } private String createHostSSLConfig(String realmName, Map<PathAddress, ModelNode> migrationOperations, ModelNode keyAlias, ModelNode password, ModelNode certificateKeyFile, ModelNode protocol, ModelNode caCertificateFile, ModelNode caCertificatePassword, ModelNode trustStoreType, ModelNode keystoreType, PathAddress managementCoreService) { //add the realm PathAddress addres = pathAddress(managementCoreService, pathElement(SECURITY_REALM, realmName)); migrationOperations.put(addres, createAddOperation(addres)); //now lets add the trust store addres = pathAddress(managementCoreService, pathElement(SECURITY_REALM, realmName), pathElement(AUTHENTICATION, TRUSTSTORE)); ModelNode addOp = createAddOperation(addres); addOp.get(ModelDescriptionConstants.KEYSTORE_PATH).set(caCertificateFile); addOp.get(ModelDescriptionConstants.KEYSTORE_PASSWORD).set(password); addOp.get(ModelDescriptionConstants.KEYSTORE_PROVIDER).set(trustStoreType); migrationOperations.put(addres, addOp); //now lets add the key store addres = pathAddress(managementCoreService, pathElement(SECURITY_REALM, realmName), pathElement(SERVER_IDENTITY, SSL)); addOp = createAddOperation(addres); addOp.get(ModelDescriptionConstants.KEYSTORE_PATH).set(certificateKeyFile); addOp.get(ModelDescriptionConstants.KEYSTORE_PASSWORD).set(password); addOp.get(ModelDescriptionConstants.KEYSTORE_PROVIDER).set(keystoreType); addOp.get(ModelDescriptionConstants.ALIAS).set(keyAlias); addOp.get(PROTOCOL).set(protocol); //addOp.get(KeystoreAttributes.KEY_PASSWORD.getName()).set(password); //TODO: is this correct? both key and keystore have same password? migrationOperations.put(addres, addOp); return realmName; } private void fixAddressesForDomainMode(PathAddress migrateAddress, Map<PathAddress, ModelNode> migrationOperations) { int i = 0; while (i < migrateAddress.size()) { if (migrateAddress.getElement(i).equals(WebExtension.SUBSYSTEM_PATH)) { break; } ++i; } if (i == 0) { //not domain mode, no need for a prefix return; } PathAddress prefix = migrateAddress.subAddress(0, i); Map<PathAddress, ModelNode> old = new HashMap<>(migrationOperations); migrationOperations.clear(); for (Map.Entry<PathAddress, ModelNode> e : old.entrySet()) { if (e.getKey().getElement(0).getKey().equals(SUBSYSTEM)) { final PathAddress oldAddress = pathAddress(e.getValue().get(ADDRESS)); List<PathElement> elements = new ArrayList<>(); for (PathElement j : prefix) { elements.add(j); } for (PathElement j : oldAddress) { elements.add(j); } PathAddress newAddress = pathAddress(elements); e.getValue().get(ADDRESS).set(newAddress.toModelNode()); migrationOperations.put(newAddress, e.getValue()); } else { //not targeted at a subsystem migrationOperations.put(e.getKey(), e.getValue()); } } } /** * We need to create the IO subsystem, if it does not already exist */ private void createIoSubsystem(OperationContext context, Map<PathAddress, ModelNode> migrationOperations, PathAddress baseAddress) { Resource root = context.readResourceFromRoot(baseAddress, false); if (root.getChildrenNames(SUBSYSTEM).contains(IOExtension.SUBSYSTEM_NAME)) { // subsystem is already added, do nothing return; } //these addresses will be fixed later, no need to use the base address PathAddress address = pathAddress(pathElement(SUBSYSTEM, IOExtension.SUBSYSTEM_NAME)); migrationOperations.put(address, createAddOperation(address)); address = pathAddress(pathElement(SUBSYSTEM, IOExtension.SUBSYSTEM_NAME), pathElement("worker", "default")); migrationOperations.put(address, createAddOperation(address)); address = pathAddress(pathElement(SUBSYSTEM, IOExtension.SUBSYSTEM_NAME), pathElement("buffer-pool", "default")); migrationOperations.put(address, createAddOperation(address)); } /** * create a handler for serving welcome content */ private void createWelcomeContentHandler(Map<PathAddress, ModelNode> migrationOperations) { PathAddress address = pathAddress(pathElement(SUBSYSTEM, UndertowExtension.SUBSYSTEM_NAME), pathElement(Constants.CONFIGURATION, Constants.HANDLER)); migrationOperations.put(address, createAddOperation(address)); address = pathAddress(pathElement(SUBSYSTEM, UndertowExtension.SUBSYSTEM_NAME), pathElement(Constants.CONFIGURATION, Constants.HANDLER), pathElement(Constants.FILE, "welcome-content")); final ModelNode add = createAddOperation(address); add.get(Constants.PATH).set(new ModelNode(new ValueExpression("${jboss.home.dir}/welcome-content"))); migrationOperations.put(address, add); } private void addDefaultResources(Map<PathAddress, ModelNode> migrationOperations, final ModelNode legacyModelDescription, List<String> warnings) { //add the default server PathAddress address = pathAddress(pathElement(SUBSYSTEM, UndertowExtension.SUBSYSTEM_NAME), DEFAULT_SERVER_PATH); ModelNode add = createAddOperation(address); ModelNode defaultSessionTimeout = null; //static resources ModelNode directoryListing = null; //todo: add support for some of these ModelNode sendfile = null; ModelNode fileEncoding = null; ModelNode readOnly = null; ModelNode webdav = null; ModelNode secret = null; ModelNode maxDepth = null; ModelNode disabled = null; for (ModelNode legacyAddOp : legacyModelDescription.get(RESULT).asList()) { final PathAddress la = pathAddress(legacyAddOp.get(ADDRESS)); if (la.equals(pathAddress(WebExtension.SUBSYSTEM_PATH))) { ModelNode defaultHost = legacyAddOp.get(WebDefinition.DEFAULT_VIRTUAL_SERVER.getName()); if (defaultHost.isDefined()) { add.get(Constants.DEFAULT_HOST).set(defaultHost.clone()); } ModelNode sessionTimeout = legacyAddOp.get(WebDefinition.DEFAULT_SESSION_TIMEOUT.getName()); if (sessionTimeout.isDefined()) { defaultSessionTimeout = sessionTimeout; } } else if (la.equals(pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.STATIC_RESOURCES_PATH))) { ModelNode node = legacyAddOp.get(WebStaticResources.LISTINGS.getName()); if (node.isDefined()) { directoryListing = node; } node = legacyAddOp.get(WebStaticResources.SENDFILE.getName()); if (node.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebStaticResources.SENDFILE.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); sendfile = node; } node = legacyAddOp.get(WebStaticResources.FILE_ENCODING.getName()); if (node.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebStaticResources.FILE_ENCODING.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); fileEncoding = node; } node = legacyAddOp.get(WebStaticResources.READ_ONLY.getName()); if (node.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebStaticResources.READ_ONLY.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); readOnly = node; } node = legacyAddOp.get(WebStaticResources.WEBDAV.getName()); if (node.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebStaticResources.WEBDAV.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); webdav = node; } node = legacyAddOp.get(WebStaticResources.SECRET.getName()); if (node.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebStaticResources.SECRET.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); secret = node; } node = legacyAddOp.get(WebStaticResources.MAX_DEPTH.getName()); if (node.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebStaticResources.MAX_DEPTH.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); maxDepth = node; } node = legacyAddOp.get(WebStaticResources.DISABLED.getName()); if (node.isDefined()) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebStaticResources.DISABLED.getName(), pathAddress(legacyAddOp.get(ADDRESS)))); disabled = node; } } } migrationOperations.put(address, add); address = pathAddress(pathElement(SUBSYSTEM, UndertowExtension.SUBSYSTEM_NAME), pathElement(Constants.BUFFER_CACHE, "default")); add = createAddOperation(address); migrationOperations.put(address, add); address = pathAddress(pathElement(SUBSYSTEM, UndertowExtension.SUBSYSTEM_NAME), pathElement(Constants.SERVLET_CONTAINER, "default")); add = createAddOperation(address); if (defaultSessionTimeout != null) { add.get(Constants.DEFAULT_SESSION_TIMEOUT).set(defaultSessionTimeout.clone()); } if (directoryListing != null) { add.get(Constants.DIRECTORY_LISTING).set(directoryListing); } migrationOperations.put(address, add); } /** * It's possible that the extension is already present. In that case, this method does nothing. */ private void addExtension(OperationContext context, Map<PathAddress, ModelNode> migrationOperations, boolean describe, String extension) { Resource root = context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS, false); if (root.getChildrenNames(EXTENSION).contains(extension)) { // extension is already added, do nothing return; } PathAddress extensionAddress = pathAddress(EXTENSION, extension); OperationEntry addEntry = context.getRootResourceRegistration().getOperationEntry(extensionAddress, ADD); ModelNode addOperation = createAddOperation(extensionAddress); addOperation.get(MODULE).set(extension); if (describe) { migrationOperations.put(extensionAddress, addOperation); } else { context.addStep(context.getResult().get(extensionAddress.toString()), addOperation, addEntry.getOperationHandler(), MODEL); } } private void removeWebSubsystem(Map<PathAddress, ModelNode> migrationOperations, boolean standalone, PathAddress subsystemAddress) { ModelNode removeOperation = createRemoveOperation(subsystemAddress); migrationOperations.put(subsystemAddress, removeOperation); //only add this if we are describing //to maintain the order we manually execute this last if(standalone) { removeOperation = createRemoveOperation(EXTENSION_ADDRESS); migrationOperations.put(EXTENSION_ADDRESS, removeOperation); } } private Map<PathAddress, ModelNode> migrateSubsystems(OperationContext context, final Map<PathAddress, ModelNode> migrationOperations) throws OperationFailedException { final Map<PathAddress, ModelNode> result = new LinkedHashMap<>(); MultistepUtil.recordOperationSteps(context, migrationOperations, result); return result; } private void transformResources(final OperationContext context, final ModelNode legacyModelDescription, final Map<PathAddress, ModelNode> newAddOperations, List<String> warnings, boolean domainMode) throws OperationFailedException { Set<String> hosts = new LinkedHashSet<>(); for (ModelNode legacyAddOp : legacyModelDescription.get(RESULT).asList()) { final ModelNode newAddOp = legacyAddOp.clone(); PathAddress address = pathAddress(newAddOp.get(ADDRESS)); if (address.size() == 1) { //subsystem migrateSubsystem(newAddOperations, newAddOp); } else if (address.equals(pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.STATIC_RESOURCES_PATH))) { //covered in the servlet container add, so just ignore } else if (address.equals(pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.JSP_CONFIGURATION_PATH))) { migrateJSPConfig(newAddOperations, newAddOp); } else if (address.equals(pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.CONTAINER_PATH))) { migrateMimeMapping(newAddOperations, newAddOp); } else if (wildcardEquals(address, pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.CONNECTOR_PATH))) { migrateConnector(context, newAddOperations, newAddOp, address, legacyModelDescription, warnings, domainMode); } else if (wildcardEquals(address, pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.CONNECTOR_PATH, WebExtension.SSL_PATH))) { // ignore, handled as part of connector migration } else if (wildcardEquals(address, pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.HOST_PATH))) { String host = address.getLastElement().getValue(); hosts.add(host); migrateVirtualHost(newAddOperations, newAddOp, host); } else if (wildcardEquals(address, pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.VALVE_PATH))) { migrateValves(newAddOperations, newAddOp, address, warnings); } else if (wildcardEquals(address, pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.HOST_PATH, WebExtension.ACCESS_LOG_PATH))) { migrateAccessLog(newAddOperations, newAddOp, address, legacyModelDescription, warnings); } else if (wildcardEquals(address, pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.HOST_PATH, WebExtension.ACCESS_LOG_PATH, WebExtension.DIRECTORY_PATH))) { //ignore, handled by access-log } else if (wildcardEquals(address, pathAddress(WebExtension.SUBSYSTEM_PATH, WebExtension.HOST_PATH, WebExtension.SSO_PATH))) { migrateSso(newAddOperations, newAddOp, address, warnings); } else { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(legacyAddOp)); } } if (!hosts.isEmpty()) { migrateVirtualHostChildren(newAddOperations, hosts); } newAddOperations.remove(VALVE_ACCESS_LOG_ADDRESS); } private void migrateSso(Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp, PathAddress address, List<String> warnings) { PathAddress newAddress = pathAddress(UndertowExtension.SUBSYSTEM_PATH, DEFAULT_SERVER_PATH, pathElement(Constants.HOST, address.getElement(address.size() - 2).getValue()), UndertowExtension.PATH_SSO); ModelNode add = createAddOperation(newAddress); add.get(Constants.DOMAIN).set(newAddOp.get(WebSSODefinition.DOMAIN.getName()).clone()); add.get(Constants.HTTP_ONLY).set(newAddOp.get(WebSSODefinition.HTTP_ONLY.getName()).clone()); if (newAddOp.hasDefined(WebSSODefinition.CACHE_CONTAINER.getName())) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebSSODefinition.CACHE_CONTAINER.getName(), pathAddress(newAddOp.get(ADDRESS)))); } if (newAddOp.hasDefined(WebSSODefinition.REAUTHENTICATE.getName())) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebSSODefinition.REAUTHENTICATE.getName(), pathAddress(newAddOp.get(ADDRESS)))); } if (newAddOp.hasDefined(WebSSODefinition.CACHE_NAME.getName())) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebSSODefinition.CACHE_NAME.getName(), pathAddress(newAddOp.get(ADDRESS)))); } newAddOperations.put(newAddress, add); } private void migrateAccessLog(Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp, PathAddress address, ModelNode legacyAddOps, List<String> warnings) { PathAddress newAddress = pathAddress(UndertowExtension.SUBSYSTEM_PATH, DEFAULT_SERVER_PATH, pathElement(Constants.HOST, address.getElement(address.size() - 2).getValue()), UndertowExtension.PATH_ACCESS_LOG); ModelNode add = createAddOperation(newAddress); //TODO: parse the pattern and modify to Undertow version ModelNode patternNode = newAddOp.get(WebAccessLogDefinition.PATTERN.getName()); if(patternNode.isDefined()) { add.get(Constants.PATTERN).set(migrateAccessLogPattern(patternNode.asString())); } add.get(Constants.PREFIX).set(newAddOp.get(WebAccessLogDefinition.PREFIX.getName()).clone()); add.get(Constants.ROTATE).set(newAddOp.get(WebAccessLogDefinition.ROTATE.getName()).clone()); if (newAddOp.hasDefined(WebAccessLogDefinition.RESOLVE_HOSTS.getName())) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebAccessLogDefinition.RESOLVE_HOSTS.getName(), pathAddress(newAddOp.get(ADDRESS)))); } add.get(Constants.EXTENDED).set(newAddOp.get(WebAccessLogDefinition.EXTENDED.getName()).clone()); ModelNode directory = findResource(pathAddress(pathAddress(newAddOp.get(ADDRESS)), WebExtension.DIRECTORY_PATH), legacyAddOps); if(directory != null){ add.get(Constants.DIRECTORY).set(directory.get(PATH)); add.get(Constants.RELATIVE_TO).set(directory.get(RELATIVE_TO)); } newAddOperations.put(newAddress, add); } private String migrateAccessLogPattern(String legacyPattern) { Matcher m = ACCESS_LOG_PATTERN.matcher(legacyPattern); StringBuilder sb = new StringBuilder(); int lastIndex = 0; while (m.find()) { sb.append(legacyPattern.substring(lastIndex, m.start())); lastIndex = m.end(); sb.append("%{"); sb.append(m.group(2)); sb.append(","); sb.append(m.group(1)); sb.append("}"); } sb.append(legacyPattern.substring(lastIndex)); return sb.toString(); } private void migrateAccessLogValve(Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp, String valveName, List<String> warnings) { ModelNode add = createAddOperation(VALVE_ACCESS_LOG_ADDRESS); final ModelNode params = newAddOp.get(WebValveDefinition.PARAMS.getName()); //TODO: parse the pattern and modify to Undertow version final ModelNode patternNode = params.get(Constants.PATTERN); if(patternNode.isDefined()) { add.get(Constants.PATTERN).set(migrateAccessLogPattern(patternNode.asString())); } add.get(Constants.PREFIX).set(params.get(Constants.PREFIX).clone()); add.get(Constants.SUFFIX).set(params.get(Constants.SUFFIX).clone()); add.get(Constants.ROTATE).set(params.get("rotatable").clone()); add.get(Constants.EXTENDED).set(newAddOp.get(Constants.EXTENDED).clone()); if(params.hasDefined(Constants.DIRECTORY)){ add.get(Constants.DIRECTORY).set(params.get(Constants.DIRECTORY).clone()); } if(params.hasDefined("conditionIf")) { add.get(Constants.PREDICATE).set("exists(%{r," + params.get("conditionIf").asString() + "})"); } if(params.hasDefined("conditionUnless")) { add.get(Constants.PREDICATE).set("not exists(%{r," + params.get("conditionUnless").asString() + "})"); } if(params.hasDefined("condition")) { add.get(Constants.PREDICATE).set("not exists(%{r," + params.get("condition").asString() + "})"); } final String[] unsupportedConfigParams = new String[] {"resolveHosts", "fileDateFormat", "renameOnRotate", "encoding", "locale", "requestAttributesEnabled", "buffered"}; for(String unsupportedConfigParam : unsupportedConfigParams) { if(params.hasDefined(unsupportedConfigParam)) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValveAttribute(unsupportedConfigParam, valveName)); } } newAddOperations.put(VALVE_ACCESS_LOG_ADDRESS, add); } private boolean wildcardEquals(PathAddress a1, PathAddress a2) { if (a1.size() != a2.size()) { return false; } for (int i = 0; i < a1.size(); ++i) { PathElement p1 = a1.getElement(i); PathElement p2 = a2.getElement(i); if (!p1.getKey().equals(p2.getKey())) { return false; } if (!p1.isWildcard() && !p2.isWildcard()) { if (!p1.getValue().equals(p2.getValue())) { return false; } } } return true; } private void migrateVirtualHost(Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp, String host) { PathAddress newAddress = pathAddress(UndertowExtension.SUBSYSTEM_PATH, DEFAULT_SERVER_PATH, pathElement(Constants.HOST, host)); ModelNode add = createAddOperation(newAddress); if (newAddOp.hasDefined(WebVirtualHostDefinition.ENABLE_WELCOME_ROOT.getName()) && newAddOp.get(WebVirtualHostDefinition.ENABLE_WELCOME_ROOT.getName()).asBoolean()) { PathAddress welcomeAddress = pathAddress(newAddress, pathElement(Constants.LOCATION, "/")); ModelNode welcomeAdd = createAddOperation(welcomeAddress); welcomeAdd.get(Constants.HANDLER).set("welcome-content"); newAddOperations.put(welcomeAddress, welcomeAdd); } add.get(Constants.ALIAS).set(newAddOp.get(WebVirtualHostDefinition.ALIAS.getName()).clone()); add.get(Constants.DEFAULT_WEB_MODULE).set(newAddOp.get(WebVirtualHostDefinition.DEFAULT_WEB_MODULE.getName())); newAddOperations.put(newAddress, add); } private void migrateVirtualHostChildren(Map<PathAddress, ModelNode> newAddOperations, Set<String> hosts) { final PathAddress customFilterAddresses = pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS, pathElement(CustomFilterDefinition.INSTANCE.getPathElement().getKey())); final PathAddress expressionFilterAddresses = pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS, pathElement(ExpressionFilterDefinition.INSTANCE.getPathElement().getKey())); List<PathAddress> filterAddresses = new ArrayList<>(); for(PathAddress a : newAddOperations.keySet()) { if(wildcardEquals(customFilterAddresses, a) || wildcardEquals(expressionFilterAddresses, a)) { filterAddresses.add(a); } } boolean hasAccessLogValve = newAddOperations.containsKey(VALVE_ACCESS_LOG_ADDRESS); if (hasAccessLogValve || !filterAddresses.isEmpty()) { for (String host : hosts) { PathAddress hostAddress = pathAddress(UndertowExtension.SUBSYSTEM_PATH, DEFAULT_SERVER_PATH, pathElement(Constants.HOST, host)); for (PathAddress filterAddress : filterAddresses) { PathAddress filterRefAddress = pathAddress(hostAddress, pathElement(Constants.FILTER_REF, filterAddress.getLastElement().getValue())); ModelNode filterRefAdd = createAddOperation(filterRefAddress); newAddOperations.put(filterRefAddress, filterRefAdd); } if (hasAccessLogValve) { PathAddress accessLogAddress = pathAddress(hostAddress, UndertowExtension.PATH_ACCESS_LOG); if(!newAddOperations.containsKey(accessLogAddress)) { ModelNode operation = newAddOperations.get(VALVE_ACCESS_LOG_ADDRESS).clone(); operation.get(OP_ADDR).set(accessLogAddress.toModelNode()); newAddOperations.put(accessLogAddress, operation); } } } } } private void migrateValves(Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp, PathAddress address, List<String> warnings) { if (newAddOp.hasDefined(WebValveDefinition.CLASS_NAME.getName())) { String valveClassName = newAddOp.get(WebValveDefinition.CLASS_NAME.getName()).asString(); String valveName = address.getLastElement().getValue(); switch (valveClassName) { case "org.apache.catalina.valves.CrawlerSessionManagerValve": PathAddress crawlerAddress = pathAddress(pathElement(SUBSYSTEM, UndertowExtension.SUBSYSTEM_NAME), pathElement(Constants.SERVLET_CONTAINER, "default"), pathElement(Constants.SETTING, Constants.CRAWLER_SESSION_MANAGEMENT)); ModelNode crawlerAdd = createAddOperation(crawlerAddress); if (newAddOp.hasDefined(WebValveDefinition.PARAMS.getName())) { ModelNode params = newAddOp.get(WebValveDefinition.PARAMS.getName()); if (params.hasDefined("crawlerUserAgents")) { crawlerAdd.get(Constants.USER_AGENTS).set(params.get("crawlerUserAgents")); } if (params.hasDefined("sessionInactiveInterval")) { crawlerAdd.get(Constants.SESSION_TIMEOUT).set(params.get("sessionInactiveInterval")); } } newAddOperations.put(crawlerAddress, crawlerAdd); break; case "org.apache.catalina.valves.RequestDumperValve": newAddOperations.putIfAbsent(pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS), createAddOperation(pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS))); PathAddress filterAddress = pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS, pathElement(ExpressionFilterDefinition.INSTANCE.getPathElement().getKey(), valveName)); ModelNode filterAdd = createAddOperation(filterAddress); filterAdd.get(ExpressionFilterDefinition.EXPRESSION.getName()).set("dump-request"); newAddOperations.put(filterAddress, filterAdd); break; case "org.apache.catalina.valves.StuckThreadDetectionValve": newAddOperations.putIfAbsent(pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS), createAddOperation(pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS))); PathAddress filterAddressStuckThread = pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS, pathElement(ExpressionFilterDefinition.INSTANCE.getPathElement().getKey(), valveName)); ModelNode filterAddStuckThread = createAddOperation(filterAddressStuckThread); StringBuilder expressionStruckThread = new StringBuilder("stuck-thread-detector"); if (newAddOp.hasDefined(WebValveDefinition.PARAMS.getName())) { ModelNode params = newAddOp.get(WebValveDefinition.PARAMS.getName()); if (params.hasDefined("threshold")) { expressionStruckThread.append("(threshhold='").append(params.get("threshold").asInt()).append("')"); } if (params.hasDefined("interruptThreadThreshold")) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValveAttribute("interruptThreadThreshold", valveName)); } } filterAddStuckThread.get(ExpressionFilterDefinition.EXPRESSION.getName()).set(expressionStruckThread.toString()); newAddOperations.put(filterAddressStuckThread, filterAddStuckThread); break; case "org.apache.catalina.valves.AccessLogValve": newAddOp.get(WebAccessLogDefinition.EXTENDED.getName()).set(false); migrateAccessLogValve(newAddOperations, newAddOp, valveName, warnings); break; case "org.apache.catalina.valves.ExtendedAccessLogValve": newAddOp.get(WebAccessLogDefinition.EXTENDED.getName()).set(true); migrateAccessLogValve(newAddOperations, newAddOp, valveName, warnings); break; case "org.apache.catalina.valves.RemoteHostValve": createAccesControlExpressionFilter(newAddOperations, warnings, valveName, "%h", newAddOp); break; case "org.apache.catalina.valves.RemoteAddrValve": createAccesControlExpressionFilter(newAddOperations, warnings, valveName, "%a", newAddOp); break; case "org.apache.catalina.valves.RemoteIpValve": if (newAddOp.hasDefined(WebValveDefinition.PARAMS.getName())) { StringBuilder expression = new StringBuilder(); ModelNode params = newAddOp.get(WebValveDefinition.PARAMS.getName()); if(params.hasDefined("remoteIpHeader") && ! X_FORWARDED_FOR_STRING.equalsIgnoreCase(params.get("remoteIpHeader").asString())) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValveAttribute("remoteIpHeader", valveName)); } if(params.hasDefined("protocolHeader") && ! X_FORWARDED_PROTO_STRING.equalsIgnoreCase(params.get("protocolHeader").asString())) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValveAttribute("protocolHeader", valveName)); } if(params.hasDefined("httpServerPort")) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValveAttribute("httpServerPort", valveName)); } if(params.hasDefined("httpsServerPort")) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValveAttribute("httpsServerPort", valveName)); } if(params.hasDefined("proxiesHeader")) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValveAttribute("proxiesHeader", valveName)); } if(params.hasDefined("protocolHeaderHttpsValue")) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValveAttribute("protocolHeaderHttpsValue", valveName)); } boolean trustedProxies = false; if (params.hasDefined("trustedProxies")) { expression.append("regex(pattern=\"").append(params.get("trustedProxies").asString()).append("\", value=%{i,x-forwarded-for}, full-match=true)"); trustedProxies = true; } String internalProxies; if (params.hasDefined("internalProxies")) { internalProxies = params.get("internalProxies").asString(); } else { internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|169\\.254\\.\\d{1,3}\\.\\d{1,3}|127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"; } if (trustedProxies) { expression.append(" and "); } expression.append("regex(pattern=\"").append(internalProxies).append("\", value=%{i,x-forwarded-for}, full-match=true)"); expression.append(" -> proxy-peer-address"); createExpressionFilter(newAddOperations, valveName, expression.toString()); } break; default: warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValve(valveName)); break; } } } private void createAccesControlExpressionFilter(Map<PathAddress, ModelNode> newAddOperations, List<String> warnings, String name, String attribute, ModelNode newAddOp) { if (newAddOp.hasDefined(WebValveDefinition.PARAMS.getName())) { StringBuilder expression = new StringBuilder(); expression.append("access-control(acl={"); ModelNode params = newAddOp.get(WebValveDefinition.PARAMS.getName()); boolean isValid = false; if (params.hasDefined("deny")) { isValid = true; String[] denied = params.get("deny").asString().split(","); for (String deny : denied) { expression.append('\'').append(deny.trim()).append(" deny\', "); } } if (params.hasDefined("allow")) { isValid = true; String[] allowed = params.get("allow").asString().split(","); for (String allow : allowed) { expression.append('\'').append(allow.trim()).append(" allow\', "); } } if (isValid) { expression.delete(expression.length() - 2, expression.length()); expression.append("} , attribute=").append(attribute.trim()).append(')'); createExpressionFilter(newAddOperations, name, expression.toString()); } else { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValve(name)); } } else { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateValve(name)); } } private void createExpressionFilter(Map<PathAddress, ModelNode> newAddOperations, String name, String expression) { newAddOperations.putIfAbsent(pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS), createAddOperation(pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS))); PathAddress filterAddress = pathAddress(UndertowExtension.SUBSYSTEM_PATH, PATH_FILTERS, pathElement(ExpressionFilterDefinition.INSTANCE.getPathElement().getKey(), name)); ModelNode filterAdd = createAddOperation(filterAddress); filterAdd.get(ExpressionFilterDefinition.EXPRESSION.getName()).set(expression); newAddOperations.put(filterAddress, filterAdd); } private void migrateConnector(OperationContext context, Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp, PathAddress address, ModelNode legacyModelAddOps, List<String> warnings, boolean domainMode) throws OperationFailedException { String protocol = newAddOp.get(WebConnectorDefinition.PROTOCOL.getName()).asString(); String scheme = null; if (newAddOp.hasDefined(WebConnectorDefinition.SCHEME.getName())) { scheme = newAddOp.get(WebConnectorDefinition.SCHEME.getName()).asString(); } final PathAddress newAddress; final ModelNode addConnector; switch (protocol) { case "org.apache.coyote.http11.Http11Protocol": case "org.apache.coyote.http11.Http11NioProtocol": case "org.apache.coyote.http11.Http11AprProtocol": case "HTTP/1.1": if (scheme == null || scheme.equals("http")) { newAddress = pathAddress(UndertowExtension.SUBSYSTEM_PATH, DEFAULT_SERVER_PATH, pathElement(Constants.HTTP_LISTENER, address.getLastElement().getValue())); addConnector = createAddOperation(newAddress); } else if (scheme.equals("https")) { newAddress = pathAddress(UndertowExtension.SUBSYSTEM_PATH, DEFAULT_SERVER_PATH, pathElement(Constants.HTTPS_LISTENER, address.getLastElement().getValue())); addConnector = createAddOperation(newAddress); SSLInformation sslInfo = createSecurityRealm(context, newAddOperations, legacyModelAddOps, newAddress.getLastElement().getValue(), warnings, domainMode); if (sslInfo == null) { throw WebLogger.ROOT_LOGGER.noSslConfig(); } else { addConnector.get(Constants.SECURITY_REALM).set(sslInfo.realmName); ModelNode verify = sslInfo.verifyClient; if(verify.isDefined()) { if(verify.getType() == ModelType.EXPRESSION) { warnings.add(WebLogger.ROOT_LOGGER.couldNotTranslateVerifyClientExpression(verify.toString())); addConnector.get(Constants.VERIFY_CLIENT).set(verify); } else { String translated = translateVerifyClient(verify.asString(), warnings); if(translated != null) { addConnector.get(Constants.VERIFY_CLIENT).set(translated); } } } addConnector.get(Constants.SSL_SESSION_CACHE_SIZE).set(sslInfo.sessionCacheSize); addConnector.get(Constants.SSL_SESSION_TIMEOUT).set(sslInfo.sessionTimeout); addConnector.get(Constants.ENABLED_PROTOCOLS).set(sslInfo.sslProtocol); addConnector.get(Constants.ENABLED_CIPHER_SUITES).set(sslInfo.cipherSuites); } } else { newAddress = null; addConnector = null; } break; case "org.apache.coyote.ajp.AjpAprProtocol": case "org.apache.coyote.ajp.AjpProtocol": case "AJP/1.3": newAddress = pathAddress(UndertowExtension.SUBSYSTEM_PATH, DEFAULT_SERVER_PATH, pathElement(Constants.AJP_LISTENER, address.getLastElement().getValue())); addConnector = createAddOperation(newAddress); addConnector.get(Constants.SCHEME).set(newAddOp.get(Constants.SCHEME)); break; default: newAddress = null; addConnector = null; } if (newAddress == null) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(newAddOp)); return; } addConnector.get(Constants.SOCKET_BINDING).set(newAddOp.get(SOCKET_BINDING)); addConnector.get(Constants.SECURE).set(newAddOp.get(WebConnectorDefinition.SECURE.getName())); addConnector.get(Constants.REDIRECT_SOCKET).set(newAddOp.get(WebConnectorDefinition.REDIRECT_BINDING.getName())); addConnector.get(Constants.ENABLED).set(newAddOp.get(WebConnectorDefinition.ENABLED.getName())); addConnector.get(Constants.RESOLVE_PEER_ADDRESS).set(newAddOp.get(WebConnectorDefinition.ENABLE_LOOKUPS.getName())); addConnector.get(Constants.MAX_POST_SIZE).set(newAddOp.get(WebConnectorDefinition.MAX_POST_SIZE.getName())); addConnector.get(Constants.REDIRECT_SOCKET).set(newAddOp.get(WebConnectorDefinition.REDIRECT_BINDING.getName())); addConnector.get(Constants.MAX_CONNECTIONS).set(newAddOp.get(WebConnectorDefinition.MAX_CONNECTIONS.getName())); addConnector.get(Constants.MAX_BUFFERED_REQUEST_SIZE).set(newAddOp.get(WebConnectorDefinition.MAX_SAVE_POST_SIZE.getName())); addConnector.get(Constants.SECURE).set(newAddOp.get(WebConnectorDefinition.SECURE.getName())); if(newAddOp.hasDefined(WebConnectorDefinition.REDIRECT_PORT.getName())) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebConnectorDefinition.REDIRECT_PORT.getName(), pathAddress(newAddOp.get(ADDRESS)))); } if(newAddOp.hasDefined(WebConnectorDefinition.PROXY_BINDING.getName())) { warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebConnectorDefinition.PROXY_BINDING.getName(), pathAddress(newAddOp.get(ADDRESS)))); } if (newAddOp.hasDefined(WebConnectorDefinition.EXECUTOR.getName())) { //TODO: migrate executor to worker warnings.add(WebLogger.ROOT_LOGGER.couldNotMigrateResource(WebConnectorDefinition.EXECUTOR.getName(), pathAddress(newAddOp.get(ADDRESS)))); } newAddOperations.put(pathAddress(newAddOp.get(OP_ADDR)), addConnector); } private String translateVerifyClient(String s, List<String> warnings) { switch(s) { case "optionalNoCA": case "optional": { return "REQUESTED"; } case "require" : { return "REQUIRED"; } case "none": { return "NOT_REQUESTED"; } case "true": { return "REQUIRED"; } case "false": { return "NOT_REQUESTED"; } default: { warnings.add(WebLogger.ROOT_LOGGER.couldNotTranslateVerifyClient(s)); return null; } } } private void migrateMimeMapping(Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp) { migrateWelcomeFiles(newAddOperations, newAddOp); ModelNode mime = newAddOp.get("mime-mapping"); if (mime.isDefined()) { for (ModelNode w : mime.asList()) { PathAddress wa = pathAddress(pathAddress(UndertowExtension.SUBSYSTEM_PATH, pathElement(Constants.SERVLET_CONTAINER, "default"), pathElement(Constants.MIME_MAPPING, w.asProperty().getName()))); ModelNode add = createAddOperation(wa); add.get(Constants.VALUE).set(w.asProperty().getValue()); newAddOperations.put(wa, add); } } } private void migrateWelcomeFiles(Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp) { ModelNode welcome = newAddOp.get("welcome-file"); if (welcome.isDefined()) { for (ModelNode w : welcome.asList()) { PathAddress wa = pathAddress(pathAddress(UndertowExtension.SUBSYSTEM_PATH, pathElement(Constants.SERVLET_CONTAINER, "default"), pathElement(Constants.WELCOME_FILE, w.asString()))); ModelNode add = createAddOperation(wa); newAddOperations.put(wa, add); } } } private void migrateJSPConfig(Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp) { newAddOp.get(ADDRESS).set(pathAddress(UndertowExtension.SUBSYSTEM_PATH, pathElement(Constants.SERVLET_CONTAINER, "default"), UndertowExtension.PATH_JSP).toModelNode()); newAddOperations.put(pathAddress(newAddOp.get(OP_ADDR)), newAddOp); } private void migrateSubsystem(Map<PathAddress, ModelNode> newAddOperations, ModelNode newAddOp) { newAddOp.get(ADDRESS).set(pathAddress(pathElement(SUBSYSTEM, UndertowExtension.SUBSYSTEM_NAME)).toModelNode()); PathAddress address = pathAddress(newAddOp.get(OP_ADDR)); ModelNode subsystemAdd = createAddOperation(address); ModelNode defaultServer = newAddOperations.get(address.append(DEFAULT_SERVER_PATH)); if (defaultServer.hasDefined(Constants.DEFAULT_HOST)){ subsystemAdd.get(Constants.DEFAULT_VIRTUAL_HOST).set(defaultServer.get(Constants.DEFAULT_HOST)); } newAddOperations.put(address, subsystemAdd); } private void describeLegacyWebResources(OperationContext context, ModelNode legacyModelDescription) { ModelNode describeLegacySubsystem = createOperation(GenericSubsystemDescribeHandler.DEFINITION, context.getCurrentAddress()); context.addStep(legacyModelDescription, describeLegacySubsystem, GenericSubsystemDescribeHandler.INSTANCE, MODEL, true); } private static ModelNode findResource(PathAddress address, ModelNode legacyAddOps) { for (ModelNode legacyAddOp : legacyAddOps.get(RESULT).asList()) { final PathAddress la = pathAddress(legacyAddOp.get(ADDRESS)); if (la.equals(address)) { return legacyAddOp; } } return null; } private class SSLInformation { final String realmName; final ModelNode verifyClient; final ModelNode sessionCacheSize; final ModelNode sessionTimeout; final ModelNode sslProtocol; final ModelNode cipherSuites; private SSLInformation(String realmName, ModelNode verifyClient, ModelNode sessionCacheSize, ModelNode sessionTimeout, ModelNode sslProtocol, ModelNode cipherSuites) { this.realmName = realmName; this.verifyClient = verifyClient; this.sessionCacheSize = sessionCacheSize; this.sessionTimeout = sessionTimeout; this.sslProtocol = sslProtocol; this.cipherSuites = cipherSuites; } } }