IGNITE-9845 Web Console: Added support for two way SSL between browser, web 
server, agent and cluster.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/9f9bb752
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/9f9bb752
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/9f9bb752

Branch: refs/heads/ignite-601
Commit: 9f9bb75201a3c4a4ca5398c450d741a095321167
Parents: ca93282
Author: Alexey Kuznetsov <akuznet...@apache.org>
Authored: Thu Dec 27 16:36:02 2018 +0700
Committer: Alexey Kuznetsov <akuznet...@apache.org>
Committed: Thu Dec 27 16:36:02 2018 +0700

----------------------------------------------------------------------
 .../ignite/ssl/DelegatingSSLContextSpi.java     |   4 +-
 .../ignite/ssl/SSLSocketFactoryWrapper.java     |  64 ++--
 modules/web-console/assembly/README.txt         |  46 ++-
 .../web-console/backend/app/browsersHandler.js  |  34 +-
 modules/web-console/backend/app/settings.js     |  66 +++-
 .../backend/config/settings.json.sample         |  51 +--
 modules/web-console/backend/launch-tools.js     |   6 +-
 modules/web-console/backend/package.json        |   2 +-
 modules/web-console/frontend/app/app.js         |   3 -
 .../connected-clusters-badge/controller.js      |  11 +-
 .../app/modules/agent/AgentManager.service.js   |  12 +-
 .../frontend/app/modules/demo/Demo.module.js    |  24 +-
 .../frontend/app/utils/SimpleWorkerPool.js      |   2 +-
 modules/web-console/frontend/app/vendor.js      |   1 -
 modules/web-console/frontend/package.json       |   5 +-
 .../web-console/frontend/webpack/webpack.dev.js |  25 +-
 modules/web-console/web-agent/pom.xml           |  18 +-
 .../console/agent/AgentConfiguration.java       | 330 +++++++++++++++++--
 .../ignite/console/agent/AgentLauncher.java     |  89 +++--
 .../apache/ignite/console/agent/AgentUtils.java | 140 +++++++-
 .../agent/handlers/AbstractListener.java        |   9 +-
 .../console/agent/handlers/ClusterListener.java |  77 ++---
 .../console/agent/handlers/RestListener.java    |   6 +-
 .../ignite/console/agent/rest/RestExecutor.java |  84 +++--
 .../agent/rest/RestExecutorSelfTest.java        | 329 ++++++++++++++++++
 .../testsuites/IgniteWebAgentTestSuite.java     |  33 ++
 .../web-agent/src/test/resources/ca.jks         | Bin 0 -> 1394 bytes
 .../web-agent/src/test/resources/client.jks     | Bin 0 -> 2030 bytes
 .../web-agent/src/test/resources/generate.bat   | 122 +++++++
 .../web-agent/src/test/resources/generate.sh    | 111 +++++++
 .../src/test/resources/jetty-with-ciphers-0.xml |  94 ++++++
 .../src/test/resources/jetty-with-ciphers-1.xml |  94 ++++++
 .../src/test/resources/jetty-with-ciphers-2.xml |  94 ++++++
 .../src/test/resources/jetty-with-ssl.xml       |  89 +++++
 .../web-agent/src/test/resources/server.jks     | Bin 0 -> 1419 bytes
 35 files changed, 1794 insertions(+), 281 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java 
b/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java
index d8621f2..360e922 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java
@@ -31,7 +31,6 @@ import javax.net.ssl.TrustManager;
 
 /** */
 class DelegatingSSLContextSpi extends SSLContextSpi {
-
     /** */
     private final SSLContext delegate;
 
@@ -57,8 +56,7 @@ class DelegatingSSLContextSpi extends SSLContextSpi {
 
     /** {@inheritDoc} */
     @Override protected SSLServerSocketFactory engineGetServerSocketFactory() {
-        return new 
SSLServerSocketFactoryWrapper(delegate.getServerSocketFactory(),
-            parameters);
+        return new 
SSLServerSocketFactoryWrapper(delegate.getServerSocketFactory(), parameters);
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java
----------------------------------------------------------------------
diff --git 
a/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java 
b/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java
index bfe6d0d..992f836 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java
@@ -24,9 +24,10 @@ import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 
-/** */
+/**
+ * SSL socket factory that configure additional SSL params for socket.
+ */
 class SSLSocketFactoryWrapper extends SSLSocketFactory {
-
     /** */
     private final SSLSocketFactory delegate;
 
@@ -49,65 +50,46 @@ class SSLSocketFactoryWrapper extends SSLSocketFactory {
         return delegate.getSupportedCipherSuites();
     }
 
-    /** {@inheritDoc} */
-    @Override public Socket createSocket() throws IOException {
-        SSLSocket sock = (SSLSocket)delegate.createSocket();
-
+    /**
+     * Configure socket with SSL parameters.
+     *
+     * @param socket Socket to configure.
+     * @return Configured socket.
+     */
+    private Socket configureSocket(Socket socket) {
         if (parameters != null)
-            sock.setSSLParameters(parameters);
+            ((SSLSocket)socket).setSSLParameters(parameters);
 
-        return sock;
+        return socket;
     }
 
     /** {@inheritDoc} */
-    @Override public Socket createSocket(Socket sock, String host, int port, 
boolean autoClose) throws IOException {
-        SSLSocket sslSock = (SSLSocket)delegate.createSocket(sock, host, port, 
autoClose);
-
-        if (parameters != null)
-            sslSock.setSSLParameters(parameters);
+    @Override public Socket createSocket() throws IOException {
+        return configureSocket(delegate.createSocket());
+    }
 
-        return sock;
+    /** {@inheritDoc} */
+    @Override public Socket createSocket(Socket sock, String host, int port, 
boolean autoClose) throws IOException {
+        return configureSocket(delegate.createSocket(sock, host, port, 
autoClose));
     }
 
     /** {@inheritDoc} */
     @Override public Socket createSocket(String host, int port) throws 
IOException {
-        SSLSocket sock = (SSLSocket)delegate.createSocket(host, port);
-
-        if (parameters != null)
-            sock.setSSLParameters(parameters);
-
-        return sock;
+        return configureSocket(delegate.createSocket(host, port));
     }
 
     /** {@inheritDoc} */
     @Override public Socket createSocket(String host, int port, InetAddress 
locAddr, int locPort) throws IOException {
-        SSLSocket sock = (SSLSocket)delegate.createSocket(host, port, locAddr, 
locPort);
-
-        if (parameters != null)
-            sock.setSSLParameters(parameters);
-
-        return sock;
+        return configureSocket(delegate.createSocket(host, port, locAddr, 
locPort));
     }
 
     /** {@inheritDoc} */
     @Override public Socket createSocket(InetAddress addr, int port) throws 
IOException {
-        SSLSocket sock = (SSLSocket)delegate.createSocket(addr, port);
-
-        if (parameters != null)
-            sock.setSSLParameters(parameters);
-
-        return sock;
+        return configureSocket(delegate.createSocket(addr, port));
     }
 
     /** {@inheritDoc} */
-    @Override public Socket createSocket(InetAddress addr, int port, 
InetAddress locAddr,
-        int locPort) throws IOException {
-        SSLSocket sock = (SSLSocket)delegate.createSocket(addr, port, locAddr, 
locPort);
-
-        if (parameters != null)
-            sock.setSSLParameters(parameters);
-
-        return sock;
+    @Override public Socket createSocket(InetAddress addr, int port, 
InetAddress locAddr, int locPort) throws IOException {
+        return configureSocket(delegate.createSocket(addr, port, locAddr, 
locPort));
     }
-
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/assembly/README.txt
----------------------------------------------------------------------
diff --git a/modules/web-console/assembly/README.txt 
b/modules/web-console/assembly/README.txt
index f1e114c..2656aca 100644
--- a/modules/web-console/assembly/README.txt
+++ b/modules/web-console/assembly/README.txt
@@ -43,12 +43,13 @@ Technical details
 All available parameters with defaults:
     Web Console host:           --server:host 0.0.0.0
     Web Console port:           --server:port 80
+
     Enable HTTPS:               --server:ssl false
-    HTTPS key:                  --server:key "serve/keys/test.key"
-    HTTPS certificate:          --server:cert "serve/keys/test.crt"
-    HTTPS passphrase:           --server:keyPassphrase "password"
+
     Disable self registration:  --server:disable:signup false
+
     MongoDB URL:                --mongodb:url mongodb://localhost/console
+
     Mail service:               --mail:service "gmail"
     Signature text:             --mail:sign "Kind regards, Apache Ignite Team"
     Greeting text:              --mail:greeting "Apache Ignite Web Console"
@@ -56,8 +57,31 @@ All available parameters with defaults:
     User to send e-mail:        --mail:auth:user 
"someusername@somecompany.somedomain"
     E-mail service password:    --mail:auth:pass ""
 
-Sample usage:
-    `ignite-web-console-win.exe --mail:auth:user "my_u...@gmail.com"  
--mail:auth:pass "my_password"`
+SSL options has no default values:
+    --server:key "path to file with server.key"
+    --server:cert "path to file with server.crt"
+    --server:ca "path to file with ca.crt"
+    --server:passphrase "Password for key"
+    --server:ciphers "Comma separated ciphers list"
+    --server:secureProtocol "The TLS protocol version to use"
+    --server:clientCertEngine "Name of an OpenSSL engine which can provide the 
client certificate"
+    --server:pfx "Path to PFX or PKCS12 encoded private key and certificate 
chain"
+    --server:crl "Path to file with CRLs (Certificate Revocation Lists)"
+    --server:dhparam "Diffie Hellman parameters"
+    --server:ecdhCurve "A string describing a named curve"
+    --server:maxVersion "Optional the maximmu TLS version to allow"
+    --server:minVersion "Optional the minimum TLS version to allow"
+    --server:secureOptions "Optional OpenSSL options"
+    --server:sessionIdContext "Opaque identifier used by servers to ensure 
session state is not shared between applications"
+    --server:honorCipherOrder "true or false"
+    --server:requestCert "Set to true to specify whether a server should 
request a certificate from a connecting client"
+    --server:rejectUnauthorized "Set to true to automatically reject clients 
with invalid certificates"
+
+Documentation for SSL options: 
https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
+
+Sample usages:
+    `ignite-web-console-win.exe --mail:auth:user "my_u...@gmail.com" 
--mail:auth:pass "my_password"`
+    `ignite-web-console-win.exe --server:port 11443 --server:ssl true 
--server:requestCert true --server:key "server.key" --server:cert "server.crt" 
--server:ca "ca.crt" --server:passphrase "my_password"`
 
 Advanced configuration of SMTP for Web Console.
 -------------------------------------
@@ -90,9 +114,11 @@ In case of non GMail SMTP server it may require to change 
options in "settings.j
 Troubleshooting
 -------------------------------------
 1. On Windows check that MongoDB is not blocked by 
Antivirus/Firewall/Smartscreen.
-2. Root permission is required to bind to 80 port under Mac OS X and Linux, 
but you may always start Web Console on another port if you don't have such 
permission.
+2. Root permission is required to bind to 80 port under macOS and Linux, but 
you may always start Web Console
+   on another port if you don't have such permission.
 3. For extended debug output start Web Console as following:
-       On Linux execute command in terminal: `DEBUG=mongodb-* 
./ignite-web-console-linux`
-       On Windows execute two commands in terminal:
-               `SET DEBUG=mongodb-*`
-               `ignite-web-console-win.exe`
+     On Linux execute command in terminal: `DEBUG=mongodb-* 
./ignite-web-console-linux`
+     On macOS execute command in terminal: `DEBUG=mongodb-* 
./ignite-web-console-macos`
+     On Windows execute two commands in terminal:
+         `SET DEBUG=mongodb-*`
+         `ignite-web-console-win.exe`

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/app/browsersHandler.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/browsersHandler.js 
b/modules/web-console/backend/app/browsersHandler.js
index a7156c5..820c3d4 100644
--- a/modules/web-console/backend/app/browsersHandler.js
+++ b/modules/web-console/backend/app/browsersHandler.js
@@ -38,12 +38,12 @@ module.exports = {
              * @param {Socket} sock
              */
             add(sock) {
-                const token = sock.request.user._id.toString();
+                const key = sock.request.user._id.toString();
 
-                if (this.sockets.has(token))
-                    this.sockets.get(token).push(sock);
+                if (this.sockets.has(key))
+                    this.sockets.get(key).push(sock);
                 else
-                    this.sockets.set(token, [sock]);
+                    this.sockets.set(key, [sock]);
 
                 return this.sockets.get(sock.request.user);
             }
@@ -52,9 +52,9 @@ module.exports = {
              * @param {Socket} sock
              */
             remove(sock) {
-                const token = sock.request.user.token;
+                const key = sock.request.user._id.toString();
 
-                const sockets = this.sockets.get(token);
+                const sockets = this.sockets.get(key);
 
                 _.pull(sockets, sock);
 
@@ -62,17 +62,15 @@ module.exports = {
             }
 
             get(account) {
-                let sockets = this.sockets.get(account._id.toString());
+                const key = account._id.toString();
+
+                let sockets = this.sockets.get(key);
 
                 if (_.isEmpty(sockets))
-                    this.sockets.set(account._id.toString(), sockets = []);
+                    this.sockets.set(key, sockets = []);
 
                 return sockets;
             }
-
-            demo(token) {
-                return _.filter(this.sockets.get(token), (sock) => 
sock.request._query.IgniteDemoMode === 'true');
-            }
         }
 
         class BrowsersHandler {
@@ -191,10 +189,10 @@ module.exports = {
              * @param {Object.<String, String>} params
              * @return {Promise.<T>}
              */
-            executeOnNode(agent, demo, credentials, params) {
+            executeOnNode(agent, token, demo, credentials, params) {
                 return agent
                     .then((agentSock) => agentSock.emitEvent('node:rest',
-                        {uri: 'ignite', demo, params: _.merge({}, credentials, 
params)}));
+                        {uri: 'ignite', token, demo, params: _.merge({}, 
credentials, params)}));
             }
 
             registerVisorTask(taskId, taskCls, ...argCls) {
@@ -222,7 +220,9 @@ module.exports = {
 
                     const agent = this._agentHnd.agent(sock.request.user, 
demo, clusterId);
 
-                    this.executeOnNode(agent, demo, credentials, params)
+                    const token = sock.request.user.token;
+
+                    this.executeOnNode(agent, token, demo, credentials, params)
                         .then((data) => cb(null, data))
                         .catch((err) => cb(this.errorTransformer(err)));
                 });
@@ -282,7 +282,9 @@ module.exports = {
 
                     const agent = this._agentHnd.agent(sock.request.user, 
demo, clusterId);
 
-                    this.executeOnNode(agent, demo, credentials, exeParams)
+                    const token = sock.request.user.token;
+
+                    this.executeOnNode(agent, token, demo, credentials, 
exeParams)
                         .then((data) => {
                             if (data.finished && !data.zipped)
                                 return cb(null, data.result);

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/app/settings.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/settings.js 
b/modules/web-console/backend/app/settings.js
index 233566b..9c4d3c3 100644
--- a/modules/web-console/backend/app/settings.js
+++ b/modules/web-console/backend/app/settings.js
@@ -61,7 +61,7 @@ module.exports = {
             return v === 'true' || v === true;
         };
 
-        return {
+        const settings = {
             agent: {
                 dists: nconf.get('agent:dists') || dfltAgentDists
             },
@@ -69,15 +69,6 @@ module.exports = {
             server: {
                 host: nconf.get('server:host') || dfltHost,
                 port: _normalizePort(nconf.get('server:port') || dfltPort),
-                // eslint-disable-next-line eqeqeq
-                SSLOptions: _isTrue('server:ssl') && {
-                    enable301Redirects: true,
-                    trustXFPHeader: true,
-                    key: fs.readFileSync(nconf.get('server:key')),
-                    cert: fs.readFileSync(nconf.get('server:cert')),
-                    passphrase: nconf.get('server:keyPassphrase')
-                },
-                // eslint-disable-next-line eqeqeq
                 disableSignup: _isTrue('server:disable:signup')
             },
             mail,
@@ -86,5 +77,60 @@ module.exports = {
             sessionSecret: nconf.get('server:sessionSecret') || 'keyboard cat',
             tokenLength: 20
         };
+
+        // Configure SSL options.
+        if (_isTrue('server:ssl')) {
+            const sslOptions = {
+                enable301Redirects: true,
+                trustXFPHeader: true,
+                isServer: true
+            };
+
+            const setSslOption = (name, fromFile = false) => {
+                const v = nconf.get(`server:${name}`);
+
+                const hasOption = !!v;
+
+                if (hasOption)
+                    sslOptions[name] = fromFile ? fs.readFileSync(v) : v;
+
+                return hasOption;
+            };
+
+            const setSslOptionBoolean = (name) => {
+                const v = nconf.get(`server:${name}`);
+
+                if (v)
+                    sslOptions[name] = v === 'true' || v === true;
+            };
+
+            setSslOption('key', true);
+            setSslOption('cert', true);
+            setSslOption('ca', true);
+            setSslOption('passphrase');
+            setSslOption('ciphers');
+            setSslOption('secureProtocol');
+            setSslOption('clientCertEngine');
+            setSslOption('pfx', true);
+            setSslOption('crl');
+            setSslOption('dhparam');
+            setSslOption('ecdhCurve');
+            setSslOption('maxVersion');
+            setSslOption('minVersion');
+            setSslOption('secureOptions');
+            setSslOption('sessionIdContext');
+
+            setSslOptionBoolean('honorCipherOrder');
+            setSslOptionBoolean('requestCert');
+            setSslOptionBoolean('rejectUnauthorized');
+
+            // Special care for case, when user set password for something 
like "123456".
+            if (sslOptions.passphrase)
+                sslOptions.passphrase = sslOptions.passphrase.toString();
+
+            settings.server.SSLOptions = sslOptions;
+        }
+
+        return settings;
     }
 };

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/config/settings.json.sample
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/config/settings.json.sample 
b/modules/web-console/backend/config/settings.json.sample
index aa20ab4..c16ba26 100644
--- a/modules/web-console/backend/config/settings.json.sample
+++ b/modules/web-console/backend/config/settings.json.sample
@@ -1,26 +1,31 @@
 {
-    "server": {
-        "port": 3000,
-        "sessionSecret": "CHANGE ME",
-        "ssl": false,
-        "key": "serve/keys/test.key",
-        "cert": "serve/keys/test.crt",
-        "keyPassphrase": "password",
-        "disable": {
-            "signup": false
-        }
-    },
-    "mongodb": {
-        "url": "mongodb://localhost/console"
-    },
-    "mail": {
-        "service": "",
-        "from": "Some Company Web Console <some_username@some_company.com>",
-        "greeting": "Some Company Web Console",
-        "sign": "Kind regards,<br>Some Company Team",
-        "auth": {
-            "user": "some_username@some_company.com",
-            "pass": ""
-        }
+  "server": {
+    "port": 3000,
+    "sessionSecret": "CHANGE ME",
+    "ssl": false,
+    "key": "path to file with server.key",
+    "cert": "path to file with server.crt",
+    "ca": "path to file with ca.crt",
+    "passphrase": "password",
+    "ciphers": "ECDHE-RSA-AES128-GCM-SHA256",
+    "secureProtocol": "TLSv1_2_method",
+    "requestCert": false,
+    "rejectUnauthorized": false,
+    "disable": {
+      "signup": false
     }
+  },
+  "mongodb": {
+    "url": "mongodb://localhost/console"
+  },
+  "mail": {
+    "service": "gmail",
+    "from": "Some Company Web Console <some_username@some_company.com>",
+    "greeting": "Some Company Web Console",
+    "sign": "Kind regards,<br>Some Company Team",
+    "auth": {
+      "user": "some_username@some_company.com",
+      "pass": "CHANGE ME"
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/launch-tools.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/launch-tools.js 
b/modules/web-console/backend/launch-tools.js
index 3870b0d..0d6114f 100644
--- a/modules/web-console/backend/launch-tools.js
+++ b/modules/web-console/backend/launch-tools.js
@@ -55,7 +55,11 @@ const _onError = (addr, error) => {
  */
 const init = ([settings, apiSrv, agentsHnd, browsersHnd]) => {
     // Start rest server.
-    const srv = settings.server.SSLOptions ? 
https.createServer(settings.server.SSLOptions) : http.createServer();
+    const sslOptions = settings.server.SSLOptions;
+
+    console.log(`Starting ${sslOptions ? 'HTTPS' : 'HTTP'} server`);
+
+    const srv = sslOptions ? https.createServer(sslOptions) : 
http.createServer();
 
     srv.listen(settings.server.port, settings.server.host);
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/backend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/package.json 
b/modules/web-console/backend/package.json
index fb12748..4399ae7 100644
--- a/modules/web-console/backend/package.json
+++ b/modules/web-console/backend/package.json
@@ -68,7 +68,7 @@
     "passport-local-mongoose": "4.0.0",
     "passport.socketio": "3.7.0",
     "pkg": "4.3.1",
-    "socket.io": "1.7.3",
+    "socket.io": "2.1.1",
     "uuid": "3.1.0"
   },
   "devDependencies": {

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/app.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/app.js 
b/modules/web-console/frontend/app/app.js
index e1cbdbb..fd75ade 100644
--- a/modules/web-console/frontend/app/app.js
+++ b/modules/web-console/frontend/app/app.js
@@ -44,7 +44,6 @@ import './modules/configuration/configuration.module';
 import './modules/getting-started/GettingStarted.provider';
 import './modules/dialog/dialog.module';
 import './modules/ace.module';
-import './modules/socket.module';
 import './modules/loading/loading.module';
 import servicesModule from './services';
 // endignite
@@ -177,7 +176,6 @@ export default angular.module('ignite-console', [
     'ngSanitize',
     'ngMessages',
     // Third party libs.
-    'btford.socket-io',
     'dndLists',
     'gridster',
     'mgcrea.ngStrap',
@@ -201,7 +199,6 @@ export default angular.module('ignite-console', [
     'ignite-console.input-dialog',
     'ignite-console.user',
     'ignite-console.branding',
-    'ignite-console.socket',
     'ignite-console.agent',
     'ignite-console.nodes',
     'ignite-console.demo',

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js
 
b/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js
index 294f955..896c02b 100644
--- 
a/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js
+++ 
b/modules/web-console/frontend/app/components/connected-clusters-badge/controller.js
@@ -18,15 +18,17 @@
 import {tap} from 'rxjs/operators';
 
 export default class {
-    static $inject = ['AgentManager', 'ConnectedClustersDialog'];
+    static $inject = ['$scope', 'AgentManager', 'ConnectedClustersDialog'];
 
     connectedClusters = 0;
 
     /**
+     * @param $scope Angular scope.
      * @param {import('app/modules/agent/AgentManager.service').default} 
agentMgr
      * @param {import('../connected-clusters-dialog/service').default} 
connectedClustersDialog
      */
-    constructor(agentMgr, connectedClustersDialog) {
+    constructor($scope, agentMgr, connectedClustersDialog) {
+        this.$scope = $scope;
         this.agentMgr = agentMgr;
         this.connectedClustersDialog = connectedClustersDialog;
     }
@@ -40,7 +42,10 @@ export default class {
     $onInit() {
         this.connectedClusters$ = this.agentMgr.connectionSbj.pipe(
             tap(({ clusters }) => this.connectedClusters = clusters.length),
-            tap(({ clusters }) => this.clusters = clusters)
+            tap(({ clusters }) => {
+                this.clusters = clusters;
+                this.$scope.$applyAsync();
+            })
         )
         .subscribe();
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
----------------------------------------------------------------------
diff --git 
a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js 
b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
index 56eef35..0c0d9b6 100644
--- a/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
+++ b/modules/web-console/frontend/app/modules/agent/AgentManager.service.js
@@ -21,6 +21,8 @@ import {nonEmpty, nonNil} from 'app/utils/lodashMixins';
 import {BehaviorSubject} from 'rxjs';
 import {first, pluck, tap, distinctUntilChanged, map, filter} from 
'rxjs/operators';
 
+import io from 'socket.io-client';
+
 import AgentModal from './AgentModal.service';
 // @ts-ignore
 import Worker from './decompress.worker';
@@ -117,7 +119,7 @@ class ConnectionState {
 }
 
 export default class AgentManager {
-    static $inject = ['$rootScope', '$q', '$transitions', 
'igniteSocketFactory', 'AgentModal', 'UserNotifications', 'IgniteVersion', 
'ClusterLoginService'];
+    static $inject = ['$rootScope', '$q', '$transitions', 'AgentModal', 
'UserNotifications', 'IgniteVersion', 'ClusterLoginService'];
 
     /** @type {ng.IScope} */
     $root;
@@ -162,17 +164,15 @@ export default class AgentManager {
      * @param {ng.IRootScopeService} $root
      * @param {ng.IQService} $q
      * @param {import('@uirouter/angularjs').TransitionService} $transitions
-     * @param {unknown} socketFactory
      * @param {import('./AgentModal.service').default} agentModal
      * @param {import('app/components/user-notifications/service').default} 
UserNotifications
      * @param {import('app/services/Version.service').default} Version
      * @param {import('./components/cluster-login/service').default} 
ClusterLoginSrv
      */
-    constructor($root, $q, $transitions, socketFactory, agentModal, 
UserNotifications, Version, ClusterLoginSrv) {
+    constructor($root, $q, $transitions, agentModal, UserNotifications, 
Version, ClusterLoginSrv) {
         this.$root = $root;
         this.$q = $q;
         this.$transitions = $transitions;
-        this.socketFactory = socketFactory;
         this.agentModal = agentModal;
         this.UserNotifications = UserNotifications;
         this.Version = Version;
@@ -219,7 +219,9 @@ export default class AgentManager {
         if (nonNil(this.socket))
             return;
 
-        this.socket = this.socketFactory();
+        const options = this.isDemoMode() ? {query: 'IgniteDemoMode=true'} : 
{};
+
+        this.socket = io.connect(options);
 
         const onDisconnect = () => {
             const conn = this.connectionSbj.getValue();

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/modules/demo/Demo.module.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/modules/demo/Demo.module.js 
b/modules/web-console/frontend/app/modules/demo/Demo.module.js
index 9416bca..57f3a02 100644
--- a/modules/web-console/frontend/app/modules/demo/Demo.module.js
+++ b/modules/web-console/frontend/app/modules/demo/Demo.module.js
@@ -25,19 +25,15 @@ const DEMO_QUERY_STATE = {state: 'base.sql.notebook', 
params: {noteId: 'demo'}};
 /**
  * @param {import('@uirouter/angularjs').StateProvider} $state
  * @param {ng.IHttpProvider} $http
- * @param {unknown} socketFactory
  */
-export function DemoProvider($state, $http, socketFactory) {
+export function DemoProvider($state, $http) {
     if (/(\/demo.*)/ig.test(location.pathname))
         sessionStorage.setItem('IgniteDemoMode', 'true');
 
     const enabled = sessionStorage.getItem('IgniteDemoMode') === 'true';
 
-    if (enabled) {
-        socketFactory.set({query: 'IgniteDemoMode=true'});
-
+    if (enabled)
         $http.interceptors.push('demoInterceptor');
-    }
 
     function service($root) {
         $root.IgniteDemoMode = enabled;
@@ -50,7 +46,7 @@ export function DemoProvider($state, $http, socketFactory) {
     return this;
 }
 
-DemoProvider.$inject = ['$stateProvider', '$httpProvider', 
'igniteSocketFactoryProvider'];
+DemoProvider.$inject = ['$stateProvider', '$httpProvider'];
 
 /**
  * @param {{enabled: boolean}} Demo
@@ -178,11 +174,9 @@ function config($stateProvider) {
 config.$inject = ['$stateProvider'];
 
 angular
-.module('ignite-console.demo', [
-    'ignite-console.socket'
-])
-.config(config)
-.provider('Demo', DemoProvider)
-.factory('demoInterceptor', demoInterceptor)
-.provider('igniteDemoInfo', igniteDemoInfoProvider)
-.service('DemoInfo', DemoInfo);
+    .module('ignite-console.demo', [])
+    .config(config)
+    .provider('Demo', DemoProvider)
+    .factory('demoInterceptor', demoInterceptor)
+    .provider('igniteDemoInfo', igniteDemoInfoProvider)
+    .service('DemoInfo', DemoInfo);

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/utils/SimpleWorkerPool.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/utils/SimpleWorkerPool.js 
b/modules/web-console/frontend/app/utils/SimpleWorkerPool.js
index 25c7ae4..b751dc3 100644
--- a/modules/web-console/frontend/app/utils/SimpleWorkerPool.js
+++ b/modules/web-console/frontend/app/utils/SimpleWorkerPool.js
@@ -83,7 +83,7 @@ export default class SimpleWorkerPool {
         worker.postMessage(task.data);
 
         if (this.__dbg)
-            console.timeEnd('Post message');
+            console.timeEnd(`Post message[pool=${this._name}]`);
     }
 
     terminate() {

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/app/vendor.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/app/vendor.js 
b/modules/web-console/frontend/app/vendor.js
index 90808be..e9fc8e9 100644
--- a/modules/web-console/frontend/app/vendor.js
+++ b/modules/web-console/frontend/app/vendor.js
@@ -22,7 +22,6 @@ import 'angular-animate';
 import 'angular-sanitize';
 import 'angular-strap';
 import 'angular-strap/dist/angular-strap.tpl';
-import 'angular-socket-io';
 
 import 'angular-messages';
 import '@uirouter/angularjs';

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/package.json 
b/modules/web-console/frontend/package.json
index 9d848df..aa6e037 100644
--- a/modules/web-console/frontend/package.json
+++ b/modules/web-console/frontend/package.json
@@ -44,7 +44,6 @@
     "angular-nvd3": "1.0.9",
     "angular-sanitize": "1.7.4",
     "angular-smart-table": "2.1.11",
-    "angular-socket-io": "0.7.0",
     "angular-strap": "2.3.12",
     "angular-translate": "2.18.1",
     "angular-tree-control": "0.2.28",
@@ -60,7 +59,7 @@
     "file-saver": "1.3.3",
     "font-awesome": "4.7.0",
     "jquery": "3.2.1",
-    "json-bigint": "0.2.3",
+    "json-bigint": "0.3.0",
     "jsondiffpatch": "0.2.5",
     "jszip": "3.1.5",
     "lodash": "4.17.11",
@@ -71,7 +70,7 @@
     "resize-observer-polyfill": "1.5.0",
     "roboto-font": "0.1.0",
     "rxjs": "6.3.3",
-    "socket.io-client": "1.7.3",
+    "socket.io-client": "2.1.1",
     "tf-metatags": "2.0.0"
   },
   "devDependencies": {

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/frontend/webpack/webpack.dev.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/webpack/webpack.dev.js 
b/modules/web-console/frontend/webpack/webpack.dev.js
index 3af6377..59e3d45 100644
--- a/modules/web-console/frontend/webpack/webpack.dev.js
+++ b/modules/web-console/frontend/webpack/webpack.dev.js
@@ -23,9 +23,11 @@ const commonCfg = require('./webpack.common');
 
 const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 
-const backendPort = process.env.BACKEND_PORT || 3000;
-const devServerPort = process.env.PORT || 9000;
-const devServerHost = process.env.HOST || '0.0.0.0';
+const backendUrl = process.env.BACKEND_URL || 'http://localhost:3000';
+const webpackDevServerHost = process.env.HOST || '0.0.0.0';
+const webpackDevServerPort = process.env.PORT || 9000;
+
+console.log(`Backend url: ${backendUrl}`);
 
 module.exports = merge(commonCfg, {
     mode: 'development',
@@ -70,15 +72,18 @@ module.exports = merge(commonCfg, {
         inline: true,
         proxy: {
             '/socket.io': {
-                target: `http://localhost:${backendPort}`,
-                ws: true
+                target: backendUrl,
+                ws: true,
+                secure: false
             },
             '/agents': {
-                target: `http://localhost:${backendPort}`,
-                ws: true
+                target: backendUrl,
+                ws: true,
+                secure: false
             },
             '/api/*': {
-                target: `http://localhost:${backendPort}`
+                target: backendUrl,
+                secure: false
             }
         },
         watchOptions: {
@@ -86,7 +91,7 @@ module.exports = merge(commonCfg, {
             poll: 2000
         },
         stats: 'errors-only',
-        host: devServerHost,
-        port: devServerPort
+        host: webpackDevServerHost,
+        port: webpackDevServerPort
     }
 });

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/pom.xml
----------------------------------------------------------------------
diff --git a/modules/web-console/web-agent/pom.xml 
b/modules/web-console/web-agent/pom.xml
index 2a4864b..705f7ab 100644
--- a/modules/web-console/web-agent/pom.xml
+++ b/modules/web-console/web-agent/pom.xml
@@ -43,7 +43,7 @@
         <dependency>
             <groupId>io.socket</groupId>
             <artifactId>socket.io-client</artifactId>
-            <version>0.8.3</version>
+            <version>1.0.0</version>
         </dependency>
 
         <dependency>
@@ -55,13 +55,13 @@
         <dependency>
             <groupId>com.beust</groupId>
             <artifactId>jcommander</artifactId>
-            <version>1.48</version>
+            <version>1.58</version>
         </dependency>
 
         <dependency>
             <groupId>com.squareup.okhttp3</groupId>
             <artifactId>okhttp</artifactId>
-            <version>3.7.0</version>
+            <version>3.12.0</version>
         </dependency>
 
         <dependency>
@@ -112,6 +112,18 @@
     <build>
         <finalName>ignite-web-agent-${project.version}</finalName>
 
+        <testResources>
+            <testResource>
+                <directory>src/test/java</directory>
+                <excludes>
+                    <exclude>**/*.java</exclude>
+                </excludes>
+            </testResource>
+            <testResource>
+                <directory>src/test/resources</directory>
+            </testResource>
+        </testResources>
+
         <plugins>
             <plugin>
                 <artifactId>maven-jar-plugin</artifactId>

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
----------------------------------------------------------------------
diff --git 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
index bb2a8a2..1a919d0 100644
--- 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
+++ 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.console.agent;
 
-import com.beust.jcommander.Parameter;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -28,6 +27,8 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Properties;
+import java.util.stream.Collectors;
+import com.beust.jcommander.Parameter;
 import org.apache.ignite.internal.util.typedef.F;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -96,6 +97,51 @@ public class AgentConfiguration {
     private Boolean disableDemo;
 
     /** */
+    @Parameter(names = {"-nks", "--node-key-store"},
+        description = "Path to key store that will be used to connect to 
cluster")
+    private String nodeKeyStore;
+
+    /** */
+    @Parameter(names = {"-nkp", "--node-key-store-password"},
+        description = "Optional password for node key store")
+    private String nodeKeyStorePass;
+
+    /** */
+    @Parameter(names = {"-nts", "--node-trust-store"},
+        description = "Path to trust store that will be used to connect to 
cluster")
+    private String nodeTrustStore;
+
+    /** */
+    @Parameter(names = {"-ntp", "--node-trust-store-password"},
+        description = "Optional password for node trust store")
+    private String nodeTrustStorePass;
+
+    /** */
+    @Parameter(names = {"-sks", "--server-key-store"},
+        description = "Path to key store that will be used to connect to Web 
server")
+    private String srvKeyStore;
+
+    /** */
+    @Parameter(names = {"-skp", "--server-key-store-password"},
+        description = "Optional password for server key store")
+    private String srvKeyStorePass;
+
+    /** */
+    @Parameter(names = {"-sts", "--server-trust-store"},
+        description = "Path to trust store that will be used to connect to Web 
server")
+    private String srvTrustStore;
+
+    /** */
+    @Parameter(names = {"-stp", "--server-trust-store-password"},
+        description = "Optional password for server trust store")
+    private String srvTrustStorePass;
+
+    /** */
+    @Parameter(names = {"-cs", "--cipher-suites"},
+        description = "Optional comma-separated list of SSL cipher suites to 
be used to connect to server and cluster")
+    private List<String> cipherSuites;
+
+    /** */
     @Parameter(names = {"-h", "--help"}, help = true, description = "Print 
this help message")
     private Boolean help;
 
@@ -219,6 +265,132 @@ public class AgentConfiguration {
     }
 
     /**
+     * @return Path to node key store.
+     */
+    public String nodeKeyStore() {
+        return nodeKeyStore;
+    }
+
+    /**
+     * @param nodeKeyStore Path to node key store.
+     */
+    public void nodeKeyStore(String nodeKeyStore) {
+        this.nodeKeyStore = nodeKeyStore;
+    }
+
+    /**
+     * @return Node key store password.
+     */
+    public String nodeKeyStorePassword() {
+        return nodeKeyStorePass;
+    }
+
+    /**
+     * @param nodeKeyStorePass Node key store password.
+     */
+    public void nodeKeyStorePassword(String nodeKeyStorePass) {
+        this.nodeKeyStorePass = nodeKeyStorePass;
+    }
+
+    /**
+     * @return Path to node trust store.
+     */
+    public String nodeTrustStore() {
+        return nodeTrustStore;
+    }
+
+    /**
+     * @param nodeTrustStore Path to node trust store.
+     */
+    public void nodeTrustStore(String nodeTrustStore) {
+        this.nodeTrustStore = nodeTrustStore;
+    }
+
+    /**
+     * @return Node trust store password.
+     */
+    public String nodeTrustStorePassword() {
+        return nodeTrustStorePass;
+    }
+
+    /**
+     * @param nodeTrustStorePass Node trust store password.
+     */
+    public void nodeTrustStorePassword(String nodeTrustStorePass) {
+        this.nodeTrustStorePass = nodeTrustStorePass;
+    }
+
+    /**
+     * @return Path to server key store.
+     */
+    public String serverKeyStore() {
+        return srvKeyStore;
+    }
+
+    /**
+     * @param srvKeyStore Path to server key store.
+     */
+    public void serverKeyStore(String srvKeyStore) {
+        this.srvKeyStore = srvKeyStore;
+    }
+
+    /**
+     * @return Server key store password.
+     */
+    public String serverKeyStorePassword() {
+        return srvKeyStorePass;
+    }
+
+    /**
+     * @param srvKeyStorePass Server key store password.
+     */
+    public void serverKeyStorePassword(String srvKeyStorePass) {
+        this.srvKeyStorePass = srvKeyStorePass;
+    }
+
+    /**
+     * @return Path to server trust store.
+     */
+    public String serverTrustStore() {
+        return srvTrustStore;
+    }
+
+    /**
+     * @param srvTrustStore Path to server trust store.
+     */
+    public void serverTrustStore(String srvTrustStore) {
+        this.srvTrustStore = srvTrustStore;
+    }
+
+    /**
+     * @return Server trust store password.
+     */
+    public String serverTrustStorePassword() {
+        return srvTrustStorePass;
+    }
+
+    /**
+     * @param srvTrustStorePass Server trust store password.
+     */
+    public void serverTrustStorePassword(String srvTrustStorePass) {
+        this.srvTrustStorePass = srvTrustStorePass;
+    }
+
+    /**
+     * @return SSL cipher suites.
+     */
+    public List<String> cipherSuites() {
+        return cipherSuites;
+    }
+
+    /**
+     * @param cipherSuites SSL cipher suites.
+     */
+    public void cipherSuites(List<String> cipherSuites) {
+        this.cipherSuites = cipherSuites;
+    }
+
+    /**
      * @return {@code true} If agent options usage should be printed.
      */
     public Boolean help() {
@@ -235,35 +407,81 @@ public class AgentConfiguration {
             props.load(reader);
         }
 
-        String val = (String)props.remove("tokens");
+        String val = props.getProperty("tokens");
 
         if (val != null)
             tokens(new ArrayList<>(Arrays.asList(val.split(","))));
 
-        val = (String)props.remove("server-uri");
+        val = props.getProperty("server-uri");
 
         if (val != null)
             serverUri(val);
 
-        val = (String)props.remove("node-uri");
+        val = props.getProperty("node-uri");
 
+        // Intentionaly wrapped by ArrayList, for further maniulations.
         if (val != null)
             nodeURIs(new ArrayList<>(Arrays.asList(val.split(","))));
 
-        val = (String)props.remove("node-login");
+        val = props.getProperty("node-login");
 
         if (val != null)
             nodeLogin(val);
 
-        val = (String)props.remove("node-password");
+        val = props.getProperty("node-password");
 
         if (val != null)
             nodePassword(val);
 
-        val = (String)props.remove("driver-folder");
+        val = props.getProperty("driver-folder");
 
         if (val != null)
             driversFolder(val);
+
+        val = props.getProperty("node-key-store");
+
+        if (val != null)
+            nodeKeyStore(val);
+
+        val = props.getProperty("node-key-store-password");
+
+        if (val != null)
+            nodeKeyStorePassword(val);
+
+        val = props.getProperty("node-trust-store");
+
+        if (val != null)
+            nodeTrustStore(val);
+
+        val = props.getProperty("node-trust-store-password");
+
+        if (val != null)
+            nodeTrustStorePassword(val);
+
+        val = props.getProperty("server-key-store");
+
+        if (val != null)
+            serverKeyStore(val);
+
+        val = props.getProperty("server-key-store-password");
+
+        if (val != null)
+            serverKeyStorePassword(val);
+
+        val = props.getProperty("server-trust-store");
+
+        if (val != null)
+            serverTrustStore(val);
+
+        val = props.getProperty("server-trust-store-password");
+
+        if (val != null)
+            serverTrustStorePassword(val);
+
+        val = props.getProperty("cipher-suites");
+
+        if (val != null)
+            cipherSuites(Arrays.asList(val.split(",")));
     }
 
     /**
@@ -296,43 +514,66 @@ public class AgentConfiguration {
 
         if (disableDemo == null)
             disableDemo(cfg.disableDemo());
+
+        if (nodeKeyStore == null)
+            nodeKeyStore(cfg.nodeKeyStore());
+
+        if (nodeKeyStorePass == null)
+            nodeKeyStorePassword(cfg.nodeKeyStorePassword());
+
+        if (nodeTrustStore == null)
+            nodeTrustStore(cfg.nodeTrustStore());
+
+        if (nodeTrustStorePass == null)
+            nodeTrustStorePassword(cfg.nodeTrustStorePassword());
+
+        if (srvKeyStore == null)
+            serverKeyStore(cfg.serverKeyStore());
+
+        if (srvKeyStorePass == null)
+            serverKeyStorePassword(cfg.serverKeyStorePassword());
+
+        if (srvTrustStore == null)
+            serverTrustStore(cfg.serverTrustStore());
+
+        if (srvTrustStorePass == null)
+            serverTrustStorePassword(cfg.serverTrustStorePassword());
+
+        if (cipherSuites == null)
+            cipherSuites(cfg.cipherSuites());
+    }
+
+    /**
+     * @param s String with sensitive data.
+     * @return Secured string.
+     */
+    private String secured(String s) {
+        int len = s.length();
+        int toShow = len > 4 ? 4 : 1;
+
+        return new String(new char[len - toShow]).replace('\0', '*') + 
s.substring(len - toShow, len);
     }
 
     /** {@inheritDoc} */
     @Override public String toString() {
         StringBuilder sb = new StringBuilder();
 
+        String nl = System.lineSeparator();
+
         if (!F.isEmpty(tokens)) {
             sb.append("User's security tokens          : ");
 
-            boolean first = true;
-
-            for (String tok : tokens) {
-                if (first)
-                    first = false;
-                else
-                    sb.append(',');
-
-                if (tok.length() > 4) {
-                    sb.append(new String(new char[tok.length() - 
4]).replace('\0', '*'));
-
-                    sb.append(tok.substring(tok.length() - 4));
-                }
-                else
-                    sb.append(new String(new char[tok.length()]).replace('\0', 
'*'));
-            }
-
-            sb.append('\n');
+            
sb.append(tokens.stream().map(this::secured).collect(Collectors.joining(", 
"))).append(nl);
         }
 
         sb.append("URI to Ignite node REST server  : ")
-            .append(nodeURIs == null ? DFLT_NODE_URI : String.join(", ", 
nodeURIs)).append('\n');
+            .append(nodeURIs == null ? DFLT_NODE_URI : String.join(", ", 
nodeURIs)).append(nl);
 
         if (nodeLogin != null)
-            sb.append("Login to Ignite node REST server: 
").append(nodeLogin).append('\n');
+            sb.append("Login to Ignite node REST server: 
").append(nodeLogin).append(nl);
 
-        sb.append("URI to Ignite Console server    : ").append(srvUri == null 
? DFLT_SERVER_URI : srvUri).append('\n');
-        sb.append("Path to agent property file     : 
").append(configPath()).append('\n');
+        sb.append("URI to Ignite Console server    : ").append(srvUri == null 
? DFLT_SERVER_URI : srvUri).append(nl);
+        sb.append("Path to agent property file     : 
").append(configPath()).append(nl);
 
         String drvFld = driversFolder();
 
@@ -343,8 +584,35 @@ public class AgentConfiguration {
                 drvFld = new File(agentHome, "jdbc-drivers").getPath();
         }
 
-        sb.append("Path to JDBC drivers folder     : 
").append(drvFld).append('\n');
-        sb.append("Demo mode                       : ").append(disableDemo() ? 
"disabled" : "enabled");
+        sb.append("Path to JDBC drivers folder     : 
").append(drvFld).append(nl);
+        sb.append("Demo mode                       : ").append(disableDemo() ? 
"disabled" : "enabled").append(nl);
+
+        if (!F.isEmpty(nodeKeyStore))
+            sb.append("Node key store                  : 
").append(nodeKeyStore).append(nl);
+
+        if (!F.isEmpty(nodeKeyStorePass))
+            sb.append("Node key store password         : 
").append(secured(nodeKeyStorePass)).append(nl);
+
+        if (!F.isEmpty(nodeTrustStore))
+            sb.append("Node trust store                : 
").append(nodeTrustStore).append(nl);
+
+        if (!F.isEmpty(nodeTrustStorePass))
+            sb.append("Node trust store password       : 
").append(secured(nodeTrustStorePass)).append(nl);
+
+        if (!F.isEmpty(srvKeyStore))
+            sb.append("Server key store                : 
").append(srvKeyStore).append(nl);
+
+        if (!F.isEmpty(srvKeyStorePass))
+            sb.append("Server key store password       : 
").append(secured(srvKeyStorePass)).append(nl);
+
+        if (!F.isEmpty(srvTrustStore))
+            sb.append("Server trust store              : 
").append(srvTrustStore).append(nl);
+
+        if (!F.isEmpty(srvTrustStorePass))
+            sb.append("Server trust store password     : 
").append(secured(srvTrustStorePass)).append(nl);
+
+        if (!F.isEmpty(cipherSuites))
+            sb.append("Cipher suites                   : 
").append(String.join(", ", cipherSuites)).append(nl);
 
         return sb.toString();
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
----------------------------------------------------------------------
diff --git 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
index 579f236..9553aac 100644
--- 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
+++ 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
@@ -17,12 +17,6 @@
 
 package org.apache.ignite.console.agent;
 
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.ParameterException;
-import io.socket.client.Ack;
-import io.socket.client.IO;
-import io.socket.client.Socket;
-import io.socket.emitter.Emitter;
 import java.io.File;
 import java.io.IOException;
 import java.net.Authenticator;
@@ -40,9 +34,16 @@ import java.util.Scanner;
 import java.util.concurrent.CountDownLatch;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
-import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.TrustManager;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+import io.socket.client.Ack;
+import io.socket.client.IO;
+import io.socket.client.Socket;
+import io.socket.emitter.Emitter;
+import okhttp3.OkHttpClient;
 import org.apache.ignite.console.agent.handlers.ClusterListener;
 import org.apache.ignite.console.agent.handlers.DatabaseListener;
 import org.apache.ignite.console.agent.handlers.RestListener;
@@ -61,6 +62,8 @@ import static io.socket.client.Socket.EVENT_CONNECT_ERROR;
 import static io.socket.client.Socket.EVENT_DISCONNECT;
 import static io.socket.client.Socket.EVENT_ERROR;
 import static org.apache.ignite.console.agent.AgentUtils.fromJSON;
+import static org.apache.ignite.console.agent.AgentUtils.sslConnectionSpec;
+import static org.apache.ignite.console.agent.AgentUtils.sslSocketFactory;
 import static org.apache.ignite.console.agent.AgentUtils.toJSON;
 import static org.apache.ignite.console.agent.AgentUtils.trustManager;
 
@@ -315,28 +318,75 @@ public class AgentLauncher {
             return;
         }
 
+        boolean trustAll = Boolean.getBoolean("trust.all");
+        boolean hasServerTrustStore = cfg.serverTrustStore() != null;
+        boolean hasNodeTrustStore = cfg.nodeTrustStore() != null;
+
+        if (trustAll && hasServerTrustStore) {
+            log.warn("Options contains both '--server-trust-store' and 
'-Dtrust.all=true'. " +
+                "Option '-Dtrust.all=true' will be ignored.");
+
+            trustAll = false;
+        }
+
+        if (trustAll && hasNodeTrustStore) {
+            log.warn("Options contains both '--node-trust-store' and 
'-Dtrust.all=true'. " +
+                "Option '-Dtrust.all=true' will be ignored.");
+
+            trustAll = false;
+        }
+
         cfg.nodeURIs(nodeURIs);
 
         IO.Options opts = new IO.Options();
-
         opts.path = "/agents";
 
-        // Workaround for use self-signed certificate
-        if (Boolean.getBoolean("trust.all")) {
-            log.info("Trust to all certificates mode is enabled.");
-
-            SSLContext ctx = SSLContext.getInstance("TLS");
+        List<String> cipherSuites = cfg.cipherSuites();
+
+        if (
+            trustAll ||
+            hasServerTrustStore ||
+            cfg.serverKeyStore() != null
+        ) {
+            OkHttpClient.Builder builder = new OkHttpClient.Builder();
+
+            X509TrustManager serverTrustMgr = trustManager(
+                trustAll,
+                cfg.serverTrustStore(),
+                cfg.serverTrustStorePassword()
+            );
+
+            SSLSocketFactory sslSocketFactory = sslSocketFactory(
+                cfg.serverKeyStore(),
+                cfg.serverKeyStorePassword(),
+                serverTrustMgr,
+                cipherSuites
+            );
+
+            if (sslSocketFactory != null) {
+                builder.sslSocketFactory(sslSocketFactory, serverTrustMgr);
+
+                if (!F.isEmpty(cipherSuites))
+                    builder.connectionSpecs(sslConnectionSpec(cipherSuites));
+            }
 
-            // Create an SSLContext that uses our TrustManager
-            ctx.init(null, new TrustManager[] {trustManager()}, null);
+            OkHttpClient sslFactory = builder.build();
 
-            opts.sslContext = ctx;
+            opts.callFactory = sslFactory;
+            opts.webSocketFactory = sslFactory;
+            opts.secure = true;
         }
 
         final Socket client = IO.socket(uri, opts);
 
-        try (RestExecutor restExecutor = new RestExecutor();
-             ClusterListener clusterLsnr = new ClusterListener(cfg, client, 
restExecutor)) {
+        try (
+            RestExecutor restExecutor = new RestExecutor(
+                cfg.nodeKeyStore(), cfg.nodeKeyStorePassword(),
+                cfg.nodeTrustStore(), cfg.nodeTrustStorePassword(),
+                cipherSuites);
+
+            ClusterListener clusterLsnr = new ClusterListener(cfg, client, 
restExecutor)
+        ) {
             Emitter.Listener onConnect = connectRes -> {
                 log.info("Connection established.");
 
@@ -416,6 +466,7 @@ public class AgentLauncher {
             };
 
             DatabaseListener dbHnd = new DatabaseListener(cfg);
+
             RestListener restHnd = new RestListener(cfg, restExecutor);
 
             final CountDownLatch latch = new CountDownLatch(1);

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
----------------------------------------------------------------------
diff --git 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
index 38edd97..2242eb1 100644
--- 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
+++ 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
@@ -17,16 +17,32 @@
 
 package org.apache.ignite.console.agent;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
-import io.socket.client.Ack;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
 import java.security.ProtectionDomain;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
+import io.socket.client.Ack;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
+import okhttp3.ConnectionSpec;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.ssl.SSLContextWrapper;
 import org.apache.log4j.Logger;
 import org.json.JSONArray;
 import org.json.JSONObject;
@@ -38,6 +54,9 @@ public class AgentUtils {
     /** */
     private static final Logger log = 
Logger.getLogger(AgentUtils.class.getName());
 
+    /** */
+    private static final char[] EMPTY_PWD = new char[0];
+
     /** JSON object mapper. */
     private static final ObjectMapper MAPPER = new ObjectMapper();
 
@@ -50,7 +69,7 @@ public class AgentUtils {
     private static final Ack NOOP_CB = new Ack() {
         @Override public void call(Object... args) {
             if (args != null && args.length > 0 && args[0] instanceof 
Throwable)
-                log.error("Failed to execute request on agent.", (Throwable) 
args[0]);
+                log.error("Failed to execute request on agent.", 
(Throwable)args[0]);
             else
                 log.info("Request on agent successfully executed " + 
Arrays.toString(args));
         }
@@ -186,13 +205,120 @@ public class AgentUtils {
     }
 
     /**
-     * Create a trust manager that trusts all certificates It is not using a 
particular keyStore
+     * @param pathToJks Path to java key store file.
+     * @param pwd Key store password.
+     * @return Key store.
+     * @throws GeneralSecurityException If failed to load key store.
+     * @throws IOException If failed to load key store file content.
+     */
+    private static KeyStore keyStore(String pathToJks, char[] pwd) throws 
GeneralSecurityException, IOException {
+        KeyStore keyStore = KeyStore.getInstance("JKS");
+        keyStore.load(new FileInputStream(pathToJks), pwd);
+
+        return keyStore;
+    }
+
+    /**
+     * @param keyStorePath Path to key store.
+     * @param keyStorePwd Key store password.
+     * @return Key managers.
+     * @throws GeneralSecurityException If failed to load key store.
+     * @throws IOException If failed to load key store file content.
+     */
+    private static KeyManager[] keyManagers(String keyStorePath, String 
keyStorePwd)
+        throws GeneralSecurityException, IOException {
+        if (keyStorePath == null)
+            return null;
+
+        char[] keyPwd = keyStorePwd != null ? keyStorePwd.toCharArray() : 
EMPTY_PWD;
+
+        KeyStore keyStore = keyStore(keyStorePath, keyPwd);
+
+        KeyManagerFactory kmf = 
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        kmf.init(keyStore, keyPwd);
+
+        return kmf.getKeyManagers();
+    }
+
+    /**
+     * @param trustAll {@code true} If we trust to self-signed sertificates.
+     * @param trustStorePath Path to trust store file.
+     * @param trustStorePwd Trust store password.
+     * @return Trust manager
+     * @throws GeneralSecurityException If failed to load trust store.
+     * @throws IOException If failed to load trust store file content.
+     */
+    public static X509TrustManager trustManager(boolean trustAll, String 
trustStorePath, String trustStorePwd)
+        throws GeneralSecurityException, IOException {
+        if (trustAll)
+            return disabledTrustManager();
+
+        if (trustStorePath == null)
+            return null;
+
+        char[] trustPwd = trustStorePwd != null ? trustStorePwd.toCharArray() 
: EMPTY_PWD;
+        KeyStore trustKeyStore = keyStore(trustStorePath, trustPwd);
+
+        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+        tmf.init(trustKeyStore);
+
+        TrustManager[] trustMgrs = tmf.getTrustManagers();
+
+        return (X509TrustManager)Arrays.stream(trustMgrs)
+            .filter(tm -> tm instanceof X509TrustManager)
+            .findFirst()
+            .orElseThrow(() -> new IllegalStateException("X509TrustManager 
manager not found"));
+    }
+
+    /**
+     * Create SSL socket factory.
+     *
+     * @param keyStorePath Path to key store.
+     * @param keyStorePwd Key store password.
+     * @param trustMgr Trust manager.
+     * @param cipherSuites Optional cipher suites.
+     * @throws GeneralSecurityException If failed to load trust store.
+     * @throws IOException If failed to load store file content.
+     */
+    public static SSLSocketFactory sslSocketFactory(
+        String keyStorePath, String keyStorePwd,
+        X509TrustManager trustMgr,
+        List<String> cipherSuites
+    ) throws GeneralSecurityException, IOException {
+        KeyManager[] keyMgrs = keyManagers(keyStorePath, keyStorePwd);
+
+        if (keyMgrs == null && trustMgr == null)
+            return null;
+
+        SSLContext ctx = SSLContext.getInstance("TLS");
+
+        if (!F.isEmpty(cipherSuites))
+            ctx = new SSLContextWrapper(ctx, new 
SSLParameters(cipherSuites.toArray(new String[0])));
+
+        ctx.init(keyMgrs, new TrustManager[] {trustMgr}, null);
+
+        return ctx.getSocketFactory();
+    }
+
+    /**
+     * Create SSL configuration.
+     *
+     * @param cipherSuites SSL cipher suites.
+     */
+    public static List<ConnectionSpec> sslConnectionSpec(List<String> 
cipherSuites) {
+        return Collections.singletonList(new 
ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+            .cipherSuites(cipherSuites.toArray(new String[0]))
+            .build());
+    }
+
+    /**
+     * Create a trust manager that trusts all certificates.
      */
-    public static X509TrustManager trustManager() {
+    private static X509TrustManager disabledTrustManager() {
         return new X509TrustManager() {
             /** {@inheritDoc} */
             @Override public X509Certificate[] getAcceptedIssuers() {
-                return new X509Certificate[0];
+                return null;
             }
 
             /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java
----------------------------------------------------------------------
diff --git 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java
 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java
index 33e4c2b..e0cd595 100644
--- 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java
+++ 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java
@@ -95,12 +95,17 @@ abstract class AbstractListener implements Emitter.Listener 
{
                     }
 
                     cb.call(null, toJSON(res));
-                } catch (Exception e) {
+                }
+                catch (Throwable e) {
+                    log.error("Failed to process event in pool", e);
+
                     cb.call(e, null);
                 }
             });
         }
-        catch (Exception e) {
+        catch (Throwable e) {
+            log.error("Failed to process event", e);
+
             cb.call(e, null);
         }
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
----------------------------------------------------------------------
diff --git 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
index 14d3d5d..a9f1579 100644
--- 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
+++ 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
@@ -92,8 +92,8 @@ public class ClusterListener implements AutoCloseable {
     /** */
     private static final String EVENT_CLUSTER_DISCONNECTED = 
"cluster:disconnected";
 
-    /** Default timeout. */
-    private static final long DFLT_TIMEOUT = 3000L;
+    /** Topology refresh frequency. */
+    private static final long REFRESH_FREQ = 3000L;
 
     /** JSON object mapper. */
     private static final ObjectMapper MAPPER = new GridJettyObjectMapper();
@@ -116,13 +116,13 @@ public class ClusterListener implements AutoCloseable {
     };
 
     /** */
-    private AgentConfiguration cfg;
+    private final AgentConfiguration cfg;
 
     /** */
-    private Socket client;
+    private final Socket client;
 
     /** */
-    private RestExecutor restExecutor;
+    private final RestExecutor restExecutor;
 
     /** */
     private static final ScheduledExecutorService pool = 
Executors.newScheduledThreadPool(1);
@@ -132,7 +132,7 @@ public class ClusterListener implements AutoCloseable {
 
     /**
      * @param client Client.
-     * @param restExecutor Client.
+     * @param restExecutor REST executor.
      */
     public ClusterListener(AgentConfiguration cfg, Socket client, RestExecutor 
restExecutor) {
         this.cfg = cfg;
@@ -179,7 +179,7 @@ public class ClusterListener implements AutoCloseable {
     public void watch() {
         safeStopRefresh();
 
-        refreshTask = pool.scheduleWithFixedDelay(watchTask, 0L, DFLT_TIMEOUT, 
TimeUnit.MILLISECONDS);
+        refreshTask = pool.scheduleWithFixedDelay(watchTask, 0L, REFRESH_FREQ, 
TimeUnit.MILLISECONDS);
     }
 
     /** {@inheritDoc} */
@@ -426,14 +426,15 @@ public class ClusterListener implements AutoCloseable {
         /**
          * Collect topology.
          *
-         * @param full Full.
+         * @return REST result.
+         * @throws IOException If failed to collect topology.
          */
-        private RestResult topology(boolean full) throws IOException {
-            Map<String, Object> params = U.newHashMap(3);
+        private RestResult topology() throws IOException {
+            Map<String, Object> params = U.newHashMap(4);
 
             params.put("cmd", "top");
             params.put("attr", true);
-            params.put("mtr", full);
+            params.put("mtr", false);
             params.put("caches", false);
 
             return restCommand(params);
@@ -476,57 +477,47 @@ public class ClusterListener implements AutoCloseable {
 
             RestResult res = restCommand(params);
 
-            switch (res.getStatus()) {
-                case STATUS_SUCCESS:
-                    if (v23)
-                        return Boolean.valueOf(res.getData());
-
-                    return res.getData().contains("\"active\":true");
+            if (res.getStatus() == STATUS_SUCCESS)
+                return v23 ? Boolean.valueOf(res.getData()) : 
res.getData().contains("\"active\":true");
 
-                default:
-                    throw new IOException(res.getError());
-            }
+            throw new IOException(res.getError());
         }
 
-
         /** {@inheritDoc} */
         @Override public void run() {
             try {
-                RestResult res = topology(false);
-
-                switch (res.getStatus()) {
-                    case STATUS_SUCCESS:
-                        List<GridClientNodeBean> nodes = 
MAPPER.readValue(res.getData(),
-                            new TypeReference<List<GridClientNodeBean>>() {});
-
-                        TopologySnapshot newTop = new TopologySnapshot(nodes);
+                RestResult res = topology();
 
-                        if (newTop.differentCluster(top))
-                            log.info("Connection successfully established to 
cluster with nodes: " + newTop.nid8());
-                        else if (newTop.topologyChanged(top))
-                            log.info("Cluster topology changed, new topology: 
" + newTop.nid8());
+                if (res.getStatus() == STATUS_SUCCESS) {
+                    List<GridClientNodeBean> nodes = 
MAPPER.readValue(res.getData(),
+                        new TypeReference<List<GridClientNodeBean>>() {});
 
-                        boolean active = active(newTop.clusterVersion(), 
F.first(newTop.getNids()));
+                    TopologySnapshot newTop = new TopologySnapshot(nodes);
 
-                        newTop.setActive(active);
-                        newTop.setSecured(!F.isEmpty(res.getSessionToken()));
+                    if (newTop.differentCluster(top))
+                        log.info("Connection successfully established to 
cluster with nodes: " + newTop.nid8());
+                    else if (newTop.topologyChanged(top))
+                        log.info("Cluster topology changed, new topology: " + 
newTop.nid8());
 
-                        top = newTop;
+                    boolean active = active(newTop.clusterVersion(), 
F.first(newTop.getNids()));
 
-                        client.emit(EVENT_CLUSTER_TOPOLOGY, toJSON(top));
+                    newTop.setActive(active);
+                    newTop.setSecured(!F.isEmpty(res.getSessionToken()));
 
-                        break;
+                    top = newTop;
 
-                    default:
-                        LT.warn(log, res.getError());
+                    client.emit(EVENT_CLUSTER_TOPOLOGY, toJSON(top));
+                }
+                else {
+                    LT.warn(log, res.getError());
 
-                        clusterDisconnect();
+                    clusterDisconnect();
                 }
             }
             catch (ConnectException ignored) {
                 clusterDisconnect();
             }
-            catch (Exception e) {
+            catch (Throwable e) {
                 log.error("WatchTask failed", e);
 
                 clusterDisconnect();

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java
----------------------------------------------------------------------
diff --git 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java
 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java
index 24c2097..0db8904 100644
--- 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java
+++ 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java
@@ -24,6 +24,7 @@ import org.apache.ignite.console.agent.AgentConfiguration;
 import org.apache.ignite.console.agent.rest.RestExecutor;
 import org.apache.ignite.console.agent.rest.RestResult;
 import org.apache.ignite.console.demo.AgentClusterDemo;
+import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
@@ -38,7 +39,7 @@ public class RestListener extends AbstractListener {
 
     /**
      * @param cfg Config.
-     * @param restExecutor Executor.
+     * @param restExecutor REST executor.
      */
     public RestListener(AgentConfiguration cfg, RestExecutor restExecutor) {
         this.cfg = cfg;
@@ -65,6 +66,9 @@ public class RestListener extends AbstractListener {
 
         boolean demo = (boolean)args.get("demo");
 
+        if (F.isEmpty((String)args.get("token")))
+            return RestResult.fail(401, "Request does not contain user 
token.");
+
         Map<String, Object> headers = null;
 
         if (args.containsKey("headers"))

http://git-wip-us.apache.org/repos/asf/ignite/blob/9f9bb752/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
----------------------------------------------------------------------
diff --git 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
index d3bdcdd..b452b2c 100644
--- 
a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
+++ 
b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
@@ -20,11 +20,10 @@ package org.apache.ignite.console.agent.rest;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.net.ConnectException;
+import java.security.GeneralSecurityException;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
 import com.fasterxml.jackson.core.JsonFactory;
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonParser;
@@ -33,6 +32,8 @@ import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonDeserializer;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
 import okhttp3.Dispatcher;
 import okhttp3.FormBody;
 import okhttp3.HttpUrl;
@@ -41,6 +42,7 @@ import okhttp3.Request;
 import okhttp3.Response;
 import org.apache.ignite.IgniteLogger;
 import 
org.apache.ignite.internal.processors.rest.protocols.http.jetty.GridJettyObjectMapper;
+import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.LT;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.logger.slf4j.Slf4jLogger;
@@ -49,6 +51,8 @@ import org.slf4j.LoggerFactory;
 import static com.fasterxml.jackson.core.JsonToken.END_ARRAY;
 import static com.fasterxml.jackson.core.JsonToken.END_OBJECT;
 import static com.fasterxml.jackson.core.JsonToken.START_ARRAY;
+import static org.apache.ignite.console.agent.AgentUtils.sslConnectionSpec;
+import static org.apache.ignite.console.agent.AgentUtils.sslSocketFactory;
 import static org.apache.ignite.console.agent.AgentUtils.trustManager;
 import static 
org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_AUTH_FAILED;
 import static 
org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_FAILED;
@@ -68,14 +72,28 @@ public class RestExecutor implements AutoCloseable {
     private final OkHttpClient httpClient;
 
     /** Index of alive node URI. */
-    private Map<List<String>, Integer> startIdxs = U.newHashMap(2);
+    private final Map<List<String>, Integer> startIdxs = U.newHashMap(2);
 
     /**
-     * Default constructor.
+     * Constructor.
+     *
+     * @param keyStorePath Optional path to key store file.
+     * @param keyStorePwd Optional password for key store.
+     * @param trustStorePath Optional path to trust store file.
+     * @param trustStorePwd Optional password for trust store.
+     * @param cipherSuites Optional cipher suites.
+     * @throws GeneralSecurityException If failed to initialize SSL.
+     * @throws IOException If failed to load content of key stores.
      */
-    public RestExecutor() {
+    public RestExecutor(
+        String keyStorePath,
+        String keyStorePwd,
+        String trustStorePath,
+        String trustStorePwd,
+        List<String> cipherSuites
+    ) throws GeneralSecurityException, IOException {
         Dispatcher dispatcher = new Dispatcher();
-        
+
         dispatcher.setMaxRequests(Integer.MAX_VALUE);
         dispatcher.setMaxRequestsPerHost(Integer.MAX_VALUE);
 
@@ -83,20 +101,19 @@ public class RestExecutor implements AutoCloseable {
             .readTimeout(0, TimeUnit.MILLISECONDS)
             .dispatcher(dispatcher);
 
-        // Workaround for use self-signed certificate
-        if (Boolean.getBoolean("trust.all")) {
-            try {
-                SSLContext ctx = SSLContext.getInstance("TLS");
+        X509TrustManager trustMgr = 
trustManager(Boolean.getBoolean("trust.all"), trustStorePath, trustStorePwd);
 
-                // Create an SSLContext that uses our TrustManager
-                ctx.init(null, new TrustManager[] {trustManager()}, null);
+        SSLSocketFactory sslSocketFactory = sslSocketFactory(
+            keyStorePath, keyStorePwd,
+            trustMgr,
+            cipherSuites
+        );
 
-                builder.sslSocketFactory(ctx.getSocketFactory(), 
trustManager());
+        if (sslSocketFactory != null) {
+            builder.sslSocketFactory(sslSocketFactory, trustMgr);
 
-                builder.hostnameVerifier((hostname, session) -> true);
-            } catch (Exception ignored) {
-                LT.warn(log, "Failed to initialize the Trust Manager for 
\"-Dtrust.all\" option to skip certificate validation.");
-            }
+            if (!F.isEmpty(cipherSuites))
+                builder.connectionSpecs(sslConnectionSpec(cipherSuites));
         }
 
         httpClient = builder.build();
@@ -120,13 +137,10 @@ public class RestExecutor implements AutoCloseable {
 
             int status = holder.getSuccessStatus();
 
-            switch (status) {
-                case STATUS_SUCCESS:
-                    return RestResult.success(holder.getResponse(), 
holder.getSessionToken());
+            if (status == STATUS_SUCCESS)
+                return RestResult.success(holder.getResponse(), 
holder.getSessionToken());
 
-                default:
-                    return RestResult.fail(status, holder.getError());
-            }
+            return RestResult.fail(status, holder.getError());
         }
 
         if (res.code() == 401)
@@ -171,8 +185,20 @@ public class RestExecutor implements AutoCloseable {
         }
     }
 
-    /** */
-    public RestResult sendRequest(List<String> nodeURIs, Map<String, Object> 
params, Map<String, Object> headers) throws IOException {
+    /**
+     * Send request to cluster.
+     *
+     * @param nodeURIs List of cluster nodes URIs.
+     * @param params Map with reques params.
+     * @param headers Map with reques headers.
+     * @return Response from cluster.
+     * @throws IOException If failed to send request to cluster.
+     */
+    public RestResult sendRequest(
+        List<String> nodeURIs,
+        Map<String, Object> params,
+        Map<String, Object> headers
+    ) throws IOException {
         Integer startIdx = startIdxs.getOrDefault(nodeURIs, 0);
 
         int urlsCnt = nodeURIs.size();
@@ -196,7 +222,7 @@ public class RestExecutor implements AutoCloseable {
                 return res;
             }
             catch (ConnectException ignored) {
-                LT.warn(log, "Failed to connect to cluster [url=" + nodeUrl + 
"]");
+                LT.warn(log, "Failed connect to cluster [url=" + nodeUrl + 
"]");
             }
         }
 
@@ -274,10 +300,10 @@ public class RestExecutor implements AutoCloseable {
         }
 
         /**
-         * @param sesTokStr String representation of session token.
+         * @param sesTok String representation of session token.
          */
-        public void setSessionToken(String sesTokStr) {
-            this.sesTok = sesTokStr;
+        public void setSessionToken(String sesTok) {
+            this.sesTok = sesTok;
         }
     }
 

Reply via email to