/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.modules.plugins.wildfly10.helper;
import static org.rhq.core.util.StringUtil.EMPTY_STRING;
import static org.w3c.dom.Node.ELEMENT_NODE;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.rhq.core.pluginapi.util.CommandLineOption;
import org.rhq.modules.plugins.wildfly10.AS7CommandLine;
import org.rhq.modules.plugins.wildfly10.AS7Mode;
/**
* A host configuration - loaded from either standalone.xml or host.xml.
*
* @author Heiko Rupp
*/
public class HostConfiguration {
public static final int DEFAULT_MGMT_PORT = 9990;
public static final int DEFAULT_NATIVE_PORT = 9999;
private static final String BIND_ADDRESS_MANAGEMENT_SYSPROP = "jboss.bind.address.management";
private static final String DOMAIN_MASTER_ADDRESS_SYSPROP = "jboss.domain.master.address";
private static final String DOMAIN_MASTER_PORT_SYSPROP = "jboss.domain.master.port";
private static final String SOCKET_BINDING_PORT_OFFSET_SYSPROP = "jboss.socket.binding.port-offset";
private static final CommandLineOption BIND_ADDRESS_MANAGEMENT_OPTION = new CommandLineOption("bmanagement", null);
private static final CommandLineOption MASTER_ADDRESS_OPTION = new CommandLineOption(null, "master-address");
private static final CommandLineOption MASTER_PORT_OPTION = new CommandLineOption(null, "master-port");
private final Log log = LogFactory.getLog(HostConfiguration.class);
private Document document;
private XPathFactory xpathFactory;
/**
*
* @param hostXmlFile absolute path to standalone.xml or host.xml file
*/
public HostConfiguration(File hostXmlFile) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream is = new FileInputStream(hostXmlFile);
try {
this.document = builder.parse(is);
} finally {
is.close();
}
this.xpathFactory = XPathFactory.newInstance();
}
/**
* Try to obtain the management IP and port from the already parsed host.xml or standalone.xml
*
* @param commandLine Command line arguments of the process to
*
* @return an Object containing host and port
*/
public HostPort getManagementHostPort(AS7CommandLine commandLine, AS7Mode mode) {
// There are three ways to configure the http(s) management endpoint
//
// 1. Standalone servers favored style (socket-binding style)
// The socket-binding node attribute is *either* 'https' *or* 'http'
// <management>
// <management-interfaces>
// <http-interface security-realm="ManagementRealm">
// <socket-binding https="management-https"/>
// </http-interface>
// </management-interfaces>
// </management>
//
// 2. Host controllers style, unfavored standalone servers style (socket style)
// <management>
// <management-interfaces>
// <http-interface security-realm="ManagementRealm">
// <socket interface="management" port="9990" secure-port="9443"/>
// </http-interface>
// </management-interfaces>
// </management>
//
//
// 3. Very old and deprecated style (early as7 style)
// <management>
// <management-interfaces>
// <http-interface security-realm="ManagementRealm" interface="management" port="9990" secure-port="9443"/>
// </management-interfaces>
// </management>
String portString;
String interfaceExpression;
String socketBindingName;
String portOffsetRaw = null;
boolean isSecure = false;
// detect http interface
if (findMatchingElements("//management/management-interfaces/http-interface/socket-binding").getLength() != 0) {
// This is case 1
socketBindingName = obtainXmlPropertyViaXPath("//management/management-interfaces/http-interface/socket-binding/@https");
if (!socketBindingName.isEmpty()) {
isSecure = true;
} else {
socketBindingName = obtainXmlPropertyViaXPath("//management/management-interfaces/http-interface/socket-binding/@http");
}
portString = obtainXmlPropertyViaXPath("/server/socket-binding-group/socket-binding[@name='"
+ socketBindingName + "']/@port");
String interfaceName = obtainXmlPropertyViaXPath("/server/socket-binding-group/socket-binding[@name='"
+ socketBindingName + "']/@interface");
String xpathExpression = "/server/socket-binding-group/@port-offset";
portOffsetRaw = obtainXmlPropertyViaXPath(xpathExpression);
interfaceExpression = obtainXmlPropertyViaXPath("/server/interfaces/interface[@name='" + interfaceName
+ "']/inet-address/@value");
if (interfaceExpression.isEmpty()) {
interfaceExpression = obtainXmlPropertyViaXPath("/server/interfaces/interface[@name='" + interfaceName
+ "']/loopback-address/@value");
}
} else if (findMatchingElements("//management/management-interfaces/http-interface/socket").getLength() != 0) {
// This is case 2
String socketInterface = obtainXmlPropertyViaXPath("//management/management-interfaces/http-interface/socket/@interface");
interfaceExpression = obtainXmlPropertyViaXPath("//interfaces/interface[@name='" + socketInterface
+ "']/inet-address/@value");
if (interfaceExpression.isEmpty()) {
interfaceExpression = obtainXmlPropertyViaXPath("//interfaces/interface[@name='" + socketInterface
+ "']/loopback-address/@value");
}
portString = obtainXmlPropertyViaXPath("//management/management-interfaces/http-interface/socket/@secure-port");
if (!portString.isEmpty()) {
isSecure = true;
} else {
portString = obtainXmlPropertyViaXPath("//management/management-interfaces/http-interface/socket/@port");
}
} else {
// This is case 3
portString = obtainXmlPropertyViaXPath("//management/management-interfaces/http-interface/@secure-port");
if (!portString.isEmpty()) {
isSecure = true;
} else {
portString = obtainXmlPropertyViaXPath("//management/management-interfaces/http-interface/@port");
}
String interfaceName = obtainXmlPropertyViaXPath("//management/management-interfaces/http-interface/@interface");
interfaceExpression = obtainXmlPropertyViaXPath("/server/interfaces/interface[@name='" + interfaceName
+ "']/inet-address/@value");
if (interfaceExpression.isEmpty()) {
interfaceExpression = obtainXmlPropertyViaXPath("/server/interfaces/interface[@name='" + interfaceName
+ "']/loopback-address/@value");
}
}
HostPort hp = new HostPort();
hp.isSecure = isSecure;
if (!interfaceExpression.isEmpty()) {
hp.host = replaceDollarExpression(interfaceExpression, commandLine, "127.0.0.1");
} else {
hp.host = "127.0.0.1"; // Fallback
}
hp.port = 0;
if (portString != null && !portString.isEmpty()) {
String tmp = replaceDollarExpression(portString, commandLine, String.valueOf(DEFAULT_MGMT_PORT));
hp.port = Integer.valueOf(tmp);
}
if (portOffsetRaw != null && !portOffsetRaw.isEmpty()) {
String portOffsetString = replaceDollarExpression(portOffsetRaw, commandLine, "0");
Integer portOffset = Integer.valueOf(portOffsetString);
hp.port += portOffset;
hp.withOffset = true;
} else if (mode == AS7Mode.STANDALONE) {
// On standalone servers, offset may also be set with a system property
String value = commandLine.getSystemProperties().get(SOCKET_BINDING_PORT_OFFSET_SYSPROP);
if (value != null) {
int offset = Integer.valueOf(value);
hp.port += offset;
}
}
return hp;
}
private NodeList findMatchingElements(String expression) {
try {
XPathExpression xPathExpression = xpathFactory.newXPath().compile(expression);
return (NodeList) xPathExpression.evaluate(document, XPathConstants.NODESET);
} catch (XPathExpressionException e) {
throw new RuntimeException(e);
}
}
/**
* Try to determine the host name - that is the name of a standalone server or a
* host in domain mode by looking at the standalone.xml/host.xml files
* @return server name
*/
public String getHostName() {
String hostName = this.document.getDocumentElement().getAttribute("name");
return hostName;
}
/**
* Try to obtain the domain controller's location from looking at host.xml
* @return host and port of the domain controller
*/
public HostPort getDomainControllerHostPort(AS7CommandLine commandLine) {
// first check remote, as we can't distinguish between a missing local element or
// an empty one, which is the default
String remoteHost = obtainXmlPropertyViaXPath("/host/domain-controller/remote/@host");
String portString = obtainXmlPropertyViaXPath("/host/domain-controller/remote/@port");
HostPort hp;
if (!remoteHost.isEmpty() && !portString.isEmpty()) {
// remote domain controller
hp = new HostPort(false);
hp.host = replaceDollarExpression(remoteHost, commandLine, "localhost");
portString = replaceDollarExpression(portString, commandLine, "9999");
hp.port = Integer.parseInt(portString);
} else {
// local domain controller
hp = new HostPort(true);
hp.port = 9999;
}
return hp;
}
/**
*
* @return true if $local authentication is the only authentication configured for realm associated to native management interface
*/
public boolean isNativeLocalOnly() {
String nativeRealm = getManagementSecurityRealm();
if (nativeRealm != null) {
XPath xpath = this.xpathFactory.newXPath();
try {
XPathExpression expr = xpath.compile("count(//management/security-realms/security-realm[@name='"
+ nativeRealm + "']/authentication[count(local) = count(*)]) = 1");
return (Boolean) expr.evaluate(this.document, XPathConstants.BOOLEAN);
} catch (XPathExpressionException e) {
log.error("Evaluation of XPath expression failed: " + e.getMessage());
return false;
}
}
return false;
}
public String getManagementSecurityRealm() {
String realm = obtainXmlPropertyViaXPath("//management/management-interfaces/http-interface/@security-realm");
return realm;
}
/**
* read server SSL key-store information
* @return null if not present
*/
public TruststoreConfig getServerIdentityKeystore() {
String mgmtRealm = getManagementSecurityRealm();
if (mgmtRealm != null) {
Node keyStoreNode = (Node) xpathExpression("//management/security-realms/security-realm[@name='"
+ mgmtRealm + "']/server-identities/ssl/keystore", XPathConstants.NODE);
return TruststoreConfig.fromXmlNode(keyStoreNode);
}
return null;
}
/**
* read trust-store information for 2-way authentication
* @return null if not present
*/
public TruststoreConfig getClientAuthenticationTruststore() {
String mgmtRealm = getManagementSecurityRealm();
if (mgmtRealm != null) {
Node keyStoreNode = (Node) xpathExpression("//management/security-realms/security-realm[@name='"
+ mgmtRealm + "']/authentication/truststore", XPathConstants.NODE);
return TruststoreConfig.fromXmlNode(keyStoreNode);
}
return null;
}
/**
* read vault configuration
* @return vault configuration (key,value of vault-options) or null if vault is not present
*/
public Map<String, String> getVault() {
Node vaultNode = (Node) xpathExpression("//vault", XPathConstants.NODE);
if (vaultNode == null) {
return null;
}
Map<String, String> vault = new LinkedHashMap<String, String>();
NodeList vaultOptions = (NodeList) xpathExpression("//vault/vault-option", XPathConstants.NODESET);
for (int i=0; i< vaultOptions.getLength(); i++) {
Node option = vaultOptions.item(i);
vault.put(option.getAttributes().getNamedItem("name").getNodeValue(),
option.getAttributes().getNamedItem("value").getNodeValue());
}
return vault;
}
/**
* @Deprecated use {@link HostConfiguration#getSecurityPropertyFile(ServerPluginConfiguration, String)} instead
*/
public File getSecurityPropertyFile(File baseDir, AS7Mode mode, String realm) {
String fileName = obtainXmlPropertyViaXPath("//security-realms/security-realm[@name='" + realm
+ "']/authentication/properties/@path");
String relDir = obtainXmlPropertyViaXPath("//security-realms/security-realm[@name='" + realm
+ "']/authentication/properties/@relative-to");
String dmode;
if (mode == AS7Mode.STANDALONE)
dmode = "server";
else
dmode = "domain";
File configDir;
if (relDir.equals("jboss." + dmode + ".config.dir")) {
configDir = new File(baseDir, "configuration");
} else {
configDir = new File(relDir);
}
File securityPropertyFile = new File(configDir, fileName);
return securityPropertyFile;
}
public File getSecurityPropertyFile(ServerPluginConfiguration configuration, String realm) {
String fileName = obtainXmlPropertyViaXPath("//security-realms/security-realm[@name='" + realm
+ "']/authentication/properties/@path");
String relDir = obtainXmlPropertyViaXPath("//security-realms/security-realm[@name='" + realm
+ "']/authentication/properties/@relative-to");
if (relDir == null || relDir.isEmpty()) {
return new File(fileName);
}
File relativeDir = configuration.getPath(relDir);
if (relativeDir != null) {
return new File(relativeDir, fileName);
} else {
log.warn("Cannot resolve security property file based on path=" + fileName + " relative-to=" + relDir);
return new File(fileName);
}
}
public String getDomainApiVersion() {
// Look for the first child node of type element (<host> in domain mode or <server> in standalone mode)
// We can't just call getFirstChild because first child could be a node of type comment
for (Node childNode = document.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) {
if (childNode.getNodeType() == ELEMENT_NODE) {
String xmlns = childNode.getAttributes().getNamedItem("xmlns").getTextContent();
return xmlns.substring(xmlns.lastIndexOf(':') + 1);
}
}
return EMPTY_STRING;
}
/**
* Run the passed xpathExpression on the prepopulated hostXml document and
* return the target element or attribute as a String.
* @param xpathExpression XPath Expression to evaluate
* @return String value of the Element or Attribute the XPath was pointing to.
* Null in case the xpathExpression could not be evaluated.
* @throws IllegalArgumentException if hostXml is null
*/
public String obtainXmlPropertyViaXPath(String xpathExpression) {
return (String) xpathExpression(xpathExpression, XPathConstants.STRING);
}
private Object xpathExpression(String xpathExpression, QName returnType) {
XPath xpath = this.xpathFactory.newXPath();
try {
XPathExpression expr = xpath.compile(xpathExpression);
return expr.evaluate(this.document, returnType);
} catch (XPathExpressionException e) {
log.error("Evaluation of XPath expression failed: " + e.getMessage());
return null;
}
}
/**
* Check if the passed value has an expression in the form of ${var} or ${var:default},
* try to resolve it. Resolution is done by looking at the command line to see if
* there are -bmanagement or -Djboss.bind.address.management arguments present
*
* @param value a hostname or hostname expression
* @param commandLine The command line from the process
* @param lastResort fall back to this value if the value could not be found on the command line and
* the expression did not specify a default value
* @return resolved value
*/
protected String replaceDollarExpression(String value, AS7CommandLine commandLine, String lastResort) {
if (!value.contains("${")) {
return value;
}
// remove ${ }
value = value.substring(2, value.length() - 1);
String fallback = lastResort;
String expression;
if (value.contains(":")) {
int i = value.indexOf(":");
expression = value.substring(0, i);
fallback = ((i + 1) < value.length()) ? value.substring(i + 1) : "";
} else {
expression = value;
}
String resolvedValue = null;
if (expression.equals(BIND_ADDRESS_MANAGEMENT_SYSPROP)) {
// special case: mgmt address can be specified via either -bmanagement= or -Djboss.bind.address.management=
resolvedValue = commandLine.getClassOption(BIND_ADDRESS_MANAGEMENT_OPTION);
} else if (expression.equals(DOMAIN_MASTER_ADDRESS_SYSPROP)) {
// special case: DC address can be specified via either --master-address= or -Djboss.domain.master.address=
resolvedValue = commandLine.getClassOption(MASTER_ADDRESS_OPTION);
} else if (expression.equals(DOMAIN_MASTER_PORT_SYSPROP)) {
// special case: DC port can be specified via either --master-port= or -Djboss.domain.master.port=
resolvedValue = commandLine.getClassOption(MASTER_PORT_OPTION);
}
if (resolvedValue == null) {
resolvedValue = commandLine.getSystemProperties().get(expression);
}
if (resolvedValue == null) {
resolvedValue = fallback;
}
return resolvedValue;
}
}