IGNITE-9845 Web Console: Added support for two way SSL between browser, web server...
authorAlexey Kuznetsov <akuznetsov@apache.org>
Thu, 27 Dec 2018 09:36:02 +0000 (16:36 +0700)
committerAlexey Kuznetsov <akuznetsov@apache.org>
Thu, 27 Dec 2018 09:36:02 +0000 (16:36 +0700)
35 files changed:
modules/core/src/main/java/org/apache/ignite/ssl/DelegatingSSLContextSpi.java
modules/core/src/main/java/org/apache/ignite/ssl/SSLSocketFactoryWrapper.java
modules/web-console/assembly/README.txt
modules/web-console/backend/app/browsersHandler.js
modules/web-console/backend/app/settings.js
modules/web-console/backend/config/settings.json.sample
modules/web-console/backend/launch-tools.js
modules/web-console/backend/package.json
modules/web-console/frontend/app/app.js
modules/web-console/frontend/app/components/connected-clusters-badge/controller.js
modules/web-console/frontend/app/modules/agent/AgentManager.service.js
modules/web-console/frontend/app/modules/demo/Demo.module.js
modules/web-console/frontend/app/utils/SimpleWorkerPool.js
modules/web-console/frontend/app/vendor.js
modules/web-console/frontend/package.json
modules/web-console/frontend/webpack/webpack.dev.js
modules/web-console/web-agent/pom.xml
modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java
modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/ClusterListener.java
modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java
modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/rest/RestExecutor.java
modules/web-console/web-agent/src/test/java/org/apache/ignite/console/agent/rest/RestExecutorSelfTest.java [new file with mode: 0644]
modules/web-console/web-agent/src/test/java/org/apache/ignite/testsuites/IgniteWebAgentTestSuite.java [new file with mode: 0644]
modules/web-console/web-agent/src/test/resources/ca.jks [new file with mode: 0644]
modules/web-console/web-agent/src/test/resources/client.jks [new file with mode: 0644]
modules/web-console/web-agent/src/test/resources/generate.bat [new file with mode: 0644]
modules/web-console/web-agent/src/test/resources/generate.sh [new file with mode: 0644]
modules/web-console/web-agent/src/test/resources/jetty-with-ciphers-0.xml [new file with mode: 0644]
modules/web-console/web-agent/src/test/resources/jetty-with-ciphers-1.xml [new file with mode: 0644]
modules/web-console/web-agent/src/test/resources/jetty-with-ciphers-2.xml [new file with mode: 0644]
modules/web-console/web-agent/src/test/resources/jetty-with-ssl.xml [new file with mode: 0644]
modules/web-console/web-agent/src/test/resources/server.jks [new file with mode: 0644]

index d8621f2..360e922 100644 (file)
@@ -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} */
index bfe6d0d..992f836 100644 (file)
@@ -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));
     }
-
 }
index f1e114c..2656aca 100644 (file)
@@ -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_user@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_user@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`
index a7156c5..820c3d4 100644 (file)
@@ -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);
index 233566b..9c4d3c3 100644 (file)
@@ -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;
     }
 };
index aa20ab4..c16ba26 100644 (file)
@@ -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"
+    }
+  }
 }
index 3870b0d..0d6114f 100644 (file)
@@ -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);
 
index fb12748..4399ae7 100644 (file)
@@ -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": {
index e1cbdbb..fd75ade 100644 (file)
@@ -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',
index 294f955..896c02b 100644 (file)
 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();
     }
index 56eef35..0c0d9b6 100644 (file)
@@ -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();
index 9416bca..57f3a02 100644 (file)
@@ -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);
index 25c7ae4..b751dc3 100644 (file)
@@ -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() {
index 90808be..e9fc8e9 100644 (file)
@@ -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';
index 9d848df..aa6e037 100644 (file)
@@ -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": {
index 3af6377..59e3d45 100644 (file)
@@ -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
     }
 });
index 2a4864b..705f7ab 100644 (file)
@@ -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>
         <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>
     <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>
index bb2a8a2..1a919d0 100644 (file)
@@ -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();
     }
index 579f236..9553aac 100644 (file)
 
 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);
index 38edd97..2242eb1 100644 (file)
 
 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} */
index 33e4c2b..e0cd595 100644 (file)
@@ -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);
         }
     }
index 14d3d5d..a9f1579 100644 (file)
@@ -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();
index 24c2097..0db8904 100644 (file)
@@ -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"))
index d3bdcdd..b452b2c 100644 (file)
@@ -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;
         }
     }
 
diff --git a/modules/web-console/web-agent/src/test/java/org/apache/ignite/console/agent/rest/RestExecutorSelfTest.java b/modules/web-console/web-agent/src/test/java/org/apache/ignite/console/agent/rest/RestExecutorSelfTest.java
new file mode 100644 (file)
index 0000000..6a4fe6c
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+ * 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 org.apache.ignite.console.agent.rest;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import javax.net.ssl.SSLHandshakeException;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ConnectorConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.processors.rest.protocols.http.jetty.GridJettyObjectMapper;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Test for RestExecutor.
+ */
+public class RestExecutorSelfTest {
+    /** Name of the cache created by default in the cluster. */
+    private static final String DEFAULT_CACHE_NAME = "default";
+
+    /** Path to certificates and configs. */
+    private static final String PATH_TO_RESOURCES = "modules/web-console/web-agent/src/test/resources/";
+
+    /** JSON object mapper. */
+    private static final ObjectMapper MAPPER = new GridJettyObjectMapper();
+
+    /** */
+    private static final String HTTP_URI = "http://localhost:8080";
+
+    /** */
+    private static final String HTTPS_URI = "https://localhost:8080";
+
+    /** */
+    private static final String JETTY_WITH_SSL = "jetty-with-ssl.xml";
+
+    /** */
+    private static final String JETTY_WITH_CIPHERS_0 = "jetty-with-ciphers-0.xml";
+
+    /** */
+    private static final String JETTY_WITH_CIPHERS_1 = "jetty-with-ciphers-1.xml";
+
+    /** */
+    private static final String JETTY_WITH_CIPHERS_2 = "jetty-with-ciphers-2.xml";
+
+    /** This cipher is disabled by default in JDK 8. */
+    private static final List<String> CIPHER_0 = Collections.singletonList("TLS_DH_anon_WITH_AES_256_GCM_SHA384");
+
+    /** */
+    private static final List<String> CIPHER_1 = Collections.singletonList("TLS_RSA_WITH_NULL_SHA256");
+
+    /** */
+    private static final List<String> CIPHER_2 = Collections.singletonList("TLS_ECDHE_ECDSA_WITH_NULL_SHA");
+
+    /** */
+    private static final List<String> COMMON_CIPHERS = Arrays.asList(
+        "TLS_RSA_WITH_NULL_SHA256",
+        "TLS_ECDHE_ECDSA_WITH_NULL_SHA"
+    );
+
+    /** */
+    @Rule
+    public final ExpectedException ruleForExpectedException = ExpectedException.none();
+
+    /**
+     * @param jettyCfg Optional path to file with Jetty XML config.
+     * @return Prepare configuration for cluster node.
+     */
+    private IgniteConfiguration nodeConfiguration(String jettyCfg) {
+        TcpDiscoveryIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
+
+        ipFinder.registerAddresses(Collections.singletonList(new InetSocketAddress("127.0.0.1", 47500)));
+
+        TcpDiscoverySpi discoverySpi = new TcpDiscoverySpi();
+
+        discoverySpi.setIpFinder(ipFinder);
+
+        IgniteConfiguration cfg = new IgniteConfiguration();
+
+        cfg.setDiscoverySpi(discoverySpi);
+
+        CacheConfiguration<Integer, String> dfltCacheCfg = new CacheConfiguration<>(DEFAULT_CACHE_NAME);
+
+        cfg.setCacheConfiguration(dfltCacheCfg);
+
+        cfg.setIgniteInstanceName(UUID.randomUUID().toString());
+
+        if (!F.isEmpty(jettyCfg)) {
+            ConnectorConfiguration conCfg = new ConnectorConfiguration();
+            conCfg.setJettyPath(resolvePath(jettyCfg));
+
+            cfg.setConnectorConfiguration(conCfg);
+        }
+
+        return cfg;
+    }
+
+    /**
+     * Convert response to JSON.
+     *
+     * @param res REST result.
+     * @return JSON object.
+     * @throws IOException If failed to parse.
+     */
+    private JsonNode toJson(RestResult res) throws IOException {
+        Assert.assertNotNull(res);
+
+        String data = res.getData();
+
+        Assert.assertNotNull(data);
+        Assert.assertFalse(data.isEmpty());
+
+        return MAPPER.readTree(data);
+    }
+
+    /**
+     * @param file File name.
+     * @return Path to file.
+     */
+    private String resolvePath(String file) {
+        return IgniteUtils.resolveIgnitePath(PATH_TO_RESOURCES + file).getAbsolutePath();
+    }
+
+    /**
+     * Try to execute REST command and check response.
+     *
+     * @param nodeCfg Node configuration.
+     * @param uri Node URI.
+     * @param keyStore Key store.
+     * @param keyStorePwd Key store password.
+     * @param trustStore Trust store.
+     * @param trustStorePwd Trust store password.
+     * @param cipherSuites Cipher suites.
+     * @throws Exception If failed.
+     */
+    private void checkRest(
+        IgniteConfiguration nodeCfg,
+        String uri,
+        String keyStore,
+        String keyStorePwd,
+        String trustStore,
+        String trustStorePwd,
+        List<String> cipherSuites
+    ) throws Exception {
+        try(
+            Ignite ignite = Ignition.getOrStart(nodeCfg);
+            RestExecutor exec = new RestExecutor(keyStore, keyStorePwd, trustStore, trustStorePwd, cipherSuites)
+        ) {
+            Map<String, Object> params = new HashMap<>();
+            params.put("cmd", "top");
+            params.put("attr", false);
+            params.put("mtr", false);
+            params.put("caches", false);
+
+            RestResult res = exec.sendRequest(Collections.singletonList(uri), params, null);
+
+            JsonNode json = toJson(res);
+
+            Assert.assertTrue(json.isArray());
+
+            for (JsonNode item : json) {
+                Assert.assertTrue(item.get("attributes").isNull());
+                Assert.assertTrue(item.get("metrics").isNull());
+                Assert.assertTrue(item.get("caches").isNull());
+            }
+        }
+    }
+
+    /** */
+    @Test
+    public void nodeNoSslAgentNoSsl() throws Exception {
+        checkRest(
+            nodeConfiguration(""),
+            HTTP_URI,
+            null, null,
+            null, null,
+            null
+        );
+    }
+
+    /** */
+    @Test
+    public void nodeNoSslAgentWithSsl() throws Exception {
+        // Check Web Agent with SSL.
+        ruleForExpectedException.expect(SSLHandshakeException.class);
+        checkRest(
+            nodeConfiguration(""),
+            HTTPS_URI,
+            resolvePath("client.jks"), "123456",
+            resolvePath("ca.jks"), "123456",
+            null
+        );
+    }
+
+    /** */
+    @Test
+    public void nodeWithSslAgentNoSsl() throws Exception {
+        ruleForExpectedException.expect(IOException.class);
+        checkRest(
+            nodeConfiguration(JETTY_WITH_SSL),
+            HTTP_URI,
+            null, null,
+            null, null,
+            null
+        );
+    }
+
+    /** */
+    @Test
+    public void nodeWithSslAgentWithSsl() throws Exception {
+        checkRest(
+            nodeConfiguration(JETTY_WITH_SSL),
+            HTTPS_URI,
+            resolvePath("client.jks"), "123456",
+            resolvePath("ca.jks"), "123456",
+            null
+        );
+    }
+
+    /** */
+    @Test
+    public void nodeNoCiphersAgentWithCiphers() throws Exception {
+        ruleForExpectedException.expect(SSLHandshakeException.class);
+        checkRest(
+            nodeConfiguration(JETTY_WITH_SSL),
+            HTTPS_URI,
+            resolvePath("client.jks"), "123456",
+            resolvePath("ca.jks"), "123456",
+            CIPHER_0
+        );
+   }
+
+    /** */
+    @Test
+    public void nodeWithCiphersAgentNoCiphers() throws Exception {
+        ruleForExpectedException.expect(SSLHandshakeException.class);
+        checkRest(
+            nodeConfiguration(JETTY_WITH_CIPHERS_0),
+            HTTPS_URI,
+            resolvePath("client.jks"), "123456",
+            resolvePath("ca.jks"), "123456",
+            null
+        );
+   }
+
+    /** */
+    @Test
+    public void nodeWithCiphersAgentWithCiphers() throws Exception {
+        checkRest(
+            nodeConfiguration(JETTY_WITH_CIPHERS_1),
+            HTTPS_URI,
+            resolvePath("client.jks"), "123456",
+            resolvePath("ca.jks"), "123456",
+            CIPHER_1
+        );
+   }
+
+    /** */
+    @Test
+    public void differentCiphers1() throws Exception {
+        ruleForExpectedException.expect(SSLHandshakeException.class);
+        checkRest(
+            nodeConfiguration(JETTY_WITH_CIPHERS_1),
+            HTTPS_URI,
+            resolvePath("client.jks"), "123456",
+            resolvePath("ca.jks"), "123456",
+            CIPHER_2
+        );
+   }
+
+    /** */
+    @Test
+    public void differentCiphers2() throws Exception {
+        ruleForExpectedException.expect(SSLHandshakeException.class);
+        checkRest(
+            nodeConfiguration(JETTY_WITH_CIPHERS_2),
+            HTTPS_URI,
+            resolvePath("client.jks"), "123456",
+            resolvePath("ca.jks"), "123456",
+            CIPHER_1
+        );
+   }
+
+    /** */
+    @Test
+    public void commonCiphers() throws Exception {
+        checkRest(
+            nodeConfiguration(JETTY_WITH_CIPHERS_1),
+            HTTPS_URI,
+            resolvePath("client.jks"), "123456",
+            resolvePath("ca.jks"), "123456",
+            COMMON_CIPHERS
+        );
+   }
+}
diff --git a/modules/web-console/web-agent/src/test/java/org/apache/ignite/testsuites/IgniteWebAgentTestSuite.java b/modules/web-console/web-agent/src/test/java/org/apache/ignite/testsuites/IgniteWebAgentTestSuite.java
new file mode 100644 (file)
index 0000000..d0bc238
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 org.apache.ignite.testsuites;
+
+import org.apache.ignite.console.agent.rest.RestExecutorSelfTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * Web Agent tests.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    RestExecutorSelfTest.class
+})
+public class IgniteWebAgentTestSuite {
+    // No-op.
+}
diff --git a/modules/web-console/web-agent/src/test/resources/ca.jks b/modules/web-console/web-agent/src/test/resources/ca.jks
new file mode 100644 (file)
index 0000000..9d50bcb
Binary files /dev/null and b/modules/web-console/web-agent/src/test/resources/ca.jks differ
diff --git a/modules/web-console/web-agent/src/test/resources/client.jks b/modules/web-console/web-agent/src/test/resources/client.jks
new file mode 100644 (file)
index 0000000..197c75b
Binary files /dev/null and b/modules/web-console/web-agent/src/test/resources/client.jks differ
diff --git a/modules/web-console/web-agent/src/test/resources/generate.bat b/modules/web-console/web-agent/src/test/resources/generate.bat
new file mode 100644 (file)
index 0000000..7bc87f1
--- /dev/null
@@ -0,0 +1,122 @@
+::
+:: 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.
+::
+
+::
+:: SSL certificates generation.
+::
+
+::
+:: Preconditions:
+::  1. If needed, download Open SSL for Windows from "https://wiki.openssl.org/index.php/Binaries".
+::   and unpack it to some folder.
+::  2. If needed, install JDK 8 or newer. We need "keytool" from "JDK/bin."
+::  3. Create "openssl.cnf" in some folder.
+::     You may use "https://github.com/openssl/openssl/blob/master/apps/openssl.cnf" as template.
+::  4. If needed, add "opensll" & "keytool" to PATH variable.
+::
+::  NOTE: In case of custom SERVER_DOMAIN_NAME you may need to tweak your "etc/hosts" file.
+::
+
+:: Set Open SSL variables.
+set RANDFILE=_path_where_open_ssl_was_unpacked\.rnd
+set OPENSSL_CONF=_path_where_open_ssl_was_unpacked\openssl.cnf
+
+:: Certificates password.
+set PWD=p123456
+
+:: Server.
+set SERVER_DOMAIN_NAME=localhost
+set SERVER_EMAIL=support@test.com
+
+:: Client.
+set CLIENT_DOMAIN_NAME=localhost
+set CLIENT_EMAIL=client@test.com
+
+:: Cleanup.
+del server.*
+del client.*
+del ca.*
+
+:: Generate server config.
+(
+echo [req]
+echo prompt                 = no
+echo distinguished_name     = dn
+echo req_extensions         = req_ext
+
+echo [ dn ]
+echo countryName            = RU
+echo stateOrProvinceName    = Test
+echo localityName           = Test
+echo organizationName       = Apache
+echo commonName             = %SERVER_DOMAIN_NAME%
+echo organizationalUnitName = IT
+echo emailAddress           = %SERVER_EMAIL%
+
+echo [ req_ext ]
+echo subjectAltName         = @alt_names
+
+echo [ alt_names ]
+echo DNS.1                  = %SERVER_DOMAIN_NAME%
+) > "server.cnf"
+
+:: Generate client config.
+(
+echo [req]
+echo prompt                 = no
+echo distinguished_name     = dn
+echo req_extensions         = req_ext
+
+echo [ dn ]
+echo countryName            = RU
+echo stateOrProvinceName    = Test
+echo localityName           = Test
+echo organizationName       = Apache
+echo commonName             = %CLIENT_DOMAIN_NAME%
+echo organizationalUnitName = IT
+echo emailAddress           = %CLIENT_EMAIL%
+
+echo [ req_ext ]
+echo subjectAltName         = @alt_names
+
+echo [ alt_names ]
+echo DNS.1                  = %CLIENT_DOMAIN_NAME%
+) > "client.cnf"
+
+:: Generate certificates.
+openssl genrsa -des3 -passout pass:%PWD% -out server.key 1024
+openssl req -new -passin pass:%PWD% -key server.key -config server.cnf -out server.csr
+
+openssl req -new -newkey rsa:1024 -nodes -keyout ca.key -x509 -days 365 -config server.cnf -out ca.crt
+
+openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -extensions req_ext -extfile server.cnf -out server.crt
+openssl rsa -passin pass:%PWD% -in server.key -out server.nopass.key
+
+openssl req -new -utf8 -nameopt multiline,utf8 -newkey rsa:1024 -nodes -keyout client.key -config client.cnf -out client.csr
+openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out client.crt
+
+openssl pkcs12 -export -in server.crt -inkey server.key -certfile server.crt -out server.p12 -passin pass:%PWD% -passout pass:%PWD%
+openssl pkcs12 -export -in client.crt -inkey client.key -certfile ca.crt -out client.p12 -passout pass:%PWD%
+openssl pkcs12 -export -in ca.crt -inkey ca.key -certfile ca.crt -out ca.p12 -passout pass:%PWD%
+
+keytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore server.jks -deststoretype JKS -noprompt -srcstorepass %PWD% -deststorepass %PWD%
+keytool -importkeystore -srckeystore client.p12 -srcstoretype PKCS12 -destkeystore client.jks -deststoretype JKS -noprompt -srcstorepass %PWD% -deststorepass %PWD%
+keytool -importkeystore -srckeystore ca.p12 -srcstoretype PKCS12 -destkeystore ca.jks -deststoretype JKS -noprompt -srcstorepass %PWD% -deststorepass %PWD%
+
+openssl x509 -text -noout -in server.crt
+openssl x509 -text -noout -in client.crt
+openssl x509 -text -noout -in ca.crt
diff --git a/modules/web-console/web-agent/src/test/resources/generate.sh b/modules/web-console/web-agent/src/test/resources/generate.sh
new file mode 100644 (file)
index 0000000..95e62c3
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+#
+# SSL certificates generation.
+#
+
+#
+# Preconditions:
+#  1. If needed, install Open SSL (for example: "sudo apt-get install openssl")
+#  2. If needed, install JDK 8 or newer. We need "keytool" from "JDK/bin".
+#  3. Create "openssl.cnf" in some folder (for example: "/opt/openssl").
+#     You may use "https://github.com/openssl/openssl/blob/master/apps/openssl.cnf" as template.
+#  4. If needed, add "opensll" & "keytool" to PATH variable.
+#
+#  NOTE: In case of custom SERVER_DOMAIN_NAME you may need to tweak your "etc/hosts" file.
+#
+
+set -x
+
+# Set Open SSL variables.
+OPENSSL_CONF=/opt/openssl/openssl.cnf
+
+# Certificates password.
+PWD=p123456
+
+# Server.
+SERVER_DOMAIN_NAME=localhost
+SERVER_EMAIL=support@test.local
+
+# Client.
+CLIENT_DOMAIN_NAME=localhost
+CLIENT_EMAIL=client@test.local
+
+# Cleanup.
+rm -vf server.*
+rm -vf client.*
+rm -vf ca.*
+
+# Generate server config.
+cat << EOF > server.cnf
+[req]
+prompt                 = no
+distinguished_name     = dn
+req_extensions         = req_ext
+[ dn ]
+countryName            = RU
+stateOrProvinceName    = Moscow
+localityName           = Moscow
+organizationName       = test
+commonName             = ${SERVER_DOMAIN_NAME}
+organizationalUnitName = IT
+emailAddress           = ${SERVER_EMAIL}
+[ req_ext ]
+subjectAltName         = @alt_names
+[ alt_names ]
+DNS.1                  = ${SERVER_DOMAIN_NAME}
+EOF
+
+# Generate client config.
+cat << EOF > client.cnf
+[req]
+prompt                 = no
+distinguished_name     = dn
+req_extensions         = req_ext
+[ dn ]
+countryName            = RU
+stateOrProvinceName    = Moscow
+localityName           = Moscow
+organizationName       = test
+commonName             = ${CLIENT_DOMAIN_NAME}
+organizationalUnitName = IT
+emailAddress           = ${CLIENT_EMAIL}
+[ req_ext ]
+subjectAltName         = @alt_names
+[ alt_names ]
+DNS.1                  = ${CLIENT_DOMAIN_NAME}
+EOF
+
+# Generate certificates.
+openssl genrsa -des3 -passout pass:${PWD} -out server.key 1024
+openssl req -new -passin pass:${PWD} -key server.key -config server.cnf -out server.csr
+openssl req -new -newkey rsa:1024 -nodes -keyout ca.key -x509 -days 365 -config server.cnf -out ca.crt
+openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -extensions req_ext -extfile server.cnf -out server.crt
+openssl rsa -passin pass:${PWD} -in server.key -out server.nopass.key
+openssl req -new -utf8 -nameopt multiline,utf8 -newkey rsa:1024 -nodes -keyout client.key -config client.cnf -out client.csr
+openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out client.crt
+openssl pkcs12 -export -in server.crt -inkey server.key -certfile server.crt -out server.p12 -passin pass:${PWD} -passout pass:${PWD}
+openssl pkcs12 -export -in client.crt -inkey client.key -certfile ca.crt -out client.p12 -passout pass:${PWD}
+openssl pkcs12 -export -in ca.crt -inkey ca.key -certfile ca.crt -out ca.p12 -passout pass:${PWD}
+keytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore server.jks -deststoretype JKS -noprompt -srcstorepass ${PWD} -deststorepass ${PWD}
+keytool -importkeystore -srckeystore client.p12 -srcstoretype PKCS12 -destkeystore client.jks -deststoretype JKS -noprompt -srcstorepass ${PWD} -deststorepass ${PWD}
+keytool -importkeystore -srckeystore ca.p12 -srcstoretype PKCS12 -destkeystore ca.jks -deststoretype JKS -noprompt -srcstorepass ${PWD} -deststorepass ${PWD}
+openssl x509 -text -noout -in server.crt
+openssl x509 -text -noout -in client.crt
+openssl x509 -text -noout -in ca.crt
diff --git a/modules/web-console/web-agent/src/test/resources/jetty-with-ciphers-0.xml b/modules/web-console/web-agent/src/test/resources/jetty-with-ciphers-0.xml
new file mode 100644 (file)
index 0000000..40f08b5
--- /dev/null
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+
+<!--
+  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.
+  -->
+
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <Arg name="threadPool">
+        <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+            <Set name="minThreads">5</Set>
+            <Set name="maxThreads">10</Set>
+        </New>
+    </Arg>
+
+    <New id="httpsCfg" class="org.eclipse.jetty.server.HttpConfiguration">
+        <Set name="secureScheme">https</Set>
+        <Set name="securePort"><SystemProperty name="IGNITE_JETTY_PORT" default="8080"/></Set>
+        <Set name="sendServerVersion">true</Set>
+        <Set name="sendDateHeader">true</Set>
+        <Call name="addCustomizer">
+            <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+        </Call>
+    </New>
+
+    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+        <Set name="keyStorePath">modules/web-console/web-agent/src/test/resources/server.jks</Set>
+        <Set name="keyStorePassword">123456</Set>
+        <Set name="trustStorePath">modules/web-console/web-agent/src/test/resources/ca.jks</Set>
+        <Set name="trustStorePassword">123456</Set>
+        <Set name="needClientAuth">true</Set>
+        <Set name="includeCipherSuites">
+            <Array type="java.lang.String">
+                <Item>TLS_DH_anon_WITH_AES_256_GCM_SHA384</Item>
+            </Array>
+        </Set>
+    </New>
+
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.ServerConnector">
+                <Arg name="server">
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg name="factories">
+                    <Array type="org.eclipse.jetty.server.ConnectionFactory">
+                        <Item>
+                            <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                                <Arg><Ref refid="sslContextFactory"/></Arg>
+                                <Arg>http/1.1</Arg>
+                            </New>
+                        </Item>
+                        <Item>
+                            <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                                <Ref refid="httpsCfg"/>
+                            </New>
+                        </Item>
+                    </Array>
+                </Arg>
+                <Set name="host"><SystemProperty name="IGNITE_JETTY_HOST" default="localhost"/></Set>
+                <Set name="port"><SystemProperty name="IGNITE_JETTY_PORT" default="8080"/></Set>
+                <Set name="idleTimeout">30000</Set>
+                <Set name="reuseAddress">true</Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <Set name="handler">
+        <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+            <Set name="handlers">
+                <Array type="org.eclipse.jetty.server.Handler">
+                    <Item>
+                        <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+                    </Item>
+                </Array>
+            </Set>
+        </New>
+    </Set>
+
+    <Set name="stopAtShutdown">false</Set>
+</Configure>
diff --git a/modules/web-console/web-agent/src/test/resources/jetty-with-ciphers-1.xml b/modules/web-console/web-agent/src/test/resources/jetty-with-ciphers-1.xml
new file mode 100644 (file)
index 0000000..cb3a293
--- /dev/null
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+
+<!--
+  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.
+  -->
+
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <Arg name="threadPool">
+        <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+            <Set name="minThreads">5</Set>
+            <Set name="maxThreads">10</Set>
+        </New>
+    </Arg>
+
+    <New id="httpsCfg" class="org.eclipse.jetty.server.HttpConfiguration">
+        <Set name="secureScheme">https</Set>
+        <Set name="securePort"><SystemProperty name="IGNITE_JETTY_PORT" default="8080"/></Set>
+        <Set name="sendServerVersion">true</Set>
+        <Set name="sendDateHeader">true</Set>
+        <Call name="addCustomizer">
+            <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+        </Call>
+    </New>
+
+    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+        <Set name="keyStorePath">modules/web-console/web-agent/src/test/resources/server.jks</Set>
+        <Set name="keyStorePassword">123456</Set>
+        <Set name="trustStorePath">modules/web-console/web-agent/src/test/resources/ca.jks</Set>
+        <Set name="trustStorePassword">123456</Set>
+        <Set name="needClientAuth">true</Set>
+        <Set name="includeCipherSuites">
+            <Array type="java.lang.String">
+                <Item>TLS_RSA_WITH_NULL_SHA256</Item>
+            </Array>
+        </Set>
+    </New>
+
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.ServerConnector">
+                <Arg name="server">
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg name="factories">
+                    <Array type="org.eclipse.jetty.server.ConnectionFactory">
+                        <Item>
+                            <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                                <Arg><Ref refid="sslContextFactory"/></Arg>
+                                <Arg>http/1.1</Arg>
+                            </New>
+                        </Item>
+                        <Item>
+                            <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                                <Ref refid="httpsCfg"/>
+                            </New>
+                        </Item>
+                    </Array>
+                </Arg>
+                <Set name="host"><SystemProperty name="IGNITE_JETTY_HOST" default="localhost"/></Set>
+                <Set name="port"><SystemProperty name="IGNITE_JETTY_PORT" default="8080"/></Set>
+                <Set name="idleTimeout">30000</Set>
+                <Set name="reuseAddress">true</Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <Set name="handler">
+        <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+            <Set name="handlers">
+                <Array type="org.eclipse.jetty.server.Handler">
+                    <Item>
+                        <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+                    </Item>
+                </Array>
+            </Set>
+        </New>
+    </Set>
+
+    <Set name="stopAtShutdown">false</Set>
+</Configure>
diff --git a/modules/web-console/web-agent/src/test/resources/jetty-with-ciphers-2.xml b/modules/web-console/web-agent/src/test/resources/jetty-with-ciphers-2.xml
new file mode 100644 (file)
index 0000000..2251de2
--- /dev/null
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+
+<!--
+  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.
+  -->
+
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <Arg name="threadPool">
+        <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+            <Set name="minThreads">5</Set>
+            <Set name="maxThreads">10</Set>
+        </New>
+    </Arg>
+
+    <New id="httpsCfg" class="org.eclipse.jetty.server.HttpConfiguration">
+        <Set name="secureScheme">https</Set>
+        <Set name="securePort"><SystemProperty name="IGNITE_JETTY_PORT" default="8080"/></Set>
+        <Set name="sendServerVersion">true</Set>
+        <Set name="sendDateHeader">true</Set>
+        <Call name="addCustomizer">
+            <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+        </Call>
+    </New>
+
+    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+        <Set name="keyStorePath">modules/web-console/web-agent/src/test/resources/server.jks</Set>
+        <Set name="keyStorePassword">123456</Set>
+        <Set name="trustStorePath">modules/web-console/web-agent/src/test/resources/ca.jks</Set>
+        <Set name="trustStorePassword">123456</Set>
+        <Set name="needClientAuth">true</Set>
+        <Set name="includeCipherSuites">
+            <Array type="java.lang.String">
+                <Item>TLS_ECDHE_ECDSA_WITH_NULL_SHA</Item>
+            </Array>
+        </Set>
+    </New>
+
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.ServerConnector">
+                <Arg name="server">
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg name="factories">
+                    <Array type="org.eclipse.jetty.server.ConnectionFactory">
+                        <Item>
+                            <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                                <Arg><Ref refid="sslContextFactory"/></Arg>
+                                <Arg>http/1.1</Arg>
+                            </New>
+                        </Item>
+                        <Item>
+                            <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                                <Ref refid="httpsCfg"/>
+                            </New>
+                        </Item>
+                    </Array>
+                </Arg>
+                <Set name="host"><SystemProperty name="IGNITE_JETTY_HOST" default="localhost"/></Set>
+                <Set name="port"><SystemProperty name="IGNITE_JETTY_PORT" default="8080"/></Set>
+                <Set name="idleTimeout">30000</Set>
+                <Set name="reuseAddress">true</Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <Set name="handler">
+        <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+            <Set name="handlers">
+                <Array type="org.eclipse.jetty.server.Handler">
+                    <Item>
+                        <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+                    </Item>
+                </Array>
+            </Set>
+        </New>
+    </Set>
+
+    <Set name="stopAtShutdown">false</Set>
+</Configure>
diff --git a/modules/web-console/web-agent/src/test/resources/jetty-with-ssl.xml b/modules/web-console/web-agent/src/test/resources/jetty-with-ssl.xml
new file mode 100644 (file)
index 0000000..7e06829
--- /dev/null
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+
+<!--
+  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.
+  -->
+
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <Arg name="threadPool">
+        <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+            <Set name="minThreads">5</Set>
+            <Set name="maxThreads">10</Set>
+        </New>
+    </Arg>
+
+    <New id="httpsCfg" class="org.eclipse.jetty.server.HttpConfiguration">
+        <Set name="secureScheme">https</Set>
+        <Set name="securePort"><SystemProperty name="IGNITE_JETTY_PORT" default="8080"/></Set>
+        <Set name="sendServerVersion">true</Set>
+        <Set name="sendDateHeader">true</Set>
+        <Call name="addCustomizer">
+            <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
+        </Call>
+    </New>
+
+    <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
+        <Set name="keyStorePath">modules/web-console/web-agent/src/test/resources/server.jks</Set>
+        <Set name="keyStorePassword">123456</Set>
+        <Set name="trustStorePath">modules/web-console/web-agent/src/test/resources/ca.jks</Set>
+        <Set name="trustStorePassword">123456</Set>
+        <Set name="needClientAuth">true</Set>
+    </New>
+
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.ServerConnector">
+                <Arg name="server">
+                    <Ref refid="Server"/>
+                </Arg>
+                <Arg name="factories">
+                    <Array type="org.eclipse.jetty.server.ConnectionFactory">
+                        <Item>
+                            <New class="org.eclipse.jetty.server.SslConnectionFactory">
+                                <Arg><Ref refid="sslContextFactory"/></Arg>
+                                <Arg>http/1.1</Arg>
+                            </New>
+                        </Item>
+                        <Item>
+                            <New class="org.eclipse.jetty.server.HttpConnectionFactory">
+                                <Ref refid="httpsCfg"/>
+                            </New>
+                        </Item>
+                    </Array>
+                </Arg>
+                <Set name="host"><SystemProperty name="IGNITE_JETTY_HOST" default="localhost"/></Set>
+                <Set name="port"><SystemProperty name="IGNITE_JETTY_PORT" default="8080"/></Set>
+                <Set name="idleTimeout">30000</Set>
+                <Set name="reuseAddress">true</Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <Set name="handler">
+        <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+            <Set name="handlers">
+                <Array type="org.eclipse.jetty.server.Handler">
+                    <Item>
+                        <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+                    </Item>
+                </Array>
+            </Set>
+        </New>
+    </Set>
+
+    <Set name="stopAtShutdown">false</Set>
+</Configure>
diff --git a/modules/web-console/web-agent/src/test/resources/server.jks b/modules/web-console/web-agent/src/test/resources/server.jks
new file mode 100644 (file)
index 0000000..c673bb0
Binary files /dev/null and b/modules/web-console/web-agent/src/test/resources/server.jks differ