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>
diff --git a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java
index 12a9d3d..b0fb1ac 100644
--- a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java
+++ b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java
@@ -77,6 +77,14 @@
                                                     "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
diff --git a/core/src/main/java/com/cloud/resource/CommandWrapper.java b/core/src/main/java/com/cloud/resource/CommandWrapper.java
index d9c1ea2..ee6aa16 100644
--- a/core/src/main/java/com/cloud/resource/CommandWrapper.java
+++ b/core/src/main/java/com/cloud/resource/CommandWrapper.java
@@ -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 @@
      * @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));
+    }
 }
diff --git a/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAProvider.java b/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAProvider.java
index 388cae7..77b3ee2 100644
--- a/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAProvider.java
+++ b/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAProvider.java
@@ -22,6 +22,7 @@
 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 @@
 
     /**
      * Issues certificate with provided options
+     *
      * @param domainNames
      * @param ipAddresses
      * @param validityDays
@@ -104,4 +106,6 @@
      * @return returns description
      */
     String getDescription();
+
+    boolean isManagementCertificate(java.security.cert.Certificate certificate) throws CertificateParsingException;
 }
diff --git a/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAService.java b/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAService.java
index facf13a..721c88b 100644
--- a/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAService.java
+++ b/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAService.java
@@ -21,6 +21,7 @@
 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 @@
      * @return returns char[] passphrase
      */
     char[] getKeyStorePassphrase();
+
+    boolean isManagementCertificate(java.security.cert.Certificate certificate) throws CertificateParsingException;
 }
diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManager.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManager.java
index 1b1406c..54f5758 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManager.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManager.java
@@ -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 @@
      */
     String getSelfPeerName();
 
+    String getSelfNodeIP();
+
     long getManagementNodeId();
 
     /**
diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java
index 289638f..d601c09 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java
@@ -40,17 +40,17 @@
 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 @@
         // 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 @@
         return Long.toString(_msId);
     }
 
+    @Override
     public String getSelfNodeIP() {
         return _clusterNodeIP;
     }
diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java
index 735de5b..e073a28 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java
@@ -28,7 +28,5 @@
 
     public ClusterService getPeerService(String strPeer) throws RemoteException;
 
-    public String getServiceEndpointName(String strPeer);
-
     public int getServicePort();
 }
diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java
index 7451b5f..15ee055 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java
@@ -23,8 +23,9 @@
 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 @@
     @Inject
     private ManagementServerHostDao _mshostDao;
     @Inject
+    private CAManager caService;
+    @Inject
     protected ConfigDepot _configDepot;
 
     private ClusterServiceServletContainer _servletContainer;
@@ -51,7 +54,7 @@
     private int _clusterServicePort = DEFAULT_SERVICE_PORT;
 
     public ClusterServiceServletAdapter() {
-        setRunLevel(ComponentLifecycle.RUN_LEVEL_FRAMEWORK);
+        setRunLevel(ComponentLifecycle.RUN_LEVEL_COMPONENT);
     }
 
     @Override
@@ -66,12 +69,10 @@
         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 @@
 
     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 @@
     @Override
     public boolean start() {
         _servletContainer = new ClusterServiceServletContainer();
-        _servletContainer.start(new ClusterServiceServletHttpHandler(_manager), _clusterServicePort);
+        _servletContainer.start(new ClusterServiceServletHttpHandler(_manager), _manager.getSelfNodeIP(),
+                _clusterServicePort, caService);
         return true;
     }
 
diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletContainer.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletContainer.java
index 69cc871..1aa9caa 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletContainer.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletContainer.java
@@ -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.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 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 @@
         }
     }
 
-    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 @@
             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 @@
                                     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 @@
                 }
             }
 
-            _executor.shutdown();
+            executor.shutdown();
             if (s_logger.isInfoEnabled())
                 s_logger.info("Cluster service servlet container shutdown");
         }
diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java
index ec8b908..c5b6145 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java
@@ -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 @@
         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;
     }
diff --git a/framework/cluster/src/test/java/com/cloud/cluster/ClusterManagerImplTest.java b/framework/cluster/src/test/java/com/cloud/cluster/ClusterManagerImplTest.java
new file mode 100644
index 0000000..9b1854f
--- /dev/null
+++ b/framework/cluster/src/test/java/com/cloud/cluster/ClusterManagerImplTest.java
@@ -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());
+    }
+}
diff --git a/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletAdapterTest.java b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletAdapterTest.java
index 91d8b61..3827236 100644
--- a/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletAdapterTest.java
+++ b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletAdapterTest.java
@@ -50,7 +50,7 @@
     @Test
     public void testRunLevel() {
         int runLevel = clusterServiceServletAdapter.getRunLevel();
-        assertTrue(runLevel == ComponentLifecycle.RUN_LEVEL_FRAMEWORK);
+        assertTrue(runLevel == ComponentLifecycle.RUN_LEVEL_COMPONENT);
         assertTrue(runLevel == clusterManagerImpl.getRunLevel());
     }
 }
diff --git a/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletContainerTest.java b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletContainerTest.java
new file mode 100644
index 0000000..baf4e58
--- /dev/null
+++ b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletContainerTest.java
@@ -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");
+    }
+}
diff --git a/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletImplTest.java b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletImplTest.java
new file mode 100644
index 0000000..361c77d
--- /dev/null
+++ b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletImplTest.java
@@ -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());
+    }
+}
diff --git a/packaging/systemd/cloudstack-management.default b/packaging/systemd/cloudstack-management.default
index 252fb4b..d0b41b4 100644
--- a/packaging/systemd/cloudstack-management.default
+++ b/packaging/systemd/cloudstack-management.default
@@ -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/*"
 
diff --git a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
index f71274b..8217041 100644
--- a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
+++ b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
@@ -33,9 +33,11 @@
 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.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 @@
             "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 @@
         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 @@
 
     @Override
     public boolean start() {
+        managementCertificateCustomSAN = CAManager.CertManagementCustomSubjectAlternativeName.value();
         return loadRootCAKeyPair() && loadRootCAKeyPair() && loadManagementKeyStore();
     }
 
@@ -456,4 +465,26 @@
     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;
+    }
 }
diff --git a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java
index 15514b9..8311f4d 100644
--- a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java
+++ b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java
@@ -26,8 +26,13 @@
 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.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 @@
         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()));
+        }
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
index 5b55db2..f95948d 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
@@ -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.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 @@
                     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);
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java
index 1c27bdd..da2839d 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java
@@ -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.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 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 @@
                         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);
     }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java
index 5c79de5..117a832 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java
@@ -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.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);
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
index 3f281e5..769482f 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
@@ -47,7 +47,6 @@
 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 @@
         } 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());
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
index fc57cd4..0803fc2 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
@@ -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.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 @@
     }
 
     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;
     }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java
index 6c83c4d..f230d3f 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java
@@ -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.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 @@
             }
 
             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);
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
index fff8da7..0774d30 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
@@ -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 @@
      */
     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 @@
         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 @@
     /**
      * 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
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
index 7ee6ccd..518ee2b 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
@@ -23,28 +23,27 @@
 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 @@
                     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 {
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapperTest.java
new file mode 100644
index 0000000..fbc9c2b
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapperTest.java
@@ -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);
+        }
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
index 5530819..e534f81 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
@@ -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.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 @@
 
         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);
+        }
+    }
 }
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapperTest.java
new file mode 100644
index 0000000..cd78733
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapperTest.java
@@ -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));
+        }
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapperTest.java
new file mode 100644
index 0000000..c701946
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapperTest.java
@@ -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);
+        }
+    }
+}
diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java
index 4a7259c..cf0e689 100644
--- a/server/src/main/java/com/cloud/api/ApiServer.java
+++ b/server/src/main/java/com/cloud/api/ApiServer.java
@@ -399,6 +399,17 @@
         }
     }
 
+    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 @@
 
         setEncodeApiResponse(EncodeApiResponse.value());
 
-        if (apiPort != null) {
-            final ListenerThread listenerThread = new ListenerThread(this, apiPort);
-            listenerThread.start();
-        }
+        setupIntegrationPortListener(apiPort);
 
         return true;
     }
diff --git a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java
index facad1a..609c4d5 100644
--- a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java
@@ -24,6 +24,7 @@
 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 @@
 
     @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);
     }
 }
diff --git a/server/src/test/java/com/cloud/api/ApiServerTest.java b/server/src/test/java/com/cloud/api/ApiServerTest.java
new file mode 100644
index 0000000..2c7eebd
--- /dev/null
+++ b/server/src/test/java/com/cloud/api/ApiServerTest.java
@@ -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()));
+        }
+    }
+}
diff --git a/utils/src/main/java/com/cloud/utils/FileUtil.java b/utils/src/main/java/com/cloud/utils/FileUtil.java
index d9bf081..fbb04e1 100644
--- a/utils/src/main/java/com/cloud/utils/FileUtil.java
+++ b/utils/src/main/java/com/cloud/utils/FileUtil.java
@@ -21,15 +21,20 @@
 
 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 @@
         }
         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;
+    }
 }
diff --git a/utils/src/main/java/com/cloud/utils/script/Script.java b/utils/src/main/java/com/cloud/utils/script/Script.java
index 3158553..27ec65a 100644
--- a/utils/src/main/java/com/cloud/utils/script/Script.java
+++ b/utils/src/main/java/com/cloud/utils/script/Script.java
@@ -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.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 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 @@
         _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 @@
         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 @@
             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 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);
+    }
 }
diff --git a/utils/src/test/java/com/cloud/utils/script/ScriptTest.java b/utils/src/test/java/com/cloud/utils/script/ScriptTest.java
new file mode 100644
index 0000000..cc60479
--- /dev/null
+++ b/utils/src/test/java/com/cloud/utils/script/ScriptTest.java
@@ -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));
+    }
+}