mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
framework/cluster: improve cluster service and integration API service
- mTLS implementation for cluster service communication - Listen only on the specified cluster node IP address instead of all interfaces - Validate incoming cluster service requests are from peer management servers based on the server's certificate dns name which can be through global config - ca.framework.cert.management.custom.san - Hardening of KVM command wrapper script execution - Improve API server integration port check - cloudstack-management.default: don't have JMX configuration if not needed. JMX is used for instrumentation; users who need to use it should enable it explicitly Co-authored-by: Abhishek Kumar <abhishek.mrt22@gmail.com> Co-authored-by: Wei Zhou <weizhou@apache.org> Co-authored-by: Rohit Yadav <rohit.yadav@shapeblue.com> Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
parent
eb1a0ef7f2
commit
beebeed5e2
@ -77,6 +77,14 @@ public interface CAManager extends CAService, Configurable, PluggableService {
|
||||
"15",
|
||||
"The number of days before expiry of a client certificate, the validations are checked. Admins are alerted when auto-renewal is not allowed, otherwise auto-renewal is attempted.", true, ConfigKey.Scope.Cluster);
|
||||
|
||||
|
||||
ConfigKey<String> CertManagementCustomSubjectAlternativeName = new ConfigKey<>("Advanced", String.class,
|
||||
"ca.framework.cert.management.custom.san",
|
||||
"cloudstack.internal",
|
||||
"The custom Subject Alternative Name that will be added to the management server certificate. " +
|
||||
"The actual implementation will depend on the configured CA provider.",
|
||||
false);
|
||||
|
||||
/**
|
||||
* Returns a list of available CA provider plugins
|
||||
* @return returns list of CAProvider
|
||||
|
||||
@ -19,9 +19,12 @@
|
||||
|
||||
package com.cloud.resource;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.Command;
|
||||
import org.apache.log4j.Logger;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
public abstract class CommandWrapper<T extends Command, A extends Answer, R extends ServerResource> {
|
||||
protected Logger logger = Logger.getLogger(getClass());
|
||||
@ -32,4 +35,26 @@ public abstract class CommandWrapper<T extends Command, A extends Answer, R exte
|
||||
* @return A and the Answer from the command.
|
||||
*/
|
||||
public abstract A execute(T command, R serverResource);
|
||||
|
||||
protected String sanitizeBashCommandArgument(String input) {
|
||||
StringBuilder sanitized = new StringBuilder();
|
||||
for (char c : input.toCharArray()) {
|
||||
if ("\\\"'`$|&;()<>*?![]{}~".indexOf(c) != -1) {
|
||||
sanitized.append('\\');
|
||||
}
|
||||
sanitized.append(c);
|
||||
}
|
||||
return sanitized.toString();
|
||||
}
|
||||
|
||||
public void removeDpdkPort(String portToRemove) {
|
||||
logger.debug("Removing DPDK port: " + portToRemove);
|
||||
int port;
|
||||
try {
|
||||
port = Integer.valueOf(portToRemove);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new CloudRuntimeException(String.format("Invalid DPDK port specified: '%s'", portToRemove));
|
||||
}
|
||||
Script.executeCommand("ovs-vsctl", "del-port", String.valueOf(port));
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -45,6 +46,7 @@ public interface CAProvider {
|
||||
|
||||
/**
|
||||
* Issues certificate with provided options
|
||||
*
|
||||
* @param domainNames
|
||||
* @param ipAddresses
|
||||
* @param validityDays
|
||||
@ -104,4 +106,6 @@ public interface CAProvider {
|
||||
* @return returns description
|
||||
*/
|
||||
String getDescription();
|
||||
|
||||
boolean isManagementCertificate(java.security.cert.Certificate certificate) throws CertificateParsingException;
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
@ -47,4 +48,6 @@ public interface CAService {
|
||||
* @return returns char[] passphrase
|
||||
*/
|
||||
char[] getKeyStorePassphrase();
|
||||
|
||||
boolean isManagementCertificate(java.security.cert.Certificate certificate) throws CertificateParsingException;
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@
|
||||
// under the License.
|
||||
package com.cloud.cluster;
|
||||
|
||||
import org.apache.cloudstack.management.ManagementServerHost;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.management.ManagementServerHost;
|
||||
|
||||
import com.cloud.utils.component.Manager;
|
||||
|
||||
@ -77,6 +77,8 @@ public interface ClusterManager extends Manager {
|
||||
*/
|
||||
String getSelfPeerName();
|
||||
|
||||
String getSelfNodeIP();
|
||||
|
||||
long getManagementNodeId();
|
||||
|
||||
/**
|
||||
|
||||
@ -40,17 +40,17 @@ import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import com.cloud.cluster.dao.ManagementServerStatusDao;
|
||||
import org.apache.cloudstack.management.ManagementServerHost;
|
||||
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.cloudstack.management.ManagementServerHost;
|
||||
import org.apache.cloudstack.utils.identity.ManagementServerNode;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.cluster.dao.ManagementServerHostDao;
|
||||
import com.cloud.cluster.dao.ManagementServerHostPeerDao;
|
||||
import com.cloud.cluster.dao.ManagementServerStatusDao;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import com.cloud.utils.Profiler;
|
||||
import com.cloud.utils.component.ComponentLifecycle;
|
||||
@ -130,7 +130,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
// recursive remote calls between nodes
|
||||
//
|
||||
_executor = Executors.newCachedThreadPool(new NamedThreadFactory("Cluster-Worker"));
|
||||
setRunLevel(ComponentLifecycle.RUN_LEVEL_FRAMEWORK);
|
||||
setRunLevel(ComponentLifecycle.RUN_LEVEL_COMPONENT);
|
||||
}
|
||||
|
||||
private void registerRequestPdu(final ClusterServiceRequestPdu pdu) {
|
||||
@ -475,6 +475,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
return Long.toString(_msId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSelfNodeIP() {
|
||||
return _clusterNodeIP;
|
||||
}
|
||||
|
||||
@ -28,7 +28,5 @@ public interface ClusterServiceAdapter extends Adapter {
|
||||
|
||||
public ClusterService getPeerService(String strPeer) throws RemoteException;
|
||||
|
||||
public String getServiceEndpointName(String strPeer);
|
||||
|
||||
public int getServicePort();
|
||||
}
|
||||
|
||||
@ -23,8 +23,9 @@ import java.util.Properties;
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.cluster.dao.ManagementServerHostDao;
|
||||
import com.cloud.utils.NumbersUtil;
|
||||
@ -44,6 +45,8 @@ public class ClusterServiceServletAdapter extends AdapterBase implements Cluster
|
||||
@Inject
|
||||
private ManagementServerHostDao _mshostDao;
|
||||
@Inject
|
||||
private CAManager caService;
|
||||
@Inject
|
||||
protected ConfigDepot _configDepot;
|
||||
|
||||
private ClusterServiceServletContainer _servletContainer;
|
||||
@ -51,7 +54,7 @@ public class ClusterServiceServletAdapter extends AdapterBase implements Cluster
|
||||
private int _clusterServicePort = DEFAULT_SERVICE_PORT;
|
||||
|
||||
public ClusterServiceServletAdapter() {
|
||||
setRunLevel(ComponentLifecycle.RUN_LEVEL_FRAMEWORK);
|
||||
setRunLevel(ComponentLifecycle.RUN_LEVEL_COMPONENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,12 +69,10 @@ public class ClusterServiceServletAdapter extends AdapterBase implements Cluster
|
||||
String serviceUrl = getServiceEndpointName(strPeer);
|
||||
if (serviceUrl == null)
|
||||
return null;
|
||||
|
||||
return new ClusterServiceServletImpl(serviceUrl);
|
||||
return new ClusterServiceServletImpl(serviceUrl, caService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceEndpointName(String strPeer) {
|
||||
protected String getServiceEndpointName(String strPeer) {
|
||||
try {
|
||||
init();
|
||||
} catch (ConfigurationException e) {
|
||||
@ -95,7 +96,7 @@ public class ClusterServiceServletAdapter extends AdapterBase implements Cluster
|
||||
|
||||
private String composeEndpointName(String nodeIP, int port) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("http://").append(nodeIP).append(":").append(port).append("/clusterservice");
|
||||
sb.append("https://").append(nodeIP).append(":").append(port).append("/clusterservice");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@ -108,7 +109,8 @@ public class ClusterServiceServletAdapter extends AdapterBase implements Cluster
|
||||
@Override
|
||||
public boolean start() {
|
||||
_servletContainer = new ClusterServiceServletContainer();
|
||||
_servletContainer.start(new ClusterServiceServletHttpHandler(_manager), _clusterServicePort);
|
||||
_servletContainer.start(new ClusterServiceServletHttpHandler(_manager), _manager.getSelfNodeIP(),
|
||||
_clusterServicePort, caService);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -17,11 +17,23 @@
|
||||
package com.cloud.cluster;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import org.apache.cloudstack.framework.ca.CAService;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.http.ConnectionClosedException;
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.impl.DefaultConnectionReuseStrategy;
|
||||
@ -43,9 +55,9 @@ import org.apache.http.protocol.ResponseDate;
|
||||
import org.apache.http.protocol.ResponseServer;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.concurrency.NamedThreadFactory;
|
||||
import com.cloud.utils.nio.Link;
|
||||
|
||||
public class ClusterServiceServletContainer {
|
||||
private static final Logger s_logger = Logger.getLogger(ClusterServiceServletContainer.class);
|
||||
@ -55,9 +67,9 @@ public class ClusterServiceServletContainer {
|
||||
public ClusterServiceServletContainer() {
|
||||
}
|
||||
|
||||
public boolean start(HttpRequestHandler requestHandler, int port) {
|
||||
public boolean start(HttpRequestHandler requestHandler, String ip, int port, CAService caService) {
|
||||
|
||||
listenerThread = new ListenerThread(requestHandler, port);
|
||||
listenerThread = new ListenerThread(requestHandler, ip, port, caService);
|
||||
listenerThread.start();
|
||||
|
||||
return true;
|
||||
@ -69,24 +81,43 @@ public class ClusterServiceServletContainer {
|
||||
}
|
||||
}
|
||||
|
||||
static class ListenerThread extends Thread {
|
||||
private HttpService _httpService = null;
|
||||
private volatile ServerSocket _serverSocket = null;
|
||||
private HttpParams _params = null;
|
||||
private ExecutorService _executor;
|
||||
protected static SSLServerSocket getSecuredServerSocket(SSLContext sslContext, String ip, int port)
|
||||
throws IOException {
|
||||
SSLServerSocketFactory sslFactory = sslContext.getServerSocketFactory();
|
||||
SSLServerSocket serverSocket = null;
|
||||
if (StringUtils.isNotEmpty(ip)) {
|
||||
serverSocket = (SSLServerSocket) sslFactory.createServerSocket(port, 0,
|
||||
InetAddress.getByName(ip));
|
||||
} else {
|
||||
serverSocket = (SSLServerSocket) sslFactory.createServerSocket(port);
|
||||
}
|
||||
serverSocket.setNeedClientAuth(true);
|
||||
return serverSocket;
|
||||
}
|
||||
|
||||
public ListenerThread(HttpRequestHandler requestHandler, int port) {
|
||||
_executor = Executors.newCachedThreadPool(new NamedThreadFactory("Cluster-Listener"));
|
||||
static class ListenerThread extends Thread {
|
||||
private HttpService httpService = null;
|
||||
private volatile SSLServerSocket serverSocket = null;
|
||||
private HttpParams params = null;
|
||||
private ExecutorService executor;
|
||||
private CAService caService = null;
|
||||
|
||||
public ListenerThread(HttpRequestHandler requestHandler, String ip, int port,
|
||||
CAService caService) {
|
||||
this.executor = Executors.newCachedThreadPool(new NamedThreadFactory("Cluster-Listener"));
|
||||
this.caService = caService;
|
||||
|
||||
try {
|
||||
_serverSocket = new ServerSocket(port);
|
||||
} catch (IOException ioex) {
|
||||
s_logger.error("error initializing cluster service servlet container", ioex);
|
||||
SSLContext sslContext = Link.initManagementSSLContext(caService);
|
||||
serverSocket = getSecuredServerSocket(sslContext, ip, port);
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
s_logger.error("Error initializing cluster service servlet container for secure connection",
|
||||
e);
|
||||
return;
|
||||
}
|
||||
|
||||
_params = new BasicHttpParams();
|
||||
_params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
|
||||
params = new BasicHttpParams();
|
||||
params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
|
||||
.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
|
||||
.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
|
||||
.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
|
||||
@ -104,35 +135,55 @@ public class ClusterServiceServletContainer {
|
||||
reqistry.register("/clusterservice", requestHandler);
|
||||
|
||||
// Set up the HTTP service
|
||||
_httpService = new HttpService(httpproc, new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory());
|
||||
_httpService.setParams(_params);
|
||||
_httpService.setHandlerResolver(reqistry);
|
||||
httpService = new HttpService(httpproc, new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory());
|
||||
httpService.setParams(params);
|
||||
httpService.setHandlerResolver(reqistry);
|
||||
}
|
||||
|
||||
public void stopRunning() {
|
||||
if (_serverSocket != null) {
|
||||
if (serverSocket != null) {
|
||||
try {
|
||||
_serverSocket.close();
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {
|
||||
s_logger.info("[ignored] error on closing server socket", e);
|
||||
}
|
||||
_serverSocket = null;
|
||||
serverSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isValidPeerConnection(Socket socket) throws SSLPeerUnverifiedException,
|
||||
CertificateParsingException {
|
||||
SSLSocket sslSocket = (SSLSocket) socket;
|
||||
SSLSession session = sslSocket.getSession();
|
||||
if (session == null || !session.isValid()) {
|
||||
return false;
|
||||
}
|
||||
Certificate[] certs = session.getPeerCertificates();
|
||||
if (certs == null || certs.length < 1) {
|
||||
return false;
|
||||
}
|
||||
return caService.isManagementCertificate(certs[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (s_logger.isInfoEnabled())
|
||||
s_logger.info("Cluster service servlet container listening on port " + _serverSocket.getLocalPort());
|
||||
s_logger.info(String.format("Cluster service servlet container listening on host: %s and port %d",
|
||||
serverSocket.getInetAddress().getHostAddress(), serverSocket.getLocalPort()));
|
||||
|
||||
while (_serverSocket != null) {
|
||||
while (serverSocket != null) {
|
||||
try {
|
||||
// Set up HTTP connection
|
||||
Socket socket = _serverSocket.accept();
|
||||
Socket socket = serverSocket.accept();
|
||||
final DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
|
||||
conn.bind(socket, _params);
|
||||
|
||||
_executor.execute(new ManagedContextRunnable() {
|
||||
conn.bind(socket, params);
|
||||
if (!isValidPeerConnection(socket)) {
|
||||
s_logger.warn(String.format("Failure during validating cluster request from %s",
|
||||
socket.getInetAddress().getHostAddress()));
|
||||
conn.shutdown();
|
||||
continue;
|
||||
}
|
||||
executor.execute(new ManagedContextRunnable() {
|
||||
@Override
|
||||
protected void runInContext() {
|
||||
HttpContext context = new BasicHttpContext(null);
|
||||
@ -141,7 +192,7 @@ public class ClusterServiceServletContainer {
|
||||
if (s_logger.isTraceEnabled())
|
||||
s_logger.trace("dispatching cluster request from " + conn.getRemoteAddress().toString());
|
||||
|
||||
_httpService.handleRequest(conn, context);
|
||||
httpService.handleRequest(conn, context);
|
||||
|
||||
if (s_logger.isTraceEnabled())
|
||||
s_logger.trace("Cluster request from " + conn.getRemoteAddress().toString() + " is processed");
|
||||
@ -176,7 +227,7 @@ public class ClusterServiceServletContainer {
|
||||
}
|
||||
}
|
||||
|
||||
_executor.shutdown();
|
||||
executor.shutdown();
|
||||
if (s_logger.isInfoEnabled())
|
||||
s_logger.info("Cluster service servlet container shutdown");
|
||||
}
|
||||
|
||||
@ -17,98 +17,143 @@
|
||||
package com.cloud.cluster;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.rmi.RemoteException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.commons.httpclient.HttpException;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.cloudstack.framework.ca.CAService;
|
||||
import org.apache.commons.httpclient.HttpStatus;
|
||||
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
|
||||
import org.apache.commons.httpclient.methods.PostMethod;
|
||||
import org.apache.commons.httpclient.params.HttpClientParams;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.utils.HttpUtils;
|
||||
import com.cloud.utils.Profiler;
|
||||
import com.cloud.utils.nio.Link;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
public class ClusterServiceServletImpl implements ClusterService {
|
||||
private static final long serialVersionUID = 4574025200012566153L;
|
||||
private static final Logger s_logger = Logger.getLogger(ClusterServiceServletImpl.class);
|
||||
|
||||
private String _serviceUrl;
|
||||
private String serviceUrl;
|
||||
|
||||
protected static HttpClient s_client = null;
|
||||
private CAService caService;
|
||||
|
||||
private Gson gson = new Gson();
|
||||
|
||||
protected static CloseableHttpClient s_client = null;
|
||||
|
||||
private void logPostParametersForFailedEncoding(List<NameValuePair> parameters) {
|
||||
if (s_logger.isTraceEnabled()) {
|
||||
s_logger.trace(String.format("%s encoding failed for POST parameters: %s", HttpUtils.UTF_8,
|
||||
gson.toJson(parameters)));
|
||||
}
|
||||
}
|
||||
|
||||
public ClusterServiceServletImpl() {
|
||||
}
|
||||
|
||||
public ClusterServiceServletImpl(final String serviceUrl) {
|
||||
s_logger.info("Setup cluster service servlet. service url: " + serviceUrl + ", request timeout: " + ClusterServiceAdapter.ClusterMessageTimeOut.value() +
|
||||
" seconds");
|
||||
public ClusterServiceServletImpl(final String serviceUrl, final CAService caService) {
|
||||
s_logger.info(String.format("Setup cluster service servlet. service url: %s, request timeout: %d seconds", serviceUrl,
|
||||
ClusterServiceAdapter.ClusterMessageTimeOut.value()));
|
||||
this.serviceUrl = serviceUrl;
|
||||
this.caService = caService;
|
||||
}
|
||||
|
||||
_serviceUrl = serviceUrl;
|
||||
protected List<NameValuePair> getClusterServicePduPostParameters(final ClusterServicePdu pdu) {
|
||||
List<NameValuePair> postParameters = new ArrayList<>();
|
||||
postParameters.add(new BasicNameValuePair("method", Integer.toString(RemoteMethodConstants.METHOD_DELIVER_PDU)));
|
||||
postParameters.add(new BasicNameValuePair("sourcePeer", pdu.getSourcePeer()));
|
||||
postParameters.add(new BasicNameValuePair("destPeer", pdu.getDestPeer()));
|
||||
postParameters.add(new BasicNameValuePair("pduSeq", Long.toString(pdu.getSequenceId())));
|
||||
postParameters.add(new BasicNameValuePair("pduAckSeq", Long.toString(pdu.getAckSequenceId())));
|
||||
postParameters.add(new BasicNameValuePair("agentId", Long.toString(pdu.getAgentId())));
|
||||
postParameters.add(new BasicNameValuePair("gsonPackage", pdu.getJsonPackage()));
|
||||
postParameters.add(new BasicNameValuePair("stopOnError", pdu.isStopOnError() ? "1" : "0"));
|
||||
postParameters.add(new BasicNameValuePair("pduType", Integer.toString(pdu.getPduType())));
|
||||
return postParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String execute(final ClusterServicePdu pdu) throws RemoteException {
|
||||
|
||||
final HttpClient client = getHttpClient();
|
||||
final PostMethod method = new PostMethod(_serviceUrl);
|
||||
|
||||
method.addParameter("method", Integer.toString(RemoteMethodConstants.METHOD_DELIVER_PDU));
|
||||
method.addParameter("sourcePeer", pdu.getSourcePeer());
|
||||
method.addParameter("destPeer", pdu.getDestPeer());
|
||||
method.addParameter("pduSeq", Long.toString(pdu.getSequenceId()));
|
||||
method.addParameter("pduAckSeq", Long.toString(pdu.getAckSequenceId()));
|
||||
method.addParameter("agentId", Long.toString(pdu.getAgentId()));
|
||||
method.addParameter("gsonPackage", pdu.getJsonPackage());
|
||||
method.addParameter("stopOnError", pdu.isStopOnError() ? "1" : "0");
|
||||
method.addParameter("pduType", Integer.toString(pdu.getPduType()));
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug(String.format("Executing ClusterServicePdu with service URL: %s", serviceUrl));
|
||||
}
|
||||
final CloseableHttpClient client = getHttpClient();
|
||||
final HttpPost method = new HttpPost(serviceUrl);
|
||||
final List<NameValuePair> postParameters = getClusterServicePduPostParameters(pdu);
|
||||
try {
|
||||
method.setEntity(new UrlEncodedFormEntity(postParameters, HttpUtils.UTF_8));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
s_logger.error("Failed to encode request POST parameters", e);
|
||||
logPostParametersForFailedEncoding(postParameters);
|
||||
throw new RemoteException("Failed to encode request POST parameters", e);
|
||||
}
|
||||
|
||||
return executePostMethod(client, method);
|
||||
}
|
||||
|
||||
protected List<NameValuePair> getPingPostParameters(final String callingPeer) {
|
||||
List<NameValuePair> postParameters = new ArrayList<>();
|
||||
postParameters.add(new BasicNameValuePair("method", Integer.toString(RemoteMethodConstants.METHOD_PING)));
|
||||
postParameters.add(new BasicNameValuePair("callingPeer", callingPeer));
|
||||
return postParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ping(final String callingPeer) throws RemoteException {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Ping at " + _serviceUrl);
|
||||
s_logger.debug("Ping at " + serviceUrl);
|
||||
}
|
||||
|
||||
final HttpClient client = getHttpClient();
|
||||
final PostMethod method = new PostMethod(_serviceUrl);
|
||||
final CloseableHttpClient client = getHttpClient();
|
||||
final HttpPost method = new HttpPost(serviceUrl);
|
||||
|
||||
method.addParameter("method", Integer.toString(RemoteMethodConstants.METHOD_PING));
|
||||
method.addParameter("callingPeer", callingPeer);
|
||||
List<NameValuePair> postParameters = getPingPostParameters(callingPeer);
|
||||
try {
|
||||
method.setEntity(new UrlEncodedFormEntity(postParameters, HttpUtils.UTF_8));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
s_logger.error("Failed to encode ping request POST parameters", e);
|
||||
logPostParametersForFailedEncoding(postParameters);
|
||||
throw new RemoteException("Failed to encode ping request POST parameters", e);
|
||||
}
|
||||
|
||||
final String returnVal = executePostMethod(client, method);
|
||||
if ("true".equalsIgnoreCase(returnVal)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return Boolean.TRUE.toString().equalsIgnoreCase(returnVal);
|
||||
}
|
||||
|
||||
private String executePostMethod(final HttpClient client, final PostMethod method) {
|
||||
int response = 0;
|
||||
private String executePostMethod(final CloseableHttpClient client, final HttpPost method) {
|
||||
String result = null;
|
||||
try {
|
||||
final Profiler profiler = new Profiler();
|
||||
profiler.start();
|
||||
response = client.executeMethod(method);
|
||||
CloseableHttpResponse httpResponse = client.execute(method);
|
||||
int response = httpResponse.getStatusLine().getStatusCode();
|
||||
if (response == HttpStatus.SC_OK) {
|
||||
result = method.getResponseBodyAsString();
|
||||
result = EntityUtils.toString(httpResponse.getEntity());
|
||||
profiler.stop();
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("POST " + _serviceUrl + " response :" + result + ", responding time: " + profiler.getDurationInMillis() + " ms");
|
||||
s_logger.debug("POST " + serviceUrl + " response :" + result + ", responding time: " + profiler.getDurationInMillis() + " ms");
|
||||
}
|
||||
} else {
|
||||
profiler.stop();
|
||||
s_logger.error("Invalid response code : " + response + ", from : " + _serviceUrl + ", method : " + method.getParameter("method") + " responding time: " +
|
||||
s_logger.error("Invalid response code : " + response + ", from : " + serviceUrl + ", method : " + method.getParams().getParameter("method") + " responding time: " +
|
||||
profiler.getDurationInMillis());
|
||||
}
|
||||
} catch (final HttpException e) {
|
||||
s_logger.error("HttpException from : " + _serviceUrl + ", method : " + method.getParameter("method"));
|
||||
} catch (final IOException e) {
|
||||
s_logger.error("IOException from : " + _serviceUrl + ", method : " + method.getParameter("method"));
|
||||
} catch (final Throwable e) {
|
||||
s_logger.error("Exception from : " + _serviceUrl + ", method : " + method.getParameter("method") + ", exception :", e);
|
||||
} catch (IOException e) {
|
||||
s_logger.error("Exception from : " + serviceUrl + ", method : " + method.getParams().getParameter("method") + ", exception :", e);
|
||||
} finally {
|
||||
method.releaseConnection();
|
||||
}
|
||||
@ -116,20 +161,25 @@ public class ClusterServiceServletImpl implements ClusterService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private HttpClient getHttpClient() {
|
||||
|
||||
private CloseableHttpClient getHttpClient() {
|
||||
if (s_client == null) {
|
||||
final MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager();
|
||||
mgr.getParams().setDefaultMaxConnectionsPerHost(4);
|
||||
SSLContext sslContext = null;
|
||||
try {
|
||||
sslContext = Link.initManagementSSLContext(caService);
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// TODO make it configurable
|
||||
mgr.getParams().setMaxTotalConnections(1000);
|
||||
int timeout = ClusterServiceAdapter.ClusterMessageTimeOut.value() * 1000;
|
||||
RequestConfig config = RequestConfig.custom()
|
||||
.setConnectTimeout(timeout)
|
||||
.setConnectionRequestTimeout(timeout)
|
||||
.setSocketTimeout(timeout).build();
|
||||
|
||||
s_client = new HttpClient(mgr);
|
||||
final HttpClientParams clientParams = new HttpClientParams();
|
||||
clientParams.setSoTimeout(ClusterServiceAdapter.ClusterMessageTimeOut.value() * 1000);
|
||||
|
||||
s_client.setParams(clientParams);
|
||||
s_client = HttpClientBuilder.create()
|
||||
.setDefaultRequestConfig(config)
|
||||
.setSSLContext(sslContext)
|
||||
.build();
|
||||
}
|
||||
return s_client;
|
||||
}
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package com.cloud.cluster;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ClusterManagerImplTest {
|
||||
@InjectMocks
|
||||
ClusterManagerImpl clusterManager = new ClusterManagerImpl();
|
||||
|
||||
@Test
|
||||
public void testGetSelfNodeIP() {
|
||||
String ip = "1.2.3.4";
|
||||
ReflectionTestUtils.setField(clusterManager, "_clusterNodeIP", ip);
|
||||
Assert.assertEquals(ip, clusterManager.getSelfNodeIP());
|
||||
}
|
||||
}
|
||||
@ -50,7 +50,7 @@ public class ClusterServiceServletAdapterTest {
|
||||
@Test
|
||||
public void testRunLevel() {
|
||||
int runLevel = clusterServiceServletAdapter.getRunLevel();
|
||||
assertTrue(runLevel == ComponentLifecycle.RUN_LEVEL_FRAMEWORK);
|
||||
assertTrue(runLevel == ComponentLifecycle.RUN_LEVEL_COMPONENT);
|
||||
assertTrue(runLevel == clusterManagerImpl.getRunLevel());
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
package com.cloud.cluster;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLContextSpi;
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.cloud.utils.StringUtils;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ClusterServiceServletContainerTest {
|
||||
|
||||
private void runGetSecuredServerSocket(String ip) {
|
||||
SSLContext sslContext = Mockito.mock(SSLContext.class);
|
||||
SSLContextSpi sslContextSpi = Mockito.mock(SSLContextSpi.class);
|
||||
ReflectionTestUtils.setField(sslContext, "contextSpi", sslContextSpi);
|
||||
SSLServerSocketFactory factory = Mockito.mock(SSLServerSocketFactory.class);
|
||||
Mockito.when(sslContext.getServerSocketFactory()).thenReturn(factory);
|
||||
int port = 9090;
|
||||
final List<Boolean> socketNeedClientAuth = new ArrayList<>();
|
||||
try {
|
||||
SSLServerSocket socketMock = Mockito.mock(SSLServerSocket.class);
|
||||
if (StringUtils.isBlank(ip)) {
|
||||
Mockito.when(factory.createServerSocket(port)).thenReturn(socketMock);
|
||||
} else {
|
||||
Mockito.when(factory.createServerSocket(Mockito.anyInt(), Mockito.anyInt(),
|
||||
Mockito.any(InetAddress.class))).thenReturn(socketMock);
|
||||
}
|
||||
Mockito.doAnswer((Answer<Void>) invocationOnMock -> {
|
||||
boolean needClientAuth = (boolean) invocationOnMock.getArguments()[0];
|
||||
socketNeedClientAuth.add(needClientAuth);
|
||||
return null;
|
||||
}).when(socketMock).setNeedClientAuth(Mockito.anyBoolean());
|
||||
SSLServerSocket socket = ClusterServiceServletContainer.getSecuredServerSocket(sslContext, ip, 9090);
|
||||
if (StringUtils.isBlank(ip)) {
|
||||
Mockito.verify(factory, Mockito.times(1)).createServerSocket(port);
|
||||
} else {
|
||||
Mockito.verify(factory, Mockito.times(1)).createServerSocket(port, 0, InetAddress.getByName(ip));
|
||||
}
|
||||
Mockito.verify(socket, Mockito.times(1)).setNeedClientAuth(Mockito.anyBoolean());
|
||||
Assert.assertTrue(CollectionUtils.isNotEmpty(socketNeedClientAuth));
|
||||
Assert.assertTrue(socketNeedClientAuth.get(0));
|
||||
} catch (IOException e) {
|
||||
Assert.fail("Exception occurred: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSecuredServerSocketNoIp() {
|
||||
runGetSecuredServerSocket("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSecuredServerSocketIp() {
|
||||
runGetSecuredServerSocket("1.2.3.4");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package com.cloud.cluster;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ClusterServiceServletImplTest {
|
||||
|
||||
@InjectMocks
|
||||
ClusterServiceServletImpl clusterServiceServlet = new ClusterServiceServletImpl();
|
||||
|
||||
@Test
|
||||
public void testClusterServicePduPostParameters() {
|
||||
List<NameValuePair> parameters =
|
||||
clusterServiceServlet.getClusterServicePduPostParameters(Mockito.mock(ClusterServicePdu.class));
|
||||
Assert.assertTrue(CollectionUtils.isNotEmpty(parameters));
|
||||
Optional<NameValuePair> opt = parameters.stream().filter(x -> x.getName().equals("method")).findFirst();
|
||||
Assert.assertTrue(opt.isPresent());
|
||||
NameValuePair val = opt.get();
|
||||
Assert.assertEquals(Integer.toString(RemoteMethodConstants.METHOD_DELIVER_PDU), val.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPingPostParameters() {
|
||||
String peer = "1.2.3.4";
|
||||
List<NameValuePair> parameters =
|
||||
clusterServiceServlet.getPingPostParameters(peer);
|
||||
Assert.assertTrue(CollectionUtils.isNotEmpty(parameters));
|
||||
Optional<NameValuePair> opt = parameters.stream().filter(x -> x.getName().equals("method")).findFirst();
|
||||
Assert.assertTrue(opt.isPresent());
|
||||
NameValuePair val = opt.get();
|
||||
Assert.assertEquals(Integer.toString(RemoteMethodConstants.METHOD_PING), val.getValue());
|
||||
opt = parameters.stream().filter(x -> x.getName().equals("callingPeer")).findFirst();
|
||||
Assert.assertTrue(opt.isPresent());
|
||||
val = opt.get();
|
||||
Assert.assertEquals(peer, val.getValue());
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,7 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
JAVA_OPTS="-Djava.security.properties=/etc/cloudstack/management/java.security.ciphers -Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2G -XX:+UseParallelGC -XX:MaxGCPauseMillis=500 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:ErrorFile=/var/log/cloudstack/management/cloudstack-management.err "
|
||||
JAVA_OPTS="-Djava.security.properties=/etc/cloudstack/management/java.security.ciphers -Djava.awt.headless=true -Xmx2G -XX:+UseParallelGC -XX:MaxGCPauseMillis=500 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:ErrorFile=/var/log/cloudstack/management/cloudstack-management.err "
|
||||
|
||||
CLASSPATH="/usr/share/cloudstack-management/lib/*:/etc/cloudstack/management:/usr/share/cloudstack-common:/usr/share/cloudstack-management/setup:/usr/share/cloudstack-management:/usr/share/java/mysql-connector-java.jar:/usr/share/cloudstack-mysql-ha/lib/*"
|
||||
|
||||
|
||||
@ -33,9 +33,11 @@ import java.security.Security;
|
||||
import java.security.SignatureException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -57,6 +59,7 @@ import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.bouncycastle.asn1.pkcs.Attribute;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
@ -127,6 +130,8 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
|
||||
"true",
|
||||
"When set to true, it will allow expired client certificate during SSL handshake.", true);
|
||||
|
||||
private static String managementCertificateCustomSAN;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
/////////////// Root CA Private Methods ///////////////////
|
||||
@ -365,8 +370,11 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
|
||||
if (managementKeyStore != null) {
|
||||
return true;
|
||||
}
|
||||
final Certificate serverCertificate = issueCertificate(Collections.singletonList(NetUtils.getHostName()),
|
||||
NetUtils.getAllDefaultNicIps(), getCaValidityDays());
|
||||
List<String> domainNames = new ArrayList<>();
|
||||
domainNames.add(NetUtils.getHostName());
|
||||
domainNames.add(CAManager.CertManagementCustomSubjectAlternativeName.value());
|
||||
final Certificate serverCertificate = issueCertificate(
|
||||
domainNames, NetUtils.getAllDefaultNicIps(), getCaValidityDays());
|
||||
if (serverCertificate == null || serverCertificate.getPrivateKey() == null) {
|
||||
throw new CloudRuntimeException("Failed to generate management server certificate and load management server keystore");
|
||||
}
|
||||
@ -402,6 +410,7 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
managementCertificateCustomSAN = CAManager.CertManagementCustomSubjectAlternativeName.value();
|
||||
return loadRootCAKeyPair() && loadRootCAKeyPair() && loadManagementKeyStore();
|
||||
}
|
||||
|
||||
@ -456,4 +465,26 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
|
||||
public String getDescription() {
|
||||
return "CloudStack's Root CA provider plugin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isManagementCertificate(java.security.cert.Certificate certificate) throws CertificateParsingException {
|
||||
if (!(certificate instanceof X509Certificate)) {
|
||||
return false;
|
||||
}
|
||||
X509Certificate x509Certificate = (X509Certificate) certificate;
|
||||
|
||||
// Check for alternative names
|
||||
Collection<List<?>> altNames = x509Certificate.getSubjectAlternativeNames();
|
||||
if (CollectionUtils.isEmpty(altNames)) {
|
||||
return false;
|
||||
}
|
||||
for (List<?> altName : altNames) {
|
||||
int type = (Integer) altName.get(0);
|
||||
String name = (String) altName.get(1);
|
||||
if (type == GeneralName.dNSName && managementCertificateCustomSAN.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,8 +26,13 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
@ -35,15 +40,16 @@ import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.apache.cloudstack.utils.security.SSLUtils;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@ -150,4 +156,56 @@ public class RootCAProviderTest {
|
||||
Assert.assertEquals(provider.getProviderName(), "root");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsManagementCertificateNotX509() {
|
||||
try {
|
||||
Assert.assertFalse(provider.isManagementCertificate(Mockito.mock(java.security.cert.Certificate.class)));
|
||||
} catch (CertificateParsingException e) {
|
||||
Assert.fail(String.format("Exception occurred: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsManagementCertificateNoAltNames() {
|
||||
try {
|
||||
X509Certificate certificate = Mockito.mock(X509Certificate.class);
|
||||
Mockito.when(certificate.getSubjectAlternativeNames()).thenReturn(new ArrayList<>());
|
||||
Assert.assertFalse(provider.isManagementCertificate(certificate));
|
||||
} catch (CertificateParsingException e) {
|
||||
Assert.fail(String.format("Exception occurred: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsManagementCertificateNoMatch() {
|
||||
ReflectionTestUtils.setField(provider, "managementCertificateCustomSAN", "cloudstack");
|
||||
try {
|
||||
X509Certificate certificate = Mockito.mock(X509Certificate.class);
|
||||
List<List<?>> altNames = new ArrayList<>();
|
||||
altNames.add(List.of(GeneralName.dNSName, UUID.randomUUID().toString()));
|
||||
altNames.add(List.of(GeneralName.dNSName, UUID.randomUUID().toString()));
|
||||
Collection<List<?>> collection = new ArrayList<>(altNames);
|
||||
Mockito.when(certificate.getSubjectAlternativeNames()).thenReturn(collection);
|
||||
Assert.assertFalse(provider.isManagementCertificate(certificate));
|
||||
} catch (CertificateParsingException e) {
|
||||
Assert.fail(String.format("Exception occurred: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsManagementCertificateMatch() {
|
||||
String customSAN = "cloudstack";
|
||||
ReflectionTestUtils.setField(provider, "managementCertificateCustomSAN", customSAN);
|
||||
try {
|
||||
X509Certificate certificate = Mockito.mock(X509Certificate.class);
|
||||
List<List<?>> altNames = new ArrayList<>();
|
||||
altNames.add(List.of(GeneralName.dNSName, customSAN));
|
||||
altNames.add(List.of(GeneralName.dNSName, UUID.randomUUID().toString()));
|
||||
Collection<List<?>> collection = new ArrayList<>(altNames);
|
||||
Mockito.when(certificate.getSubjectAlternativeNames()).thenReturn(collection);
|
||||
Assert.assertTrue(provider.isManagementCertificate(certificate));
|
||||
} catch (CertificateParsingException e) {
|
||||
Assert.fail(String.format("Exception occurred: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.log4j.Logger;
|
||||
@ -36,8 +39,8 @@ import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.Storage.ImageFormat;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
@ResourceWrapper(handles = DeleteVMSnapshotCommand.class)
|
||||
@ -96,12 +99,20 @@ public final class LibvirtDeleteVMSnapshotCommandWrapper extends CommandWrapper<
|
||||
PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO) rootVolume.getDataStore();
|
||||
KVMPhysicalDisk rootDisk = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(),
|
||||
primaryStore.getUuid(), rootVolume.getPath());
|
||||
String qemu_img_snapshot = Script.runSimpleBashScript("qemu-img snapshot -l " + rootDisk.getPath() + " | tail -n +3 | awk -F ' ' '{print $2}' | grep ^" + cmd.getTarget().getSnapshotName() + "$");
|
||||
String qemuImgPath = Script.getExecutableAbsolutePath("qemu-img");
|
||||
List<String[]> commands = new ArrayList<>();
|
||||
commands.add(new String[]{qemuImgPath, "snapshot", "-l", sanitizeBashCommandArgument(rootDisk.getPath())});
|
||||
commands.add(new String[]{Script.getExecutableAbsolutePath("tail"), "-n", "+3"});
|
||||
commands.add(new String[]{Script.getExecutableAbsolutePath("awk"), "-F", " ", "{print $2}"});
|
||||
commands.add(new String[]{Script.getExecutableAbsolutePath("grep"), "^" + sanitizeBashCommandArgument(cmd.getTarget().getSnapshotName()) + "$"});
|
||||
String qemu_img_snapshot = Script.executePipedCommands(commands, 0).second();
|
||||
if (qemu_img_snapshot == null) {
|
||||
s_logger.info("Cannot find snapshot " + cmd.getTarget().getSnapshotName() + " in file " + rootDisk.getPath() + ", return true");
|
||||
return new DeleteVMSnapshotAnswer(cmd, cmd.getVolumeTOs());
|
||||
}
|
||||
int result = Script.runSimpleBashScriptForExitValue("qemu-img snapshot -d " + cmd.getTarget().getSnapshotName() + " " + rootDisk.getPath());
|
||||
int result = Script.executeCommandForExitValue(qemuImgPath, "snapshot", "-d",
|
||||
sanitizeBashCommandArgument(cmd.getTarget().getSnapshotName()),
|
||||
sanitizeBashCommandArgument(rootDisk.getPath()));
|
||||
if (result != 0) {
|
||||
return new DeleteVMSnapshotAnswer(cmd, false,
|
||||
"Delete VM Snapshot Failed due to can not remove snapshot from image file " + rootDisk.getPath() + " : " + result);
|
||||
|
||||
@ -19,6 +19,11 @@
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.GetVmIpAddressCommand;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
@ -26,7 +31,6 @@ import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.utils.net.NetUtils;
|
||||
import com.cloud.utils.script.Script;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
@ResourceWrapper(handles = GetVmIpAddressCommand.class)
|
||||
public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper<GetVmIpAddressCommand, Answer, LibvirtComputingResource> {
|
||||
@ -37,31 +41,51 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper<Ge
|
||||
public Answer execute(final GetVmIpAddressCommand command, final LibvirtComputingResource libvirtComputingResource) {
|
||||
String ip = null;
|
||||
boolean result = false;
|
||||
String vmName = command.getVmName();
|
||||
String sanitizedVmName = sanitizeBashCommandArgument(vmName);
|
||||
String networkCidr = command.getVmNetworkCidr();
|
||||
List<String[]> commands = new ArrayList<>();
|
||||
final String virt_ls_path = Script.getExecutableAbsolutePath("virt-ls");
|
||||
final String virt_cat_path = Script.getExecutableAbsolutePath("virt-cat");
|
||||
final String virt_win_reg_path = Script.getExecutableAbsolutePath("virt-win-reg");
|
||||
final String tail_path = Script.getExecutableAbsolutePath("tail");
|
||||
final String grep_path = Script.getExecutableAbsolutePath("grep");
|
||||
final String awk_path = Script.getExecutableAbsolutePath("awk");
|
||||
final String sed_path = Script.getExecutableAbsolutePath("sed");
|
||||
if(!command.isWindows()) {
|
||||
//List all dhcp lease files inside guestVm
|
||||
String leasesList = Script.runSimpleBashScript(new StringBuilder().append("virt-ls ").append(command.getVmName())
|
||||
.append(" /var/lib/dhclient/ | grep .*\\*.leases").toString());
|
||||
commands.add(new String[]{virt_ls_path, sanitizedVmName, "/var/lib/dhclient/"});
|
||||
commands.add(new String[]{grep_path, ".*\\*.leases"});
|
||||
String leasesList = Script.executePipedCommands(commands, 0).second();
|
||||
if(leasesList != null) {
|
||||
String[] leasesFiles = leasesList.split("\n");
|
||||
for(String leaseFile : leasesFiles){
|
||||
//Read from each dhclient lease file inside guest Vm using virt-cat libguestfs ulitiy
|
||||
String ipAddr = Script.runSimpleBashScript(new StringBuilder().append("virt-cat ").append(command.getVmName())
|
||||
.append(" /var/lib/dhclient/" + leaseFile + " | tail -16 | grep 'fixed-address' | awk '{print $2}' | sed -e 's/;//'").toString());
|
||||
//Read from each dhclient lease file inside guest Vm using virt-cat libguestfs utility
|
||||
commands = new ArrayList<>();
|
||||
commands.add(new String[]{virt_cat_path, sanitizedVmName, "/var/lib/dhclient/" + leaseFile});
|
||||
commands.add(new String[]{tail_path, "-16"});
|
||||
commands.add(new String[]{grep_path, "fixed-address"});
|
||||
commands.add(new String[]{awk_path, "{print $2}"});
|
||||
commands.add(new String[]{sed_path, "-e", "s/;//"});
|
||||
String ipAddr = Script.executePipedCommands(commands, 0).second();
|
||||
// Check if the IP belongs to the network
|
||||
if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)){
|
||||
if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)) {
|
||||
ip = ipAddr;
|
||||
break;
|
||||
}
|
||||
s_logger.debug("GetVmIp: "+command.getVmName()+ " Ip: "+ipAddr+" does not belong to network "+networkCidr);
|
||||
s_logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For windows, read from guest Vm registry using virt-win-reg libguestfs ulitiy. Registry Path: HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Tcpip\Parameters\Interfaces\<service>\DhcpIPAddress
|
||||
String ipList = Script.runSimpleBashScript(new StringBuilder().append("virt-win-reg --unsafe-printable-strings ").append(command.getVmName())
|
||||
.append(" 'HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces' | grep DhcpIPAddress | awk -F : '{print $2}' | sed -e 's/^\"//' -e 's/\"$//'").toString());
|
||||
commands = new ArrayList<>();
|
||||
commands.add(new String[]{virt_win_reg_path, "--unsafe-printable-strings", sanitizedVmName, "HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces"});
|
||||
commands.add(new String[]{grep_path, "DhcpIPAddress"});
|
||||
commands.add(new String[]{awk_path, "-F", ":", "{print $2}"});
|
||||
commands.add(new String[]{sed_path, "-e", "s/^\"//", "-e", "s/\"$//"});
|
||||
String ipList = Script.executePipedCommands(commands, 0).second();
|
||||
if(ipList != null) {
|
||||
s_logger.debug("GetVmIp: "+command.getVmName()+ "Ips: "+ipList);
|
||||
s_logger.debug("GetVmIp: "+ vmName + "Ips: "+ipList);
|
||||
String[] ips = ipList.split("\n");
|
||||
for (String ipAddr : ips){
|
||||
// Check if the IP belongs to the network
|
||||
@ -69,13 +93,13 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper<Ge
|
||||
ip = ipAddr;
|
||||
break;
|
||||
}
|
||||
s_logger.debug("GetVmIp: "+command.getVmName()+ " Ip: "+ipAddr+" does not belong to network "+networkCidr);
|
||||
s_logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ip != null){
|
||||
result = true;
|
||||
s_logger.debug("GetVmIp: "+command.getVmName()+ " Found Ip: "+ip);
|
||||
s_logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip);
|
||||
}
|
||||
return new Answer(command, result, ip);
|
||||
}
|
||||
|
||||
@ -19,7 +19,12 @@
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
@ -28,33 +33,73 @@ import com.cloud.agent.api.OvsFetchInterfaceCommand;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.utils.script.Script;
|
||||
import com.cloud.utils.Ternary;
|
||||
|
||||
@ResourceWrapper(handles = OvsFetchInterfaceCommand.class)
|
||||
public final class LibvirtOvsFetchInterfaceCommandWrapper extends CommandWrapper<OvsFetchInterfaceCommand, Answer, LibvirtComputingResource> {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(LibvirtOvsFetchInterfaceCommandWrapper.class);
|
||||
|
||||
private String getSubnetMaskForAddress(NetworkInterface networkInterface, InetAddress inetAddress) {
|
||||
for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
|
||||
if (!inetAddress.equals(address.getAddress())) {
|
||||
continue;
|
||||
}
|
||||
int prefixLength = address.getNetworkPrefixLength();
|
||||
int mask = 0xffffffff << (32 - prefixLength);
|
||||
return String.format("%d.%d.%d.%d",
|
||||
(mask >>> 24) & 0xff,
|
||||
(mask >>> 16) & 0xff,
|
||||
(mask >>> 8) & 0xff,
|
||||
mask & 0xff);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getMacAddress(NetworkInterface networkInterface) throws SocketException {
|
||||
byte[] macBytes = networkInterface.getHardwareAddress();
|
||||
if (macBytes == null) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder macAddress = new StringBuilder();
|
||||
for (byte b : macBytes) {
|
||||
macAddress.append(String.format("%02X:", b));
|
||||
}
|
||||
if (macAddress.length() > 0) {
|
||||
macAddress.deleteCharAt(macAddress.length() - 1); // Remove trailing colon
|
||||
}
|
||||
return macAddress.toString();
|
||||
}
|
||||
|
||||
public Ternary<String, String, String> getInterfaceDetails(String interfaceName) throws SocketException {
|
||||
NetworkInterface networkInterface = NetworkInterface.getByName(interfaceName);
|
||||
if (networkInterface == null) {
|
||||
logger.warn(String.format("Network interface: '%s' not found", interfaceName));
|
||||
return new Ternary<>(null, null, null);
|
||||
}
|
||||
Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
|
||||
while (inetAddresses.hasMoreElements()) {
|
||||
InetAddress inetAddress = inetAddresses.nextElement();
|
||||
if (inetAddress instanceof java.net.Inet4Address) {
|
||||
String ipAddress = inetAddress.getHostAddress();
|
||||
String subnetMask = getSubnetMaskForAddress(networkInterface, inetAddress);
|
||||
String macAddress = getMacAddress(networkInterface);
|
||||
return new Ternary<>(ipAddress, subnetMask, macAddress);
|
||||
}
|
||||
}
|
||||
return new Ternary<>(null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer execute(final OvsFetchInterfaceCommand command, final LibvirtComputingResource libvirtComputingResource) {
|
||||
final String label = command.getLabel();
|
||||
final String label = "'" + command.getLabel() + "'";
|
||||
|
||||
s_logger.debug("Will look for network with name-label:" + label);
|
||||
try {
|
||||
String ipadd = Script.runSimpleBashScript("ifconfig " + label + " | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'");
|
||||
if (StringUtils.isEmpty(ipadd)) {
|
||||
ipadd = Script.runSimpleBashScript("ifconfig " + label + " | grep ' inet ' | awk '{ print $2}'");
|
||||
}
|
||||
String mask = Script.runSimpleBashScript("ifconfig " + label + " | grep 'inet addr:' | cut -d: -f4");
|
||||
if (StringUtils.isEmpty(mask)) {
|
||||
mask = Script.runSimpleBashScript("ifconfig " + label + " | grep ' inet ' | awk '{ print $4}'");
|
||||
}
|
||||
String mac = Script.runSimpleBashScript("ifconfig " + label + " | grep HWaddr | awk -F \" \" '{print $5}'");
|
||||
if (StringUtils.isEmpty(mac)) {
|
||||
mac = Script.runSimpleBashScript("ifconfig " + label + " | grep ' ether ' | awk '{ print $2}'");
|
||||
}
|
||||
Ternary<String, String, String> interfaceDetails = getInterfaceDetails(label);
|
||||
return new OvsFetchInterfaceAnswer(command, true, "Interface " + label
|
||||
+ " retrieved successfully", ipadd, mask, mac);
|
||||
+ " retrieved successfully", interfaceDetails.first(), interfaceDetails.second(),
|
||||
interfaceDetails.third());
|
||||
|
||||
} catch (final Exception e) {
|
||||
s_logger.warn("Caught execption when fetching interface", e);
|
||||
|
||||
@ -47,7 +47,6 @@ import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
@ResourceWrapper(handles = PrepareForMigrationCommand.class)
|
||||
public final class LibvirtPrepareForMigrationCommandWrapper extends CommandWrapper<PrepareForMigrationCommand, Answer, LibvirtComputingResource> {
|
||||
@ -126,9 +125,7 @@ public final class LibvirtPrepareForMigrationCommandWrapper extends CommandWrapp
|
||||
} catch (final LibvirtException | CloudRuntimeException | InternalErrorException | URISyntaxException e) {
|
||||
if (MapUtils.isNotEmpty(dpdkInterfaceMapping)) {
|
||||
for (DpdkTO to : dpdkInterfaceMapping.values()) {
|
||||
String cmd = String.format("ovs-vsctl del-port %s", to.getPort());
|
||||
s_logger.debug("Removing DPDK port: " + to.getPort());
|
||||
Script.runSimpleBashScript(cmd);
|
||||
removeDpdkPort(to.getPort());
|
||||
}
|
||||
}
|
||||
return new PrepareForMigrationAnswer(command, e.toString());
|
||||
|
||||
@ -19,9 +19,13 @@
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.ReadyAnswer;
|
||||
import com.cloud.agent.api.ReadyCommand;
|
||||
@ -31,8 +35,6 @@ import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
@ResourceWrapper(handles = ReadyCommand.class)
|
||||
public final class LibvirtReadyCommandWrapper extends CommandWrapper<ReadyCommand, Answer, LibvirtComputingResource> {
|
||||
|
||||
@ -50,12 +52,17 @@ public final class LibvirtReadyCommandWrapper extends CommandWrapper<ReadyComman
|
||||
}
|
||||
|
||||
private boolean hostSupportsUefi(boolean isUbuntuHost) {
|
||||
String cmd = "rpm -qa | grep -i ovmf";
|
||||
int result;
|
||||
if (isUbuntuHost) {
|
||||
cmd = "dpkg -l ovmf";
|
||||
s_logger.debug("Running command : dpkg -l ovmf");
|
||||
result = Script.executeCommandForExitValue(Script.getExecutableAbsolutePath("dpkg"), "-l", "ovmf");
|
||||
} else {
|
||||
s_logger.debug("Running command : rpm -qa | grep -i ovmf");
|
||||
List<String[]> commands = new ArrayList<>();
|
||||
commands.add(new String[]{Script.getExecutableAbsolutePath("rpm"), "-qa"});
|
||||
commands.add(new String[]{Script.getExecutableAbsolutePath("grep"), "-i", "ovmf"});
|
||||
result = Script.executePipedCommands(commands, 0).first();
|
||||
}
|
||||
s_logger.debug("Running command : " + cmd);
|
||||
int result = Script.runSimpleBashScriptForExitValue(cmd);
|
||||
s_logger.debug("Got result : " + result);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
@ -19,6 +19,15 @@
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
@ -26,14 +35,6 @@ import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.utils.PropertiesUtil;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.script.Script;
|
||||
import org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
@ResourceWrapper(handles = RevokeDirectDownloadCertificateCommand.class)
|
||||
public class LibvirtRevokeDirectDownloadCertificateWrapper extends CommandWrapper<RevokeDirectDownloadCertificateCommand, Answer, LibvirtComputingResource> {
|
||||
@ -84,17 +85,17 @@ public class LibvirtRevokeDirectDownloadCertificateWrapper extends CommandWrappe
|
||||
}
|
||||
|
||||
final String keyStoreFile = getKeyStoreFilePath(agentFile);
|
||||
|
||||
String checkCmd = String.format("keytool -list -alias %s -keystore %s -storepass %s",
|
||||
certificateAlias, keyStoreFile, privatePassword);
|
||||
int existsCmdResult = Script.runSimpleBashScriptForExitValue(checkCmd);
|
||||
String keyToolPath = Script.getExecutableAbsolutePath("keytool");
|
||||
int existsCmdResult = Script.executeCommandForExitValue(keyToolPath, "-list", "-alias",
|
||||
sanitizeBashCommandArgument(certificateAlias), "-keystore", keyStoreFile, "-storepass",
|
||||
privatePassword);
|
||||
if (existsCmdResult == 1) {
|
||||
s_logger.error("Certificate alias " + certificateAlias + " does not exist, no need to revoke it");
|
||||
} else {
|
||||
String revokeCmd = String.format("keytool -delete -alias %s -keystore %s -storepass %s",
|
||||
certificateAlias, keyStoreFile, privatePassword);
|
||||
s_logger.debug("Revoking certificate alias " + certificateAlias + " from keystore " + keyStoreFile);
|
||||
Script.runSimpleBashScriptForExitValue(revokeCmd);
|
||||
Script.executeCommandForExitValue(keyToolPath, "-delete", "-alias",
|
||||
sanitizeBashCommandArgument(certificateAlias), "-keystore", keyStoreFile, "-storepass",
|
||||
privatePassword);;
|
||||
}
|
||||
} catch (FileNotFoundException | CloudRuntimeException e) {
|
||||
s_logger.error("Error while setting up certificate " + certificateAlias, e);
|
||||
|
||||
@ -18,21 +18,26 @@
|
||||
//
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.utils.PropertiesUtil;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.script.Script;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.utils.FileUtil;
|
||||
import com.cloud.utils.PropertiesUtil;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
@ResourceWrapper(handles = SetupDirectDownloadCertificateCommand.class)
|
||||
public class LibvirtSetupDirectDownloadCertificateCommandWrapper extends CommandWrapper<SetupDirectDownloadCertificateCommand, Answer, LibvirtComputingResource> {
|
||||
@ -79,9 +84,10 @@ public class LibvirtSetupDirectDownloadCertificateCommandWrapper extends Command
|
||||
*/
|
||||
private void importCertificate(String tempCerFilePath, String keyStoreFile, String certificateName, String privatePassword) {
|
||||
s_logger.debug("Importing certificate from temporary file to keystore");
|
||||
String importCommandFormat = "keytool -importcert -file %s -keystore %s -alias '%s' -storepass '%s' -noprompt";
|
||||
String importCmd = String.format(importCommandFormat, tempCerFilePath, keyStoreFile, certificateName, privatePassword);
|
||||
int result = Script.runSimpleBashScriptForExitValue(importCmd);
|
||||
String keyToolPath = Script.getExecutableAbsolutePath("keytool");
|
||||
int result = Script.executeCommandForExitValue(keyToolPath, "-importcert", "file", tempCerFilePath,
|
||||
"-keystore", keyStoreFile, "-alias", sanitizeBashCommandArgument(certificateName), "-storepass",
|
||||
privatePassword, "-noprompt");
|
||||
if (result != 0) {
|
||||
s_logger.debug("Certificate " + certificateName + " not imported as it already exist on keystore");
|
||||
}
|
||||
@ -94,8 +100,7 @@ public class LibvirtSetupDirectDownloadCertificateCommandWrapper extends Command
|
||||
String tempCerFilePath = String.format("%s/%s-%s",
|
||||
agentFile.getParent(), temporaryCertFilePrefix, certificateName);
|
||||
s_logger.debug("Creating temporary certificate file into: " + tempCerFilePath);
|
||||
int result = Script.runSimpleBashScriptForExitValue(String.format("echo '%s' > %s", certificate, tempCerFilePath));
|
||||
if (result != 0) {
|
||||
if (!FileUtil.writeToFile(tempCerFilePath, certificate)) {
|
||||
throw new CloudRuntimeException("Could not create the certificate file on path: " + tempCerFilePath);
|
||||
}
|
||||
return tempCerFilePath;
|
||||
@ -104,9 +109,23 @@ public class LibvirtSetupDirectDownloadCertificateCommandWrapper extends Command
|
||||
/**
|
||||
* Remove temporary file
|
||||
*/
|
||||
private void cleanupTemporaryFile(String temporaryFile) {
|
||||
protected void cleanupTemporaryFile(String temporaryFile) {
|
||||
s_logger.debug("Cleaning up temporary certificate file");
|
||||
Script.runSimpleBashScript("rm -f " + temporaryFile);
|
||||
if (StringUtils.isBlank(temporaryFile)) {
|
||||
s_logger.debug("Provided temporary certificate file path is empty");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Path filePath = Paths.get(temporaryFile);
|
||||
if (!Files.exists(filePath)) {
|
||||
s_logger.debug("Temporary certificate file does not exist: " + temporaryFile);
|
||||
return;
|
||||
}
|
||||
Files.delete(filePath);
|
||||
} catch (IOException e) {
|
||||
s_logger.warn(String.format("Error while cleaning up temporary file: %s", temporaryFile));
|
||||
s_logger.debug(String.format("Error while cleaning up temporary file: %s", temporaryFile), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -23,28 +23,27 @@ import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.agent.api.to.DpdkTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtKvmAgentHook;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.script.Script;
|
||||
import com.cloud.utils.ssh.SshHelper;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.libvirt.Connect;
|
||||
import org.libvirt.Domain;
|
||||
import org.libvirt.DomainInfo.DomainState;
|
||||
import org.libvirt.LibvirtException;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.StopAnswer;
|
||||
import com.cloud.agent.api.StopCommand;
|
||||
import com.cloud.agent.api.to.DpdkTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtKvmAgentHook;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
|
||||
import com.cloud.hypervisor.kvm.resource.VifDriver;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import org.libvirt.LibvirtException;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.ssh.SshHelper;
|
||||
|
||||
@ResourceWrapper(handles = StopCommand.class)
|
||||
public final class LibvirtStopCommandWrapper extends CommandWrapper<StopCommand, Answer, LibvirtComputingResource> {
|
||||
@ -121,10 +120,7 @@ public final class LibvirtStopCommandWrapper extends CommandWrapper<StopCommand,
|
||||
Map<String, DpdkTO> dpdkInterfaceMapping = command.getDpdkInterfaceMapping();
|
||||
if (MapUtils.isNotEmpty(dpdkInterfaceMapping)) {
|
||||
for (DpdkTO to : dpdkInterfaceMapping.values()) {
|
||||
String portToRemove = to.getPort();
|
||||
String cmd = String.format("ovs-vsctl del-port %s", portToRemove);
|
||||
s_logger.debug("Removing DPDK port: " + portToRemove);
|
||||
Script.runSimpleBashScript(cmd);
|
||||
removeDpdkPort(to.getPort());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -0,0 +1,105 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Spy;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.Ternary;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest(value = {LibvirtOvsFetchInterfaceCommandWrapper.class})
|
||||
public class LibvirtOvsFetchInterfaceCommandWrapperTest {
|
||||
|
||||
@Spy
|
||||
LibvirtOvsFetchInterfaceCommandWrapper wrapper = new LibvirtOvsFetchInterfaceCommandWrapper();
|
||||
|
||||
@Test
|
||||
public void testGetInterfaceDetailsValidValid() {
|
||||
String interfaceName = null;
|
||||
String ipAddress = null;
|
||||
try {
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while(interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
if (networkInterface.getInetAddresses().hasMoreElements() &&
|
||||
(networkInterface.getName().startsWith("eth") ||
|
||||
networkInterface.getName().startsWith("wl"))) {
|
||||
interfaceName = networkInterface.getName();
|
||||
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
|
||||
while(addresses.hasMoreElements()) {
|
||||
InetAddress addr = addresses.nextElement();
|
||||
if (addr instanceof Inet4Address) {
|
||||
ipAddress = addr.getHostAddress();
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException ignored) {}
|
||||
Ternary<String, String, String> result = null;
|
||||
try {
|
||||
result = wrapper.getInterfaceDetails(interfaceName);
|
||||
} catch (SocketException e) {
|
||||
Assert.fail("Exception occurred: " + e.getMessage());
|
||||
}
|
||||
Assert.assertNotNull(result);
|
||||
Assert.assertEquals(ipAddress, result.first().trim());
|
||||
}
|
||||
|
||||
private String getTempFilepath() {
|
||||
return String.format("%s/%s.txt", System.getProperty("java.io.tmpdir"), UUID.randomUUID());
|
||||
}
|
||||
|
||||
private void runTestGetInterfaceDetailsForRandomInterfaceName(String arg) {
|
||||
try {
|
||||
Ternary<String, String, String> result = wrapper.getInterfaceDetails(arg);
|
||||
Assert.assertTrue(StringUtils.isAllEmpty(result.first(), result.second(), result.third()));
|
||||
} catch (SocketException e) {
|
||||
Assert.fail(String.format("Exception occurred: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInterfaceDetailsForRandomInterfaceName() {
|
||||
List<String> commandVariants = List.of(
|
||||
"';touch %s'",
|
||||
";touch %s",
|
||||
"&& touch %s",
|
||||
"|| touch %s",
|
||||
UUID.randomUUID().toString());
|
||||
for (String cmd : commandVariants) {
|
||||
String filePath = getTempFilepath();
|
||||
String arg = String.format(cmd, filePath);
|
||||
runTestGetInterfaceDetailsForRandomInterfaceName(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
@ -15,15 +14,14 @@
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
||||
import com.cloud.agent.api.PrepareForMigrationCommand;
|
||||
import com.cloud.agent.api.to.DpdkTO;
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@ -33,8 +31,12 @@ import org.mockito.Spy;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
||||
import com.cloud.agent.api.PrepareForMigrationCommand;
|
||||
import com.cloud.agent.api.to.DpdkTO;
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest(value = {LibvirtPrepareForMigrationCommandWrapper.class})
|
||||
@ -72,4 +74,29 @@ public class LibvirtPrepareForMigrationCommandWrapperTest {
|
||||
|
||||
Assert.assertEquals(cpuShares, prepareForMigrationAnswer.getNewVmCpuShares().intValue());
|
||||
}
|
||||
|
||||
private String getTempFilepath() {
|
||||
return String.format("%s/%s.txt", System.getProperty("java.io.tmpdir"), UUID.randomUUID());
|
||||
}
|
||||
|
||||
private void runTestRemoveDpdkPortForCommandInjection(String portWithCommand) {
|
||||
try {
|
||||
libvirtPrepareForMigrationCommandWrapperSpy.removeDpdkPort(portWithCommand);
|
||||
Assert.fail(String.format("Command injection working for portWithCommand: %s", portWithCommand));
|
||||
} catch (CloudRuntimeException ignored) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveDpdkPortForCommandInjection() {
|
||||
List<String> commandVariants = List.of(
|
||||
"';touch %s'",
|
||||
";touch %s",
|
||||
"&& touch %s",
|
||||
"|| touch %s",
|
||||
UUID.randomUUID().toString());
|
||||
for (String cmd : commandVariants) {
|
||||
String portWithCommand = String.format(cmd, getTempFilepath());
|
||||
runTestRemoveDpdkPortForCommandInjection(portWithCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,93 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Spy;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest(value = {LibvirtSetupDirectDownloadCertificateCommandWrapper.class})
|
||||
public class LibvirtSetupDirectDownloadCertificateCommandWrapperTest {
|
||||
|
||||
@Spy
|
||||
LibvirtSetupDirectDownloadCertificateCommandWrapper wrapper = new LibvirtSetupDirectDownloadCertificateCommandWrapper();
|
||||
|
||||
private String getTempFilepath() {
|
||||
return String.format("%s/%s.txt", System.getProperty("java.io.tmpdir"), UUID.randomUUID());
|
||||
}
|
||||
|
||||
private void runTestCleanupTemporaryFileForRandomFileNames(String fileWithCommand, String filePath) {
|
||||
wrapper.cleanupTemporaryFile(fileWithCommand);
|
||||
File f = new File(filePath);
|
||||
if(f.exists() && !f.isDirectory()) {
|
||||
Assert.fail(String.format("Command injection working for fileWithCommand: %s", fileWithCommand));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupTemporaryFileForRandomFileNames() {
|
||||
List<String> commandVariants = List.of(
|
||||
"';touch %s'",
|
||||
";touch %s",
|
||||
"&& touch %s",
|
||||
"|| touch %s",
|
||||
"%s");
|
||||
for (String cmd : commandVariants) {
|
||||
String filePath = getTempFilepath();
|
||||
String arg = String.format(cmd, filePath);
|
||||
runTestCleanupTemporaryFileForRandomFileNames(arg, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private String createTempFile() {
|
||||
String filePath = getTempFilepath();
|
||||
Path path = Paths.get(getTempFilepath());
|
||||
try {
|
||||
if (Files.notExists(path)) {
|
||||
Files.createFile(path);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Assert.fail(String.format("Error while creating file: %s due to %s", filePath, e.getMessage()));
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupTemporaryFileValid() {
|
||||
String filePath = createTempFile();
|
||||
wrapper.cleanupTemporaryFile(filePath);
|
||||
File f = new File(filePath);
|
||||
if(f.exists() && !f.isDirectory()) {
|
||||
Assert.fail(String.format("Command injection working for fileWithCommand: %s", filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Spy;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest(value = {LibvirtStopCommandWrapper.class})
|
||||
public class LibvirtStopCommandWrapperTest {
|
||||
|
||||
@Spy
|
||||
LibvirtStopCommandWrapper wrapper = new LibvirtStopCommandWrapper();
|
||||
|
||||
private String getTempFilepath() {
|
||||
return String.format("%s/%s.txt", System.getProperty("java.io.tmpdir"), UUID.randomUUID());
|
||||
}
|
||||
|
||||
private void runTestRemoveDpdkPortForCommandInjection(String portWithCommand) {
|
||||
try {
|
||||
wrapper.removeDpdkPort(portWithCommand);
|
||||
Assert.fail(String.format("Command injection working for portWithCommand: %s", portWithCommand));
|
||||
} catch (CloudRuntimeException ignored) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveDpdkPortForCommandInjection() {
|
||||
List<String> commandVariants = List.of(
|
||||
"';touch %s'",
|
||||
";touch %s",
|
||||
"&& touch %s",
|
||||
"|| touch %s",
|
||||
UUID.randomUUID().toString());
|
||||
for (String cmd : commandVariants) {
|
||||
String portWithCommand = String.format(cmd, getTempFilepath());
|
||||
runTestRemoveDpdkPortForCommandInjection(portWithCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -399,6 +399,17 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupIntegrationPortListener(Integer apiPort) {
|
||||
if (apiPort == null || apiPort <= 0) {
|
||||
s_logger.trace(String.format("Skipping setting up listener for integration port as %s is set to %d",
|
||||
IntegrationAPIPort.key(), apiPort));
|
||||
return;
|
||||
}
|
||||
s_logger.debug(String.format("Setting up integration API service listener on port: %d", apiPort));
|
||||
final ListenerThread listenerThread = new ListenerThread(this, apiPort);
|
||||
listenerThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
@ -444,10 +455,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
setEncodeApiResponse(EncodeApiResponse.value());
|
||||
|
||||
if (apiPort != null) {
|
||||
final ListenerThread listenerThread = new ListenerThread(this, apiPort);
|
||||
listenerThread.start();
|
||||
}
|
||||
setupIntegrationPortListener(apiPort);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -432,6 +433,14 @@ public class CAManagerImpl extends ManagerBase implements CAManager {
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] {CAProviderPlugin, CertKeySize, CertSignatureAlgorithm, CertValidityPeriod, AutomaticCertRenewal, AllowHostIPInSysVMAgentCert, CABackgroundJobDelay, CertExpiryAlertPeriod};
|
||||
return new ConfigKey<?>[] {CAProviderPlugin, CertKeySize, CertSignatureAlgorithm, CertValidityPeriod,
|
||||
AutomaticCertRenewal, AllowHostIPInSysVMAgentCert, CABackgroundJobDelay, CertExpiryAlertPeriod,
|
||||
CertManagementCustomSubjectAlternativeName
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isManagementCertificate(java.security.cert.Certificate certificate) throws CertificateParsingException {
|
||||
return getConfiguredCaProvider().isManagementCertificate(certificate);
|
||||
}
|
||||
}
|
||||
|
||||
73
server/src/test/java/com/cloud/api/ApiServerTest.java
Normal file
73
server/src/test/java/com/cloud/api/ApiServerTest.java
Normal file
@ -0,0 +1,73 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
package com.cloud.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mockito;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest(ApiServer.class)
|
||||
public class ApiServerTest {
|
||||
|
||||
@InjectMocks
|
||||
ApiServer apiServer = new ApiServer();
|
||||
|
||||
private List<ApiServer.ListenerThread> createdListeners;
|
||||
|
||||
private void runTestSetupIntegrationPortListenerInvalidPorts(Integer port) {
|
||||
try {
|
||||
ApiServer.ListenerThread mocked = Mockito.mock(ApiServer.ListenerThread.class);
|
||||
PowerMockito.whenNew(ApiServer.ListenerThread.class).withAnyArguments().thenReturn(mocked);
|
||||
apiServer.setupIntegrationPortListener(port);
|
||||
Mockito.verify(mocked, Mockito.never()).start();
|
||||
} catch (Exception e) {
|
||||
Assert.fail(String.format("Exception occurred: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetupIntegrationPortListenerInvalidPorts() {
|
||||
List<Integer> ports = new ArrayList<>(List.of(-1, -10, 0));
|
||||
ports.add(null);
|
||||
for (Integer port : ports) {
|
||||
runTestSetupIntegrationPortListenerInvalidPorts(port);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetupIntegrationPortListenerValidPort() {
|
||||
Integer validPort = 8080;
|
||||
try {
|
||||
ApiServer.ListenerThread mocked = Mockito.mock(ApiServer.ListenerThread.class);
|
||||
PowerMockito.whenNew(ApiServer.ListenerThread.class).withAnyArguments().thenReturn(mocked);
|
||||
apiServer.setupIntegrationPortListener(validPort);
|
||||
PowerMockito.verifyNew(ApiServer.ListenerThread.class).withArguments(apiServer, validPort);
|
||||
Mockito.verify(mocked).start();
|
||||
} catch (Exception e) {
|
||||
Assert.fail(String.format("Exception occurred: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,15 +21,20 @@ package com.cloud.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.ssh.SshHelper;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.ssh.SshHelper;
|
||||
|
||||
public class FileUtil {
|
||||
private static final Logger s_logger = Logger.getLogger(FileUtil.class);
|
||||
|
||||
@ -57,4 +62,16 @@ public class FileUtil {
|
||||
}
|
||||
throw new CloudRuntimeException(finalErrMsg);
|
||||
}
|
||||
|
||||
public static boolean writeToFile(String fileName, String content) {
|
||||
Path filePath = Paths.get(fileName);
|
||||
try {
|
||||
Files.write(filePath, content.getBytes(StandardCharsets.UTF_8));
|
||||
s_logger.debug(String.format("Successfully wrote to the file: %s", fileName));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
s_logger.error(String.format("Error writing to the file: %s", fileName), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,14 +19,6 @@
|
||||
|
||||
package com.cloud.utils.script;
|
||||
|
||||
import com.cloud.utils.PropertiesUtil;
|
||||
import com.cloud.utils.concurrency.NamedThreadFactory;
|
||||
import com.cloud.utils.script.OutputInterpreter.TimedOutLogger;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -40,10 +32,24 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.PropertiesUtil;
|
||||
import com.cloud.utils.concurrency.NamedThreadFactory;
|
||||
import com.cloud.utils.script.OutputInterpreter.TimedOutLogger;
|
||||
|
||||
public class Script implements Callable<String> {
|
||||
private static final Logger s_logger = Logger.getLogger(Script.class);
|
||||
@ -52,7 +58,7 @@ public class Script implements Callable<String> {
|
||||
|
||||
public static final String ERR_EXECUTE = "execute.error";
|
||||
public static final String ERR_TIMEOUT = "timeout";
|
||||
private int _defaultTimeout = 3600 * 1000; /* 1 hour */
|
||||
private static final int DEFAULT_TIMEOUT = 3600 * 1000; /* 1 hour */
|
||||
private volatile boolean _isTimeOut = false;
|
||||
|
||||
private boolean _passwordCommand = false;
|
||||
@ -84,7 +90,7 @@ public class Script implements Callable<String> {
|
||||
_timeout = timeout;
|
||||
if (_timeout == 0) {
|
||||
/* always using default timeout 1 hour to avoid thread hang */
|
||||
_timeout = _defaultTimeout;
|
||||
_timeout = DEFAULT_TIMEOUT;
|
||||
}
|
||||
_process = null;
|
||||
_logger = logger != null ? logger : s_logger;
|
||||
@ -493,16 +499,7 @@ public class Script implements Callable<String> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String runSimpleBashScript(String command) {
|
||||
return Script.runSimpleBashScript(command, 0);
|
||||
}
|
||||
|
||||
public static String runSimpleBashScript(String command, int timeout) {
|
||||
|
||||
Script s = new Script("/bin/bash", timeout);
|
||||
s.add("-c");
|
||||
s.add(command);
|
||||
|
||||
private static String runScript(Script s) {
|
||||
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
|
||||
if (s.execute(parser) != null)
|
||||
return null;
|
||||
@ -514,16 +511,83 @@ public class Script implements Callable<String> {
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
public static int runSimpleBashScriptForExitValue(String command) {
|
||||
return runSimpleBashScriptForExitValue(command, 0);
|
||||
}
|
||||
|
||||
public static int runSimpleBashScriptForExitValue(String command, int timeout) {
|
||||
|
||||
public static String runSimpleBashScript(String command, int timeout) {
|
||||
Script s = new Script("/bin/bash", timeout);
|
||||
s.add("-c");
|
||||
s.add(command);
|
||||
return runScript(s);
|
||||
}
|
||||
|
||||
public static String runSimpleBashScript(String command) {
|
||||
return Script.runSimpleBashScript(command, 0);
|
||||
}
|
||||
|
||||
public static String getExecutableAbsolutePath(String executable) {
|
||||
for (String dirName : System.getenv("PATH").split(File.pathSeparator)) {
|
||||
File file = new File(dirName, executable);
|
||||
if (file.isFile() && file.canExecute()) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
return executable;
|
||||
}
|
||||
|
||||
private static Script getScriptForCommandRun(String... command) {
|
||||
Script s = new Script(command[0], 0);
|
||||
if (command.length > 1) {
|
||||
for (int i = 1; i < command.length; ++i) {
|
||||
s.add(command[i]);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String executeCommand(String... command) {
|
||||
return runScript(getScriptForCommandRun(command));
|
||||
}
|
||||
|
||||
public static int executeCommandForExitValue(String... command) {
|
||||
return runScriptForExitValue(getScriptForCommandRun(command));
|
||||
}
|
||||
|
||||
public static Pair<Integer, String> executePipedCommands(List<String[]> commands, long timeout) {
|
||||
if (timeout <= 0) {
|
||||
timeout = DEFAULT_TIMEOUT;
|
||||
}
|
||||
Callable<Pair<Integer, String>> commandRunner = () -> {
|
||||
List<ProcessBuilder> builders = commands.stream().map(ProcessBuilder::new).collect(Collectors.toList());
|
||||
List<Process> processes = ProcessBuilder.startPipeline(builders);
|
||||
Process last = processes.get(processes.size()-1);
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(last.getInputStream()))) {
|
||||
String line;
|
||||
StringBuilder output = new StringBuilder();
|
||||
while ((line = reader.readLine()) != null) {
|
||||
output.append(line).append(System.lineSeparator());
|
||||
}
|
||||
last.waitFor();
|
||||
s_logger.debug("Piped commands executed successfully");
|
||||
return new Pair<>(last.exitValue(), output.toString());
|
||||
} catch (IOException | InterruptedException e) {
|
||||
s_logger.error("Error executing piped commands", e);
|
||||
return new Pair<>(-1, stackTraceAsString(e));
|
||||
}
|
||||
};
|
||||
|
||||
Future<Pair<Integer, String>> future = s_executors.submit(commandRunner);
|
||||
Pair<Integer, String> result = new Pair<>(-1, ERR_EXECUTE);
|
||||
try {
|
||||
result = future.get(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
s_logger.error("Piped command execution timed out, attempting to terminate the processes.");
|
||||
future.cancel(true);
|
||||
result.second(ERR_TIMEOUT);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
s_logger.error("Error executing piped commands", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int runScriptForExitValue(Script s) {
|
||||
String result = s.execute(null);
|
||||
if (result == null || result.trim().isEmpty())
|
||||
return -1;
|
||||
@ -536,4 +600,14 @@ public class Script implements Callable<String> {
|
||||
}
|
||||
}
|
||||
|
||||
public static int runSimpleBashScriptForExitValue(String command) {
|
||||
return runSimpleBashScriptForExitValue(command, 0);
|
||||
}
|
||||
|
||||
public static int runSimpleBashScriptForExitValue(String command, int timeout) {
|
||||
Script s = new Script("/bin/bash", timeout);
|
||||
s.add("-c");
|
||||
s.add(command);
|
||||
return runScriptForExitValue(s);
|
||||
}
|
||||
}
|
||||
|
||||
81
utils/src/test/java/com/cloud/utils/script/ScriptTest.java
Normal file
81
utils/src/test/java/com/cloud/utils/script/ScriptTest.java
Normal file
@ -0,0 +1,81 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
package com.cloud.utils.script;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ScriptTest {
|
||||
|
||||
@Test
|
||||
public void testExecutePipedCommandsSingle() {
|
||||
String keyword = "Hello World!";
|
||||
List<String[]> commands = new ArrayList<>();
|
||||
commands.add(new String[]{"echo", keyword});
|
||||
Pair<Integer, String> result = Script.executePipedCommands(commands, 0);
|
||||
Assert.assertNotNull("Result should not be null", result);
|
||||
Assert.assertEquals(0, result.first().intValue());
|
||||
String output = result.second().trim();
|
||||
Assert.assertTrue(StringUtils.isNotEmpty(output));
|
||||
Assert.assertEquals(keyword, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecutePipedCommandsMultiple() {
|
||||
String keyword = "Hello";
|
||||
List<String[]> commands = Arrays.asList(
|
||||
new String[]{"echo", String.format("%s\n World", keyword)},
|
||||
new String[]{"grep", keyword}
|
||||
);
|
||||
Pair<Integer, String> result = Script.executePipedCommands(commands, 0);
|
||||
Assert.assertNotNull("Result should not be null", result);
|
||||
Assert.assertEquals(0, result.first().intValue());
|
||||
String output = result.second().trim();
|
||||
Assert.assertTrue(StringUtils.isNotEmpty(output));
|
||||
Assert.assertEquals(keyword, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecutePipedCommandsTimeout() {
|
||||
List<String[]> commands = new ArrayList<>();
|
||||
commands.add(new String[]{"sh", "-c", "sleep 10"});
|
||||
Pair<Integer, String> result = Script.executePipedCommands(commands, TimeUnit.SECONDS.toMillis(1));
|
||||
Assert.assertNotNull("Result should not be null", result);
|
||||
Assert.assertEquals(-1, result.first().intValue());
|
||||
Assert.assertEquals(Script.ERR_TIMEOUT, result.second());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetExecutableAbsolutePath() {
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
return;
|
||||
}
|
||||
String result = Script.getExecutableAbsolutePath("ls");
|
||||
Assert.assertTrue(List.of("/usr/bin/ls", "/bin/ls").contains(result));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user