/*
* 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;
}
}
}